Skip to content
Snippets Groups Projects
time.js 69.9 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* Time namespace
    
    	Use Time prefix when accessing any getters or functions (e.g. Time.second, Time.schoolDay, or Time.getLastDayOfMonth(), etc.)
    	Getters: (Most of these are being used in one way or another)
    
    Crimson Tide's avatar
    Crimson Tide committed
    
    
    	Time.date - Returns Date object of current date.
    
    Crimson Tide's avatar
    Crimson Tide committed
    
    
    	Time.holidayMonths - Returns array of all months that are considered holidays.
    
    	Time.second - Returns current number of seconds since last whole minute.
    
    	Time.minute - Returns current number of minutes since last whole hour.
    
    	Time.hour - Returns current hour of the day.
    
    	Time.weekDay - previously $weekday - Returns day of the week (1 being sunday, 7 being saturday)
    
    	Time.weekDayName - Returns the day of the week, with first letter in uppercase. (e.g. Monday)
    
    	Time.monthDay - Returns current date - (e.g. 12 if its the 12th)
    
    	Time.month - previously $month - Returns current month of the year (1 being january, 12 being december)
    
    	Time.monthName - Returns name of the current month, with first letter in uppercase. (e.g. January)
    
    	Time.year - Returns current year
    
    	Time.days - Returns total number of days since game start. (starts at 0)
    
    	Time.season - Previously $season - Returns string of current season (e.g. "winter")
    
    	Time.startDate - Returns Date object of start date
    
    	Time.tomorrow - Returns Date object of day after today
    
    	Time.yesterday - Returns Date object of day before today
    
    	Time.schoolTerm - Returns true if current day is during a school term, and false if a holiday.
    
    	Time.schoolDay - Returns true if current day is a school day, and false otherwise
    
    	Time.schoolTime - Returns true if current time is between 8-15 and is a school day
    
    	Time.dayState - previously $daystate - Returns string of day state (e.g. "dawn", or "day")
    
    	Time.nightState - previously $nightstate- Returns string of night state (e.g. "evening", or "morning")
    
    	Time.nextSchoolTermStartDate - Returns date object of the day when the next school term starts
    
    	Time.nextSchoolTermEndDate - Returns date object of the day when the current school term ends
    
    */
    
    
    xao's avatar
    xao committed
    const Time = (() => {
    
    	const moonPhases = {
    		new: {
    			start: 0,
    			end: 0.03,
    			endAlt: 1,
    			description: "New Moon",
    		},
    		waxingCrescent: {
    			start: 0.03,
    			end: 0.22,
    			description: "Waxing Crescent",
    		},
    		firstQuarter: {
    			start: 0.22,
    			end: 0.28,
    			description: "First Quarter",
    		},
    		waxingGibbous: {
    			start: 0.28,
    			end: 0.47,
    			description: "Waxing Gibbous",
    		},
    		full: {
    			start: 0.47,
    			end: 0.53,
    			description: "Full Moon",
    		},
    		waningGibbous: {
    			start: 0.53,
    			end: 0.72,
    			description: "Waning Gibbous",
    		},
    		lastQuarter: {
    			start: 0.72,
    			end: 0.78,
    			description: "Last Quarter",
    		},
    		waningCrescent: {
    			start: 0.78,
    			end: 0.97,
    			description: "Waning Crescent",
    		},
    	};
    
    majou's avatar
    majou committed
    	const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
    	const daysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
    
    
    Braymann's avatar
    Braymann committed
    	const holidayMonths = [4, 7, 8, 12];
    
    majou's avatar
    majou committed
    
    
    xao's avatar
    xao committed
    	let currentDate = {};
    
    
    	function set(time = V.timeStamp) {
    
    Purity's avatar
    Purity committed
    		V.startDate ??= new DateTime(2022, 9, 4, 7).timeStamp;
    
    
    		if (time instanceof DateTime) {
    			currentDate = time;
    			V.timeStamp = time.timeStamp - V.startDate;
    		} else {
    			currentDate = new DateTime(V.startDate + time);
    			V.timeStamp = time;
    		}
    
    xao's avatar
    xao committed
    	}
    	/*
    	 * Changes date without "passing time"
    	 */
    	function setDate(date) {
    		set(date.timeStamp - V.startDate);
    	}
    
    	function setTime(hour, minute) {
    		setDate(new DateTime(currentDate.year, currentDate.month, currentDate.day, hour || 0, minute || 0));
    	}
    
    
    	function setTimeRelative(hour, minute) {
    		setDate(new DateTime(currentDate.year, currentDate.month, currentDate.day, currentDate.hour + (hour || 0), currentDate.minute + (minute || 0)));
    	}
    
    
    xao's avatar
    xao committed
    	/**
    	 *
    	 * Pass X amount of seconds, executing code after reaching certain thresholds.
    	 * Checks for: year, week, day, hour, minute, dawn, noon.
    	 * Currently no function is called when only passing seconds (if minute mark is not reached)
    	 *
    	 * @param {number} seconds
    	 */
    	function pass(seconds) {
    
    		const fragment = document.createDocumentFragment();
    
    		if (seconds < 0) return fragment;
    
    xao's avatar
    xao committed
    
    		const prevDate = new DateTime(currentDate);
    		set(V.timeStamp + seconds);
    
    		const minutes = Math.floor((currentDate.timeStamp - prevDate.timeStamp) / 60) || (60 + (currentDate.minute - prevDate.minute)) % 60;
    
    		if (!minutes) return fragment;
    
    		fragment.append(minutePassed(minutes));
    
    xao's avatar
    xao committed
    
    		const hours = Math.floor(minutes / 60) || (24 + (currentDate.hour - prevDate.hour)) % 24;
    
    		if (!hours) return fragment;
    
    		fragment.append(hourPassed(hours));
    
    		if (
    			!V.daily.dawnCheck &&
    			((prevDate.hour < 6 && (currentDate.hour >= 6 || currentDate.day !== prevDate.day)) || (currentDate.day !== prevDate.day && currentDate.hour >= 6))
    		) {
    
    xao's avatar
    xao committed
    			V.daily.dawnCheck = true;
    
    			fragment.append(dawnCheck());
    
    		if (
    			!V.daily.noonCheck &&
    			((prevDate.hour < 12 && (currentDate.hour >= 12 || currentDate.day !== prevDate.day)) ||
    				(currentDate.day !== prevDate.day && currentDate.hour >= 12))
    		) {
    
    xao's avatar
    xao committed
    			V.daily.noonCheck = true;
    
    			fragment.append(noonCheck());
    
    xao's avatar
    xao committed
    		}
    
    		const days = Math.floor(hours / 24) || (prevDate.lastDayOfMonth + currentDate.day - prevDate.day) % prevDate.lastDayOfMonth;
    
    		if (!days) return fragment;
    
    		if (prevDate.weekDay === 7 && currentDate.weekDay === 1) {
    			fragment.append(weekPassed());
    		}
    
    Purity's avatar
    Purity committed
    		fragment.append(dayPassed());
    
    		if (prevDate.yearDay < Time.startDate.yearDay && currentDate.yearDay >= Time.startDate.yearDay) {
    			fragment.append(yearPassed());
    		}
    
    		return fragment;
    
    	function getNextSchoolTermStartDate(date) {
    
    xao's avatar
    xao committed
    		const newDate = new DateTime(date);
    
    Purity's avatar
    Purity committed
    		while (holidayMonths.includes(newDate.month)) {
    			newDate.addMonths(1);
    
    xao's avatar
    xao committed
    		}
    
    
    xao's avatar
    xao committed
    		return newDate.getFirstWeekdayOfMonth(2);
    	}
    
    
    	function getNextSchoolTermEndDate(date) {
    
    xao's avatar
    xao committed
    		const newDate = new DateTime(date);
    
    xao's avatar
    xao committed
    		newDate.addMonths(holidayMonths.find(e => e >= newDate.month) - newDate.month);
    		return newDate.getFirstWeekdayOfMonth(2).addDays(-3).addHours(15);
    
    xao's avatar
    xao committed
    	}
    
    	function isSchoolTerm(date) {
    
    		let termEndDate = getNextSchoolTermEndDate(date);
    		termEndDate = new DateTime(termEndDate.year, termEndDate.month, termEndDate.day);
    		termEndDate.addDays(1);
    
    xao's avatar
    xao committed
    		const firstMonday = date.getFirstWeekdayOfMonth(2);
    
    		const prevMonth = ((date.month - 2 + 12) % 12) + 1;
    
    Kirsty's avatar
    Kirsty committed
    
    
    		return !(
    			date.timeStamp >= termEndDate.timeStamp ||
    			(holidayMonths.includes(date.month) && date.day >= firstMonday.day) ||
    			(holidayMonths.includes(prevMonth) && date.day < firstMonday.day)
    
    xao's avatar
    xao committed
    		);
    
    xao's avatar
    xao committed
    	function isSchoolDay(date) {
    		return isSchoolTerm(date) && date.weekDay > 1 && date.weekDay < 7;
    	}
    
    xao's avatar
    xao committed
    	function isSchoolTime(date) {
    
    majou's avatar
    majou committed
    		return isSchoolDay(date) && date.hour > 8 && date.hour < 15;
    
    	function getDayOfYear(date) {
    		const start = new DateTime(date.year, 1, 1);
    		const diff = date.timeStamp - start.timeStamp;
    
    Jimmys's avatar
    Jimmys committed
    		return Math.floor(diff / TimeConstants.secondsPerDay);
    
    	}
    
    	function getSecondsSinceMidnight(date) {
    
    Jimmys's avatar
    Jimmys committed
    		return date.hour * TimeConstants.secondsPerHour + date.minute * TimeConstants.secondsPerMinute;
    
    	}
    
    	// Current moon phase
    	function currentMoonPhase(date) {
    		const phaseFraction = date.moonPhaseFraction;
    		for (const phase in moonPhases) {
    			const range = moonPhases[phase];
    			if ((phaseFraction >= range.start && phaseFraction < range.end) || (range.endAlt && phaseFraction >= 0.97)) {
    				return phase;
    			}
    		}
    	}
    	// Date of previous occurrence of a specific moon phase
    
    	// Example: Time.previousMoonPhase("full")
    
    	function previousMoonPhase(targetPhase) {
    
    		if (!(targetPhase in moonPhases)) {
    			throw new Error(`Invalid moon phase: ${targetPhase}`);
    		}
    
    
    		const date = new DateTime(currentDate.year, currentDate.month, currentDate.day, 0, 0);
    		do {
    			date.addDays(-1);
    			const currentPhase = currentMoonPhase(date);
    			if (currentPhase === targetPhase) {
    				return date;
    			}
    		} while (true);
    	}
    
    	// Date of next occurrence of a specific moon phase
    
    	// Example: Time.nextMoonPhase("full")
    
    	function nextMoonPhase(targetPhase) {
    
    		if (!(targetPhase in moonPhases)) {
    			throw new Error(`Invalid moon phase: ${targetPhase}`);
    		}
    
    
    		const date = new DateTime(currentDate.year, currentDate.month, currentDate.day, 0, 0);
    		do {
    			date.addDays(1);
    			const currentPhase = currentMoonPhase(date);
    			if (currentPhase === targetPhase) {
    				return date;
    			}
    		} while (true);
    	}
    
    	function isBloodMoon(date) {
    
    Purity's avatar
    Purity committed
    		date ??= currentDate;
    
    		return (date.day === date.lastDayOfMonth && date.hour >= 21) || (date.day === 1 && date.hour < 6);
    	}
    
    
    	function getSeason(date) {
    		return date.month > 11 || date.month < 3 ? "winter" : date.month > 8 ? "autumn" : date.month > 5 ? "summer" : "spring";
    	}
    
    
    xao's avatar
    xao committed
    	return Object.create({
    		get date() {
    			return currentDate;
    		},
    		get holidayMonths() {
    			return holidayMonths;
    		},
    		get second() {
    			return currentDate.second;
    		},
    		get minute() {
    			return currentDate.minute;
    		},
    		get hour() {
    			return currentDate.hour;
    		},
    		get weekDay() {
    			return currentDate.weekDay;
    		},
    		get weekDayName() {
    			return currentDate.weekDayName;
    		},
    		get monthDay() {
    			return currentDate.day;
    		},
    		get month() {
    			return currentDate.month;
    		},
    		get monthName() {
    			return currentDate.monthName;
    		},
    		get year() {
    			return currentDate.year;
    		},
    		get days() {
    
    			return Math.floor((currentDate.timeStamp - Time.startDate.timeStamp) / TimeConstants.secondsPerDay);
    
    xao's avatar
    xao committed
    		},
    		get season() {
    
    			return getSeason(currentDate);
    
    xao's avatar
    xao committed
    		},
    		set startDate(value) {
    			V.startDate = value.timeStamp;
    		},
    		get startDate() {
    			return new DateTime(V.startDate);
    		},
    		get tomorrow() {
    			const date = new DateTime(currentDate);
    			return date.addDays(1);
    		},
    		get yesterday() {
    			const date = new DateTime(currentDate);
    			return date.addDays(-1);
    		},
    		get schoolTerm() {
    			return isSchoolTerm(currentDate);
    		},
    		get schoolDay() {
    			return isSchoolDay(currentDate);
    		},
    		get schoolTime() {
    			return isSchoolTime(currentDate);
    		},
    		get dayState() {
    
    			const hour = currentDate.hour;
    
    Jimmys's avatar
    Jimmys committed
    			if (hour < 6 || hour >= 21) {
    				return "night";
    			}
    			if (hour >= 18) {
    				return "dusk";
    			}
    			return hour >= 9 ? "day" : "dawn";
    
    xao's avatar
    xao committed
    		},
    		get nextSchoolTermStartDate() {
    
    			return getNextSchoolTermStartDate(currentDate);
    
    xao's avatar
    xao committed
    		},
    		get nextSchoolTermEndDate() {
    
    			return getNextSchoolTermEndDate(currentDate);
    
    xao's avatar
    xao committed
    		},
    		get lastDayOfMonth() {
    			return currentDate.lastDayOfMonth;
    		},
    
    		get dayOfYear() {
    			return getDayOfYear(currentDate);
    		},
    		get secondsSinceMidnight() {
    			return getSecondsSinceMidnight(currentDate);
    		},
    		get currentMoonPhase() {
    			return currentMoonPhase(currentDate);
    		},
    
    xao's avatar
    xao committed
    		set,
    		setDate,
    		setTime,
    
    		setTimeRelative,
    
    xao's avatar
    xao committed
    		pass,
    		isSchoolTerm,
    		isSchoolDay,
    		isSchoolTime,
    
    		getDayOfYear,
    		getSecondsSinceMidnight,
    		nextMoonPhase,
    		previousMoonPhase,
    		isBloodMoon,
    
    		getSeason,
    		getNextSchoolTermStartDate,
    		getNextSchoolTermEndDate,
    
    majou's avatar
    majou committed
    		monthNames,
    		daysOfWeek,
    
    xao's avatar
    xao committed
    		getNextWeekdayDate: weekDay => currentDate.getNextWeekdayDate(weekDay),
    		getPreviousWeekdayDate: weekDay => currentDate.getPreviousWeekdayDate(weekDay),
    		isWeekEnd: () => currentDate.weekEnd,
    	});
    })();
    window.Time = Time;
    
    
    xao's avatar
    xao committed
    $(document).on(":passageinit", () => {
    
    xao's avatar
    xao committed
    /* Local functions */
    
    function yearPassed() {
    
    	const fragment = document.createDocumentFragment();
    
    
    xao's avatar
    xao committed
    	V.scienceproject = "none";
    	V.mathsproject = "none";
    	V.englishPlay = "none";
    
    xao's avatar
    xao committed
    	V.yearly.clearProperties();
    
    	return fragment;
    
    xao's avatar
    xao committed
    }
    
    function weekPassed() {
    
    	const fragment = document.createDocumentFragment();
    
    
    xao's avatar
    xao committed
    	if (V.science_exam >= 200 && V.sciencetrait < 4) {
    		V.effectsmessage = 1;
    		V.science_up_message = 1;
    
    		fragment.append(wikifier("school_skill_up", "science"));
    
    xao's avatar
    xao committed
    	} else if (V.science_exam <= -100 && V.sciencetrait >= 0) {
    		V.effectsmessage = 1;
    		V.science_down_message = 1;
    
    		fragment.append(wikifier("school_skill_down", "science"));
    
    xao's avatar
    xao committed
    	}
    	if (V.maths_exam >= 200 && V.mathstrait < 4) {
    		V.effectsmessage = 1;
    		V.maths_up_message = 1;
    
    		fragment.append(wikifier("school_skill_up", "maths"));
    
    xao's avatar
    xao committed
    	} else if (V.maths_exam <= -100 && V.mathstrait >= 0) {
    		V.effectsmessage = 1;
    		V.maths_down_message = 1;
    
    		fragment.append(wikifier("school_skill_down", "maths"));
    
    xao's avatar
    xao committed
    	}
    	if (V.english_exam >= 200 && V.englishtrait < 4) {
    		V.effectsmessage = 1;
    		V.english_up_message = 1;
    
    		fragment.append(wikifier("school_skill_up", "english"));
    
    xao's avatar
    xao committed
    	} else if (V.english_exam <= -100 && V.englishtrait >= 0) {
    		V.effectsmessage = 1;
    		V.english_down_message = 1;
    
    		fragment.append(wikifier("school_skill_down", "english"));
    
    xao's avatar
    xao committed
    	}
    	if (V.history_exam >= 200 && V.historytrait < 4) {
    		V.effectsmessage = 1;
    		V.history_up_message = 1;
    
    		fragment.append(wikifier("school_skill_up", "history"));
    
    xao's avatar
    xao committed
    	} else if (V.history_exam <= -100 && V.historytrait >= 0) {
    		V.effectsmessage = 1;
    		V.history_down_message = 1;
    
    		fragment.append(wikifier("school_skill_down", "history"));
    
    xao's avatar
    xao committed
    	}
    	if (Time.schoolTerm) {
    		V.science_exam = Math.clamp(V.science_exam - 7, -107, 200);
    
    xao's avatar
    xao committed
    		V.maths_exam = Math.clamp(V.maths_exam - 7, -107, 200);
    		V.english_exam = Math.clamp(V.english_exam - 7, -107, 200);
    		V.history_exam = Math.clamp(V.history_exam - 7, -107, 200);
    
    		fragment.append(wikifier("exam_difficulty"));
    
    xao's avatar
    xao committed
    	}
    	if (V.robinpaid === 1) V.robinPayout = 0;
    	else {
    		V.robinmoney -= 400;
    		if (V.robinmoney <= 0 && V.robindebt >= 0) {
    			V.robinmoney = 0;
    			V.robindebt++;
    		}
    	}
    	if (V.robinpaid !== 1 && V.robindebt >= V.robindebtlimit && V.robindebtevent <= 0) {
    
    		fragment.append(wikifier("robinPunishment", "docks"));
    
    xao's avatar
    xao committed
    		V.robineventnote = 1;
    	}
    
    ShinyMilk's avatar
    ShinyMilk committed
    	V.robinmoney += (V.robin.stayup >= 1 ? 250 : 300) + V.robin.moneyModifier;
    
    Purity's avatar
    Purity committed
    	if (V.robinmoney > 4000) V.robinmoney = 4000;
    
    Vrelnir's avatar
    Vrelnir committed
    	V.compoundcentre = 0;
    
    xao's avatar
    xao committed
    	if (V.edenfreedom >= 1 && V.edenshopping === 2) V.edenshopping = 0;
    	if (V.loft_kylar) V.loft_spray = 0;
    	if (V.farm) {
    		if (V.farm.tower_guard) {
    			V.farm.tower_guard_unpaid++;
    			V.farm.tower_guard_patience = 0;
    		}
    	}
    	if (V.sydney) {
    		if (V.sydney.glasses === "broken" || V.sydney.glasses === "playerbroken") {
    			V.sydney.glasses = "glasses";
    			V.sydneyGlassesNotice = 1;
    		}
    	}
    	if (V.syndromewolves === 1) V.wolfcavepatrol = 1;
    	if (V.photo) {
    		if (V.photo.silly === "paid") V.photo.silly = 0;
    		V.photo.shoot = 0;
    	}
    
    	if (V.nightmareTimer && V.nightmareTimer > 0) {
    
    xao's avatar
    xao committed
    		V.nightmareTimer--;
    		if (V.nightmareTimer <= 0) delete V.nightmareTimer;
    	}
    
    Kirsty's avatar
    Kirsty committed
    	if (V.brothelVending && V.brothelVending.products >= 1) {
    
    		if (V.brothelVending.condoms === 0 && V.brothelVending.lube === 0) V.brothelVending.weeksEmpty += 1;
    
    Trinidad's avatar
    Trinidad committed
    		V.brothelVending.weeksRent++;
    
    Kirsty's avatar
    Kirsty committed
    		if (V.brothelVending.weeksEmpty >= 4) V.brothelVending.status = "sold";
    
    LollipopScythe's avatar
    LollipopScythe committed
    	supermarketWeekly();
    
    Vrelnir's avatar
    Vrelnir committed
    
    
    	statChange.worldCorruption("soft", V.world_corruption_hard);
    
    Purity's avatar
    Purity committed
    
    
    xao's avatar
    xao committed
    	V.weekly.clearProperties();
    
    xao's avatar
    xao committed
    }
    
    function dayPassed() {
    
    	const fragment = document.createDocumentFragment();
    
    
    xao's avatar
    xao committed
    	// Lose one day of tanning
    	Skin.applyTanningLoss(1440);
    
    
    	if (V.statFreeze) return fragment;
    
    	fragment.append(wikifier("seenPassageChecks"));
    	fragment.append(wikifier("prison_day"));
    	fragment.append(wikifier("clearNPC", "pharmNurse"));
    
    xao's avatar
    xao committed
    
    	V.physiquechange = 1;
    	V.home_event_timer--;
    	V.park_fame = Math.clamp(V.park_fame - 7, 0, 100);
    	V.museuminterest = Math.clamp(V.museuminterest - (V.museuminterest >= 60 ? 5 : 2), 0, 100);
    
    	if (V.gamemode !== "hard" && V.uncomfortable.lewd) {
    		V.exhibitionism = Math.max(V.exhibitionism - 1, 0);
    		V.promiscuity = Math.max(V.promiscuity - 1, 0);
    		V.deviancy = Math.max(V.deviancy - 1, 0);
    	}
    
    	if (V.locker_suspicion > 0) statChange.lockerSuspicion(-1);
    
    xao's avatar
    xao committed
    	if (V.whitneyromance || C.npc.Whitney.dom >= 20) {
    		V.bullytimer += 20;
    		V.bullytimeroutside += 10;
    	} else {
    		V.bullytimer += 10;
    		V.bullytimeroutside += 5;
    	}
    	if (Time.weekDay === 7) {
    
    xao's avatar
    xao committed
    		if (V.brothelshowdata.type !== "none" && !V.brothelshowdata.done && V.brothelshowdata.intro) {
    
    xao's avatar
    xao committed
    			V.brothelshowdata.missed = true;
    			V.brothelshowdata.type = "none";
    		}
    		V.brothelshowdata.done = false;
    	}
    
    	if (V.brothel_escortjob !== undefined && Time.date.timeStamp > V.brothel_escortjob.date) {
    
    		V.brothel_escortjob.missed = true;
    	}
    
    
    xao's avatar
    xao committed
    	if (Time.weekDay === 2) {
    		delete V.museumhorse;
    		delete V.museumduck;
    	}
    
    	if (V.medicated) V.medicated = Math.max(Math.trunc((V.medicated - 1) * 0.5), 0);
    	if (V.asylummedicated) V.asylummedicated = Math.max(Math.trunc((V.asylummedicated - 1) * 0.5), 0);
    
    xao's avatar
    xao committed
    	if (V.brothel_rivalry_timer !== undefined) V.brothel_rivalry_timer--;
    
    xao's avatar
    xao committed
    	if (V.orphanageWardIntro) V.home_event_ward_timer--;
    	if (V.location === "asylum") V.asylumbound--;
    
    	const rng = random(1, 100);
    	if (rng >= 95) V.brothel_basement_price = 3000;
    	else if (rng >= 85) V.brothel_basement_price = 2000;
    	else if (rng >= 45) V.brothel_basement_price = 1000;
    	else V.brothel_basement_price = 500;
    
    
    xao's avatar
    xao committed
    	if (V.chef_rework > 0) V.chef_rework--;
    	if (V.chef_sus > 0) V.chef_sus--;
    
    xao's avatar
    xao committed
    	if (V.stall_rejected >= 1) V.stall_rejected = Math.clamp(V.stall_rejected - 1, 0, 100);
    	if (V.temple_garden >= 1) V.temple_garden = Math.clamp(V.temple_garden - 10, 0, 100);
    	if (V.temple_quarters >= 1) V.temple_quarters = Math.clamp(V.temple_quarters - 10, 0, 100);
    	if (V.temple_chastity_timer > 0) V.temple_chastity_timer--;
    	if (V.temple_rank !== "prospective" && V.temple_rank !== "initiate") {
    
    		if (V.grace >= 1 && !V.daily.graceUp) statChange.grace(-2);
    
    xao's avatar
    xao committed
    	}
    	if (V.temple_evaluation) {
    		V.temple_evaluation--;
    		if (V.temple_evaluation <= 0) delete V.temple_evaluation;
    	}
    	if (V.wolfcavebreast >= 1) delete V.wolfcavebreast;
    	if (V.wolfcavepatrol === 1) V.wolfcavepatrolchance = random(1, 3);
    	if (V.temple_jordan_prayer === 1) delete V.temple_jordan_prayer;
    
    xao's avatar
    xao committed
    	if (V.temple_event !== undefined) V.temple_event = 1;
    
    xao's avatar
    xao committed
    	if (V.school_crossdress_message >= 1 || V.school_herm_message >= 1) V.effectsmessage = 1;
    	if (V.syndromewolves === 1) {
    
    		fragment.append(wikifier("wolf_cave_update"));
    
    xao's avatar
    xao committed
    		if (V.wolfchallengetimer === undefined) V.wolfchallengetimer = 14;
    		else V.wolfchallengetimer--;
    	}
    	if (V.estatePersistent) {
    
    		if (V.estatePersistent.suspicion && V.estatePersistent.suspicion >= 1) fragment.append(wikifier("blackjackSuspicion", -5 - C.npc.Wren.love / 5));
    
    xao's avatar
    xao committed
    		if (V.estatePersistent.newDeckTimer > 0 && V.estatePersistent.markedCards && V.estatePersistent.markedCards.size > 0) {
    			/*  we don't re-set this to 3 here - we only do that in the same
    				passage where we actually reset the deck.
    				we don't do that here because we acknowledge the timer and actually reset it
    				when the player enters the cottage, to not confuse things mid-game
    			*/
    			V.estatePersistent.newDeckTimer--;
    		}
    	}
    
    Kirsty's avatar
    Kirsty committed
    	if (V.balloonStand.robin.status === "closed") V.balloonStand.robin.status = "sabotaged";
    	if (V.robin.timer.customer >= 1) V.robin.timer.customer--;
    	if (V.robin.timer.hurt >= 1) V.robin.timer.hurt--;
    	if (V.robin.timer.hurt === 0) V.robin.hurtReason = "nothing";
    
    
    	V.robin.stayup = V.robin.stayup === 1 ? 2 : 0;
    
    
    	if (numberOfEarSlime()) {
    		// Daily Corruption
    
    		if (V.earSlime.growth < 50) statChange.corruption(-1);
    		statChange.corruption(numberOfEarSlime(), true);
    
    		earSlimeDaily();
    	}
    
    xao's avatar
    xao committed
    	if (V.bell_timer) V.bell_timer--;
    	if (V.lake_ice_broken >= 1) V.lake_ice_broken--;
    	if (V.lake_ice_broken < 1) delete V.lake_ice_broken;
    
    Purity's avatar
    Purity committed
    	if (V.community_service >= 1) {
    		if (V.community_service_done !== 1 && !["asylum", "prison"].includes(V.location)) {
    
    Purity's avatar
    Purity committed
    			fragment.append(wikifier("crimeUp", 200, "obstruction"));
    
    xao's avatar
    xao committed
    			V.effectsmessage = 1;
    			V.community_message = "missed";
    		}
    		delete V.community_service_done;
    	}
    
    	if (V.awareness >= 300) V.awarelevel = 2;
    	else if (V.awareness >= 200) V.awarelevel = 1;
    	else if (V.awareness <= -1) V.awarelevel = -1;
    	else V.awarelevel = 0;
    
    	if (V.awarelevel <= 1 && V.loveInterest.secondary !== "None") {
    		V.loveInterest_message = 1;
    		V.loveInterest.secondary = "None";
    		V.effectsmessage = 1;
    	} else if (V.awarelevel >= 2 && V.loveInterest.primary !== "None" && V.loveInterest.secondary === "None" && !V.loveInterestAwareMessage) {
    		V.loveInterest_message = 2;
    		V.effectsmessage = 1;
    	}
    	if (V.pound) {
    		V.pound.compete = 0;
    
    		fragment.append(wikifier("stray_happiness", -1));
    
    xao's avatar
    xao committed
    		V.pound.tasks = [];
    	}
    	V.renttime--;
    
    	// Not seen bailey for more than 2 weeks, tracks missed rent
    	if (V.renttime < 0 && V.renttime % 7 === 0) {
    		V.baileyRefusedToPayTotal += V.rentmoney + (V.babyRent || 0);
    		V.baileyRefusedToPayTotalStat += V.rentmoney + (V.babyRent || 0);
    	}
    
    xao's avatar
    xao committed
    
    	if (V.flashbacktown > 0) V.flashbacktown--;
    	if (V.flashbackhome > 0) V.flashbackhome--;
    	if (V.flashbackbeach > 0) V.flashbackbeach--;
    	if (V.flashbackunderground > 0) V.flashbackunderground--;
    	if (V.flashbackschool > 0) V.flashbackschool--;
    
    	if (V.flashbacktown === 1) V.flashbacktownready = 1;
    	if (V.flashbackhome === 1) V.flashbackhomeready = 1;
    	if (V.flashbackbeach === 1) V.flashbackbeachready = 1;
    	if (V.flashbackunderground === 1) V.flashbackundergroundready = 1;
    	if (V.flashbackschool === 1) V.flashbackschoolready = 1;
    
    
    Crimson Tide's avatar
    Crimson Tide committed
    	V.smuggler_timer--;
    	if (V.smuggler_timer < 0) {
    		V.smuggler_timer = random(4, 7);
    		const rng = random(1, 100);
    		if (rng >= 76) V.smuggle_location = "forest";
    		else if (rng >= 51) V.smuggle_location = "sewer";
    		else if (rng >= 26) V.smuggle_location = "beach";
    		else V.smuggle_location = "bus";
    		delete V.smuggler_known;
    
    xao's avatar
    xao committed
    	}
    
    	if (V.tailorMonthlyService > 0) V.tailorMonthlyService--;
    	else if (V.tailorMonthlyService === 0) delete V.tailorMonthlyService;
    
    	if (V.wardrobeRepair && V.wardrobeRepair.timeLeft === 1) V.wardrobeRepair.timeLeft = 0;
    	if (V.clothingShop.ban > 0) V.clothingShop.ban--;
    	else V.clothingShop.banExtension = false;
    
    
    xao's avatar
    xao committed
    	if (V.adultShop !== undefined) {
    
    xao's avatar
    xao committed
    		if (V.adultShop.ban > 0) V.adultShop.ban--;
    		else V.adultShop.banExtension = false;
    
    xao's avatar
    xao committed
    	} else {
    
    xao's avatar
    xao committed
    		V.adultShop = { ban: 0, banExtension: false, spotted: false, stolenClothes: 0, totalStolenClothes: 0, banCount: 0, rng: random(0, 1000) };
    	}
    
    	if (V.farm) {
    		if (V.farm.milking.catchChance > random(10, 1000) / 10) V.farm.milking.caught = true;
    
    Jimmys's avatar
    Jimmys committed
    		if (V.farm.milking.catchChance >= 25) V.farm.milking.catchChance = Math.clamp(V.farm.milking.catchChance * 0.95, 0, 100).toFixed(3);
    		else V.farm.milking.catchChance = Math.clamp(V.farm.milking.catchChance * 0.98, 0, 100).toFixed(3);
    
    xao's avatar
    xao committed
    	if (Weather.precipitation === "rain" && V.bird.upgrades?.firepit && !V.bird.upgrades.shelter) {
    
    		const burnTime = getBirdBurnTime() * 60; // seconds
    		if (burnTime > 0) {
    
    			Cooker.addBurnTime(V.bird.firepit, Math.floor(-burnTime / 2) + Time.minute * 30);
    
    xao's avatar
    xao committed
    	if (V.moorLuck > 0) V.moorLuck--;
    
    xao's avatar
    xao committed
    	if (V.officejobintro === 1) V.officelastcomplaintday++;
    
    xao's avatar
    xao committed
    	delete V.glideScared;
    	delete V.swimCrossdressPermission;
    
    xao's avatar
    xao committed
    	delete V.masturbation_oralSkillMax;
    
    	if (V.pubfame) {
    		if (V.pubfame.timer >= 1) V.pubfame.timer--;
    
    Purity's avatar
    Purity committed
    		if (V.pubfame.timer <= 0) {
    
    xao's avatar
    xao committed
    			if (V.pubfame.status === "hiding") V.pubfame.detail = "hiding";
    			if (V.pubfame.target) V.pubfame.status = "accepted";
    			else V.pubfame.status = "ready";
    			delete V.pubfame.timer;
    		}
    		for (const fameKeys of Object.keys(V.fameDecay)) {
    			if (V.fameDecayTimer[fameKeys] >= 1) {
    				V.fameDecayTimer[fameKeys]--;
    				V.fame[fameKeys] -= V.fameDecay[fameKeys];
    			} else if (V.fameDecayTimer[fameKeys] <= 0) {
    
    xao's avatar
    xao committed
    				delete V.fameDecayTimer[fameKeys];
    				delete V.fameDecay[fameKeys];
    
    xao's avatar
    xao committed
    				V.fame[fameKeys] = Math.round(V.fame[fameKeys]);
    			}
    		}
    	}
    
    	if (V.randomNNPCStraponsToClear) {
    		V.NPCName.forEach(npc => {
    			if (npc.strapons && npc.strapons.length >= 1) {
    				/* This removes all strapons that have the temp tag, and ignores any that lack this variable */
    				npc.strapons = npc.strapons.filter(strapon => !strapon.temp);
    
    				console.debug("Removed temp strap-ons");
    
    xao's avatar
    xao committed
    	if (V.adultshopprogress < 22 && Time.weekDay === 6) V.adultshopprogress++;
    
    	else if (V.adultshopgrandopening) fragment.append(wikifier("unlockAdultShop"));
    
    xao's avatar
    xao committed
    	else if (V.adultshopprogress >= 22 && !V.adultshopunlocked) V.adultshopgrandopening = true;
    	else if (V.adultshopdegree < 15) V.adultshopdegree += 0.1;
    	delete V.adultshophelped;
    
    
    Purity's avatar
    Purity committed
    	if (V.location !== "tentworld") {
    		delete V.tentacle_forest_lurker;
    	}
    
    
    	if (V.brothelVending) {
    		const rng = random(Math.min(1, V.brothelVending.condoms), Math.min(10, V.brothelVending.condoms));
    		V.brothelVending.condoms -= rng;
    
    majou's avatar
    majou committed
    		V.brothelVending.condomsSold += rng;
    
    Ybyx's avatar
    Ybyx committed
    		V.brothelVending.condomsToRefill = 200 - V.brothelVending.condoms;
    
    Trinidad's avatar
    Trinidad committed
    		V.brothelVending.total = (V.brothelVending.total || 0) + rng;
    
    	}
    
    	if (V.brothelVending) {
    		const rng = random(Math.min(1, V.brothelVending.lube), Math.min(10, V.brothelVending.lube));
    		V.brothelVending.lube -= rng;
    
    majou's avatar
    majou committed
    		V.brothelVending.lubeSold += rng;
    
    Ybyx's avatar
    Ybyx committed
    		V.brothelVending.lubeToRefill = 200 - V.brothelVending.lube;
    
    Trinidad's avatar
    Trinidad committed
    		V.brothelVending.total = (V.brothelVending.total || 0) + rng;
    
    	fragment.append(wikifier("menstruationCycle", "daily"));
    
    	pregnancyProgress();
    	pregnancyProgress("anus");
    
    	fragment.append(wikifier("rutCycle"));
    
    	npcPregnancyCycle();
    	randomPregnancyProgress();
    
    LollipopScythe's avatar
    LollipopScythe committed
    	fragment.append(wikifier("physicalAdjustments"));
    
    	fragment.append(dailyPlayerEffects());
    
    xao's avatar
    xao committed
    	dailyMasochismSadismEffects();
    
    	fragment.append(dailySchoolEffects());
    	fragment.append(dailyFarmEvents());
    
    xao's avatar
    xao committed
    	dailyLiquidEffects();
    
    	fragment.append(dailyTransformationEffects());
    	fragment.append(dailyNPCEffects());
    	fragment.append(yearlyEventChecks());
    
    xao's avatar
    xao committed
    
    	moonState();
    
    	parasiteProgressDay();
    	parasiteProgressDay("vagina");
    
    xao's avatar
    xao committed
    	tendingDay();
    
    	fragment.append(wikifier("creatureContainersProgressDay"));
    
    Jimmy's avatar
    Jimmy committed
    	if (Number.isInteger(V.challengetimer)) {
    		V.challengetimer--;
    		if (V.challengetimer < 0) delete V.challengetimer;
    	}
    
    	if (V.whitneyRescueStatus) {
    		V.whitneyRescueTimer = (V.whitneyRescueTimer || 8) - 1;
    		if (V.whitneyRescueTimer <= 0) {
    			if (V.whitneyRescueStatus === "humiliated") {
    				V.whitneyRescueStatus = "shaken";
    				V.whitneyRescueTimer = 14;
    			} else {
    				delete V.whitneyRescueTimer;
    				delete V.whitneyRescueStatus;
    			}
    
    Vrelnir's avatar
    Vrelnir committed
    	if (V.pirate_journey > 1) {
    
    LollipopScythe's avatar
    LollipopScythe committed
    		V.pirate_journey--;
    
    Vrelnir's avatar
    Vrelnir committed
    	} else {
    		delete V.pirate_journey;
    	}
    
    Vrelnir's avatar
    Vrelnir committed
    	if (V.pirate_attack) {
    		delete V.pirate_attack;
    	}
    
    Purity's avatar
    Purity committed
    	if (V.moorLessDangerAll > 1) {
    		V.moorLessDangerAll -= 1000;
    	} else {
    		delete V.moorLessDangerAll;
    	}
    
    Purity's avatar
    Purity committed
    	if (V.bird.clean >= 1) V.bird.clean = Math.clamp(V.bird.clean - (10 - V.bird.upgrades.shelter), 0, 100);
    
    Vrelnir's avatar
    Vrelnir committed
    
    
    Jimmys's avatar
    Jimmys committed
    	/* Set flag to determine Kylar's position at lunch */
    	V.daily.kylar.libraryStalk = rollKylarLibraryStalkFlag();
    
    
    Vrelnir's avatar
    Vrelnir committed
    	if (V.whitney_roof) {
    		delete V.whitney_roof;
    	}
    
    
    	// daysTillLaying only applies to unfertilised eggs
    	if (V.harpyEggs) V.harpyEggs.daysTillLaying--;
    	if (V.harpyEggsPrevent) {
    		V.harpyEggsPrevent--;
    		if (V.harpyEggsPrevent <= 0) delete V.harpyEggsPrevent;
    	}
    
    
    LollipopScythe's avatar
    LollipopScythe committed
    	// Activate the robin pillory
    	if (V.robinPillory && V.robinPillory.danger !== undefined && (V.robindebtevent <= 1 || !V.baileySold)) V.robinPillory.active = true;
    
    
    xao's avatar
    xao committed
    	V.daily.clearProperties();
    
    	return fragment;
    
    xao's avatar
    xao committed
    }
    
    function hourPassed(hours) {
    
    	const fragment = document.createDocumentFragment();
    
    	if (V.statFreeze) return fragment;
    
    xao's avatar
    xao committed
    
    	for (let i = 0; i < hours; i++) {
    
    		if (V.innocencestate === 1 && V.control <= 0) statChange.awareness(1);
    		statChange.control(1);
    
    		fragment.append(wikifier("orgasmHourlyRecovery"));
    
    		statChange.arousal(0, "time");
    
    		fragment.append(wikifier("wetnessCalculate"));
    		fragment.append(wikifier("bimboCheck", "upper"));
    		fragment.append(wikifier("bimboCheck", "lower"));
    		fragment.append(wikifier("bimboCheck", "feet"));
    
    xao's avatar
    xao committed
    
    		if (V.ejactrait >= 1) V.stress -= (V.goocount + V.semencount) * 10;
    		if (V.kylarwatched) V.kylarwatchedtimer--;
    
    		if (V.parasite.nipples.name) statChange.milkvolume(1);
    
    LollipopScythe's avatar
    LollipopScythe committed
    		if (V.worn.head.name === "hairpin" || V.sexStats.pills.pills["Hair Growth Formula"].doseTaken) {
    			let count = 0 + (V.worn.head.name === "hairpin" && random(0, 100) >= 75 ? 1 : 0);
    			count += V.sexStats.pills.pills["Hair Growth Formula"].doseTaken ? 1 : 0;
    			V.hairlength += count;
    			V.fringelength += count;
    
    			fragment.append(wikifier("calchairlengthstage"));
    
    LollipopScythe's avatar
    LollipopScythe committed
    		if (V.earSlime.defyCooldown) {
    			V.earSlime.defyCooldown--;
    
    			if (numberOfEarSlime() > 1 && V.earSlime.growth < 100) V.earSlime.defyCooldown--;
    
    LollipopScythe's avatar
    LollipopScythe committed
    			if (V.earSlime.defyCooldown <= 0) V.earSlime.defyCooldown = 0;
    
    		playerEndWaterProgress();
    
    xao's avatar
    xao committed
    	}
    
    	if (
    		V.sexStats.vagina.menstruation.running &&
    		(V.sexStats.vagina.menstruation.currentState === "pregnant" ||
    			(V.sexStats.vagina.menstruation.currentState === "normal" && (V.playerPregnancyHumanDisable === "f" || V.playerPregnancyBeastDisable === "f")))
    	) {
    		V.pregnancyDailyEvent = true;
    	}
    
    	V.openinghours = Time.hour >= 8 && Time.hour < 21 ? 1 : 0;
    
    LollipopScythe's avatar
    LollipopScythe committed
    	const feats = earnHourlyFeats();
    	if (feats) fragment.append(feats);
    
    xao's avatar
    xao committed
    
    	if (!V.wolfevent) V.wolfevent = 1;
    	if (V.wolfpatrolsent >= 24) delete V.wolfpatrolsent;
    	else if (V.wolfpatrolsent >= 1) V.wolfpatrolsent++;
    
    LollipopScythe's avatar
    LollipopScythe committed
    	if (V.robinPillory && V.robinPillory.danger !== undefined && V.robinPillory.active) fragment.append(wikifier("robinPilloryHour"));
    
    xao's avatar
    xao committed
    	if (V.pillory.tenant.exists && V.pillory.tenant.endTime < V.timeStamp) fragment.append(wikifier("clear_pillory"));
    
    xao's avatar
    xao committed
    	if (C.npc.Sydney.init === 1) {
    
    		fragment.append(wikifier("sydneySchedule"));
    
    xao's avatar
    xao committed
    		if (T.sydney_location === "temple" && V.temple_rank !== undefined && V.temple_rank !== "prospective") {
    			if (V.sydney_templeWork === "garden") {
    				if (V.temple_garden >= 1) V.temple_garden++;
    			} else if (V.sydney_templeWork === "quarters") {
    				if (V.temple_quarters >= 1) V.temple_quarters++;
    			}
    
    xao's avatar
    xao committed
    		}
    	}
    	if (V.per_npc.pubfame_receptionist) {
    
    		fragment.append(wikifier("clearNPC", "pubfame_receptionist"));
    
    xao's avatar
    xao committed
    		V.pubfame.hospital = {};
    
    		if (V.per_npc.pubfame_nurse) fragment.append(wikifier("clearNPC", "pubfame_nurse"));
    
    xao's avatar
    xao committed
    	}
    
    	V.home_gone++;
    
    xao's avatar
    xao committed
    }
    
    function minutePassed(minutes) {
    
    	const fragment = document.createDocumentFragment();
    
    xao's avatar
    xao committed
    
    
    xao's avatar
    xao committed
    	// Stress
    
    majou's avatar
    majou committed
    	// decay/rise and crossdresser trait
    	const isCrossdresser = V.backgroundTraits.includes("crossdresser");
    	const isCrossdressing = V.player.gender !== V.player.gender_appearance && V.player.gender !== "h";
    	if (V.controlled === 0 && V.anxiety >= 2) V.stress += minutes * ((isCrossdresser && !isCrossdressing) + 1);
    	else if (V.stress < V.stressmax && (V.controlled === 1 || V.anxiety === 0)) V.stress -= minutes * ((isCrossdresser && isCrossdressing) + 1);
    
    	parasiteProgressTime(minutes);
    	parasiteProgressTime(minutes, "vagina");
    
    Kirsty's avatar
    Kirsty committed
    	// eslint-disable-next-line no-undef
    	if (isPregnancyEnding()) {
    
    Kirsty's avatar
    Kirsty committed
    		// To prevent new events from occurring, allowing players to more easily go to the hospital or similar locations
    
    		V.eventskip = 1;
    		V.stress += Math.floor(minutes * 40);
    	}
    
    xao's avatar
    xao committed
    	// Tanning
    
    xao's avatar
    xao committed
    	Skin.applyTanningGain(minutes);
    
    xao's avatar
    xao committed
    	// Body temperature
    	const temperature = V.outside ? Weather.temperature : Weather.insideTemperature;
    
    	if (!V.possessed) Weather.BodyTemperature.update(temperature, minutes);
    
    xao's avatar
    xao committed
    	V.stress += Math.round(Weather.BodyTemperature.stressModifier * minutes);
    
    
    xao's avatar
    xao committed
    	Weather.setAccumulatedSnow(minutes);
    
    	Weather.setIceThickness(minutes);
    
    	Weather.sky.updateFade();
    	V.weatherObj.overcast = round(Weather.sky.fadables.overcast.factor, 2);
    
    xao's avatar
    xao committed
    	// Effects
    
    xao's avatar
    xao committed
    	V.stress = Math.min(V.stress, V.stressmax);
    
    	if (V.drunk > 0) statChange.alcohol(-minutes);
    
    LollipopScythe's avatar
    LollipopScythe committed
    	if (V.hallucinogen > 0) statChange.hallucinogen(-minutes);
    
    	if (V.drugged > 0) statChange.drugs(-minutes);
    
    xao's avatar
    xao committed
    	if (minutes < 1200) statChange.tiredness((minutes * (V.drunk > 0 ? 2 : 1)) / 15);
    
    	statChange.pain(minutes, -1);
    
    xao's avatar
    xao committed
    
    	// Arousal
    	const arousalMultiplier = V.backgroundTraits.includes("lustful") ? 0.2 * (12 - Math.floor(V.purity / 80)) + 1 + (V.purity <= 50 ? 1 : 0) : -10;
    
    	statChange.arousal(minutes * arousalMultiplier + getArousal(minutes));
    
    xao's avatar
    xao committed
    	V.timeSinceArousal = V.arousal < V.arousalmax / 4 ? V.timeSinceArousal + minutes : 1;
    
    xao's avatar
    xao committed
    	if (V.player.vaginaExist) fragment.append(passArousalWetness(minutes));
    
    xao's avatar
    xao committed
    	passWater(minutes);
    
    
    	if (V["ob" + "j" + "ec" + "tVe" + "rs" + "ion"]["t" + "e" + "st"] !== undefined || V["ch" + "ea" + "td" + "isa" + "" + "bl" + "e"] === "f") {
    		V["f" + "ea" + "" + "t" + "s"]["lo" + "ck" + "ed"] = true;
    		V["ob" + "jec" + "tVe" + "rs" + "ion"]["te" + "st"] = true;
    	}
    
    xao's avatar
    xao committed
    }
    
    function noonCheck() {
    
    	const fragment = document.createDocumentFragment();
    
    
    xao's avatar
    xao committed
    
    
    	if (V.statFreeze) return fragment;
    
    
    xao's avatar
    xao committed
    	delete V.bartend_info;
    	delete V.bartend_info_other;
    
    	if (V.per_npc.bartend) fragment.append(wikifier("clearNPC", "bartend"));