diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 026050feb4a09b2a24a74f134604bd62d1aafd70..421893ff646f10103e959719325631b7da4d90e4 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -55,6 +55,7 @@ module.exports = {
 		globals: "readonly",
 		T: "readonly",
 		V: "readonly",
+		statChange: "readonly",
 		// DoL main namespaces
 		DOL: "readonly",
 		defineGlobalNamespaces: "readonly",
@@ -67,70 +68,147 @@ module.exports = {
 		Constants: "readonly",
 		ConstantsLoader: "readonly",
 		currentlyLoadingMap: "readonly",
+		DateTime: "readonly",
 		DoLHouse: "readonly",
 		DolSettingsExport: "readonly",
 		Dynamic: "readonly",
+		EventSystem: "readonly",
+		JsonCompressor: "readonly",
+		JsonDecompressor: "readonly",
 		IronMan: "readonly",
 		Links: "readonly",
 		playerDoll: "readonly",
 		Renderer: "readonly",
 		SexTypes: "readonly",
 		StartConfig: "readonly",
+		Time: "readonly",
+		TimeConstants: "readonly",
 		Utils: "readonly",
 		ZIndices: "readonly",
-		Time: "readonly",
-		EventSystem: "readonly",
+		// DoL SC2 functions
+		compressionVerifier: "readonly",
+		DefineMacro: "readonly",
+		DefineMacroS: "readonly",
+		DoLCompressorDictionaries: "readonly",
+		DoLSave: "readonly",
 		// DoL functions
+		ampm: "readonly",
 		assignDefaults: "readonly",
-		between: "readonly",
+		calculatePenisBulge: "readonly",
+		canBeMPregnant: "readonly",
+		closeOverlay: "readonly",
 		clothesDataTrimmer: "readonly",
 		clothesIndex: "readonly",
 		clothingData: "readonly",
 		combatListColor: "readonly",
-		DefineMacro: "readonly",
-		DefineMacroS: "readonly",
-		DoLSave: "readonly",
+		currentSkillValue: "readonly",
 		eCheckbox: "readonly",
+		earnHourlyFeats: "readonly",
 		eInput: "readonly",
 		elechild: "readonly",
 		elechildren: "readonly",
 		element: "readonly",
+		endPlayerPregnancy: "readonly",
 		ensure: "readonly",
 		ensureIsArray: "readonly",
 		eSelect: "readonly",
+		getTimeString: "readonly",
 		generateBabyName: "readonly",
 		getCustomClothesColourCanvasFilter: "readonly",
 		getCustomColourRGB: "readonly",
 		getCustomColourStyle: "readonly",
+		getKylarLibraryState: "readonly",
+		getPregnancyObject: "readonly",
 		getRandomIntInclusive: "readonly",
 		getRobinLocation: "readonly",
 		getSexesFromRandomGroup: "readonly",
+		getSydneyLoveNorm: "readonly",
 		getTrueWarmth: "readonly",
+		getVisibleClothesList: "readonly",
+		hairLengthStringToNumber: "readonly",
 		inDOM: "readonly",
+		integrityKeyword: "readonly",
 		ironmanAutoSave: "readonly",
+		isBloodmoon: "readonly",
+		isKylarInPlayRole: "readonly",
+		isKylarRehearsing: "readonly",
+		isPlayerNonparasitePregnancyEnding: "readonly",
+		knowsAboutAnyPregnancy: "readonly",
+		listUniqueCarriedSextoys: "readonly",
 		loadCustomColourPreset: "readonly",
+		maleChance: "readonly",
+		masturbationActions: "readonly",
+		masturbationAudience: "readonly",
+		masturbationEffects: "readonly",
+		masturbationSlimeControl: "readonly",
+		maxParasites: "readonly",
+		npcCompressor: "readonly",
+		npcDecompressor: "readonly",
 		npcEquipSet: "readonly",
 		npcMakeNaked: "readonly",
+		npcPregnancyCycle: "readonly",
+		npcPregObject: "readonly",
+		numberOfEarSlime: "readonly",
+		ordinalSuffixOf: "readonly",
 		overlayShowHide: "readonly",
+		painToTearsLvl: "readonly",
+		parasiteProgressDay: "readonly",
+		parasiteProgressTime: "readonly",
+		parentFunction: "readonly",
 		parseCSSFilter: "readonly",
 		pickRandomItemInArray: "readonly",
+		playerAwareTheyArePregnant: "readonly",
+		playerBellySize: "readonly",
+		playerBellyVisible: "readonly",
+		playerChastity: "readonly",
+		playerEndWaterProgress: "readonly",
+		playerHasButtPlug: "readonly",
 		playerHasStrapon: "readonly",
+		playerHeatMinArousal: "readonly",
+		playerIsPregnant: "readonly",
+		playerNormalPregnancyTotal: "readonly",
+		playerPregnancyProgress: "readonly",
+		playerRutMinArousal: "readonly",
+		pregnancyGenerator: "readonly",
+		pregnancyProgress: "readonly",
+		pregPrep: "readonly",
+		randomPregnancyProgress: "readonly",
 		registerGeneratedPattern: "readonly",
 		registerImagePattern: "readonly",
 		returnTimeFormat: "readonly",
 		rgbToHsv: "readonly",
+		rollKylarLibraryStalkFlag: "readonly",
 		selfOr: "readonly",
+		setKnowsAboutPregnancy: "readonly",
+		setLowerVisibility: "readonly",
 		settingsConvert: "readonly",
 		settingsObjects: "readonly",
 		shopClothCustomColorWheel: "readonly",
 		sliderPerc: "readonly",
-		stringFrom: "readonly",
+		statusCheck: "readonly",
+		tanned: "readonly",
 		updateCustomColour: "readonly",
 		updateExportDay: "readonly",
 		updateMannequin: "readonly",
 		updateSavesCount: "readonly",
 		validateValue: "readonly",
-		tanned: "readonly",
+		waterproofCheck: "readonly",
+		// DoL math functions
+		round: "readonly",
+		normalise: "readonly",
+		randomExp: "readonly",
+		expCurve: "readonly",
+		between: "readonly",
+		nCr: "readonly",
+		formatDecimals: "readonly",
+		inverseLerp: "readonly",
+		lerp: "readonly",
+		interpolate: "readonly",
+		interpolateObject: "readonly",
+		// DoL object functions
+		formatList: "readonly",
+		stringFrom: "readonly",
+		toTitleCase: "readonly",
 		// DoL classes
 		ObservableValue: "readonly",
 		CanvasModel: "readonly",
@@ -153,6 +231,7 @@ module.exports = {
 		getClothingCost: "readonly",
 		isLoveInterest: "readonly",
 		skinColor: "readonly",
+		nullable: "readonly",
 	},
 
 	ignorePatterns: [
diff --git a/events.twee-config.yml b/events.twee-config.yml
index f518a933326227c68945936f5f6e07b82e08444a..f9b90872e35a1cc0574b519a2bdc443e5f030a33 100644
--- a/events.twee-config.yml
+++ b/events.twee-config.yml
@@ -207,6 +207,10 @@ sugarcube-2:
       name: sydneyEvent3HumblePeddlerofHonestWares
     sydneyEvent4Backrooms:
       name: sydneyEvent4Backrooms
+    whitneyShoppingCentre:
+      description: |-
+        Prints interrupt passage of Whitney taking pc to the shopping centre
+      tags: ["text", "links"]
     wolfwetnurse:
       name: wolfwetnurse
     wraithIntro:
diff --git a/game/00-framework-tools/10-time/datetime.js b/game/00-framework-tools/10-time/datetime.js
index ba44b02e6b4bf1a1731116a9873bd872a9f6a242..6d0ac458f215087ab0b56f9f5168d74e626b22b9 100644
--- a/game/00-framework-tools/10-time/datetime.js
+++ b/game/00-framework-tools/10-time/datetime.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 class DateTime {
 	constructor(year = 2020, month = 1, day = 1, hour = 0, minute = 0, second = 1) {
 		if (arguments.length === 1) {
diff --git a/game/00-framework-tools/perflog.js b/game/00-framework-tools/perflog.js
index 5d9d59bc393434aedcce291ba0ed21c0f74f21cb..770ce7513031236ecbc3673ee94a42a986b3223a 100644
--- a/game/00-framework-tools/perflog.js
+++ b/game/00-framework-tools/perflog.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 // Time in milliseconds, float with microsecond precision if available.
 const millitime =
 	typeof performance === "object" && typeof performance.now === "function"
diff --git a/game/03-JavaScript/03-Templates/t-pronouns.js b/game/03-JavaScript/03-Templates/t-pronouns.js
index c928e2d5cd6194b02f41e14afd890a81426fecff..ca7ff4b41c235d6337248c5ee64d3bcd2f2473a7 100644
--- a/game/03-JavaScript/03-Templates/t-pronouns.js
+++ b/game/03-JavaScript/03-Templates/t-pronouns.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 function getHe() {
 	switch (V.pronoun) {
 		case "m":
diff --git a/game/03-JavaScript/04-Pregnancy/children-story-functions.js b/game/03-JavaScript/04-Pregnancy/children-story-functions.js
index fb5aa15821874787325e965736670ddd918ee468..ea00f32ba8c856a45f36bf400616da4bcc8881ac 100644
--- a/game/03-JavaScript/04-Pregnancy/children-story-functions.js
+++ b/game/03-JavaScript/04-Pregnancy/children-story-functions.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 const setChildFirstWord = (childId, word, playerAbsent = false) => {
 	if (!childId && V.childSelected) childId = V.childSelected.childId;
 	if (!childId && !V.childSelected) return false;
diff --git a/game/03-JavaScript/04-Pregnancy/parasite.js b/game/03-JavaScript/04-Pregnancy/parasite.js
index 43bd2ef58a7b555529ad4ce072c0470faa46faff..ff5c3d278d8659076225e754354c28542918b29d 100644
--- a/game/03-JavaScript/04-Pregnancy/parasite.js
+++ b/game/03-JavaScript/04-Pregnancy/parasite.js
@@ -1,5 +1,3 @@
-/* eslint-disable no-undef */
-
 function fertiliseParasites(genital = "anus") {
 	// Runs whenever someone ejaculates in your `genital`
 	const pregnancy = V.sexStats[genital].pregnancy;
diff --git a/game/03-JavaScript/04-Pregnancy/parent-functions.js b/game/03-JavaScript/04-Pregnancy/parent-functions.js
index b43f9657bfe1a6246c6e93af13446ac9b439c3e7..0650d3ac13275907cc60f2bf22a062e9ca85d78e 100644
--- a/game/03-JavaScript/04-Pregnancy/parent-functions.js
+++ b/game/03-JavaScript/04-Pregnancy/parent-functions.js
@@ -1,5 +1,4 @@
 /* eslint-disable jsdoc/require-returns-type */
-/* eslint-disable no-undef */
 // Format for storing the parents of a child.
 const parentList = {
 	mothers: [{ name: "pc", compressed: "none", births: 0, kids: 0, id: 0 }],
diff --git a/game/03-JavaScript/04-Pregnancy/pregnancy-types.js b/game/03-JavaScript/04-Pregnancy/pregnancy-types.js
index 9abc518f7a0f422f655aa248e4780b596e6ef03a..b39900c64b16867316b15ff1dcec1510937309bb 100644
--- a/game/03-JavaScript/04-Pregnancy/pregnancy-types.js
+++ b/game/03-JavaScript/04-Pregnancy/pregnancy-types.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 function maxParasites(genital = "anus") {
 	switch (V.sexStats[genital].pregnancy.motherStatus) {
 		case 1:
diff --git a/game/03-JavaScript/04-Pregnancy/pregnancy.js b/game/03-JavaScript/04-Pregnancy/pregnancy.js
index 083290813cf31182ba6180e94f5af9548cf91319..d6b25cd1cd7d84caba3a89d3b8b5b6ef35aaf404 100644
--- a/game/03-JavaScript/04-Pregnancy/pregnancy.js
+++ b/game/03-JavaScript/04-Pregnancy/pregnancy.js
@@ -1,5 +1,3 @@
-/* eslint-disable no-undef */
-
 // Should a name type for species be setup, say, human/wolf specific names
 function generateBabyName(name, gender, childId) {
 	let result = "";
@@ -339,7 +337,7 @@ function playerEndWaterProgress() {
 	const pregnancy = getPregnancyObject();
 	if (!pregnancy || !pregnancy.type || pregnancy.type === "parasite" || pregnancy.timer < pregnancy.timerEnd || V.statFreeze) {
 		// Fixes an issue when the above "parasite" was "parasites"
-		if (pregnancy && (!pregnancy.type || pregnancy.type === "parasite") && pregnancy.waterBreaking) waterBreaking = false;
+		if (pregnancy && (!pregnancy.type || pregnancy.type === "parasite") && pregnancy.waterBreaking) pregnancy.waterBreaking = false;
 		return null;
 	}
 
@@ -883,7 +881,7 @@ function recordSperm({
 	if (V.disableImpregnation) return false; // To be set at the start of sex scenes, unset with <<endcombat>>
 	if (V.playerPregnancyHumanDisable === "t" && spermType === "human" && target === "pc") return false; // Human player pregnancy disabled
 	if (V.playerPregnancyBeastDisable === "t" && spermType !== "human" && target === "pc") return false; // Beast player pregnancy disabled
-	if (V.playerPregnancyEggLayingDisable === "t" && ["hawk", "harpy"].includes(npcType)) return false; // Egg laying player pregnancy disabled
+	if (V.playerPregnancyEggLayingDisable === "t" && ["hawk", "harpy"].includes(spermOwner?.type)) return false; // Egg laying player pregnancy disabled
 	if (V.npcPregnancyDisable === "t" && target !== "pc") return false; // Npc pregnancy disabled
 
 	if (["realistic", "fetish"].includes(V.pregnancytype) && !["anus", "vagina"].includes(genital)) return null;
diff --git a/game/03-JavaScript/04-Pregnancy/story-functions.js b/game/03-JavaScript/04-Pregnancy/story-functions.js
index a33cd4a03a690cf43fa9cc884a23d0e957bb882b..97b57a9646c6fb90e7b8f4a62b86c975fd286d0d 100644
--- a/game/03-JavaScript/04-Pregnancy/story-functions.js
+++ b/game/03-JavaScript/04-Pregnancy/story-functions.js
@@ -1,5 +1,3 @@
-/* eslint-disable no-undef */
-
 function getPregnancyObject(mother = "pc", returnGenital = false) {
 	let pregnancy = {};
 	let genital = "vagina";
diff --git a/game/03-JavaScript/base.js b/game/03-JavaScript/base.js
index 7c97203ec41cb96a73e6b655ebd486cc439f4c88..9ed69815a9d232d304b0451c466934789d1d46c2 100644
--- a/game/03-JavaScript/base.js
+++ b/game/03-JavaScript/base.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 /* eslint-disable jsdoc/require-description-complete-sentence */
 // adjust mousetrap behavior, see mousetrap.js
 Mousetrap.prototype.stopCallback = function (e, element, combo) {
diff --git a/game/03-JavaScript/debug-menu.js b/game/03-JavaScript/debug-menu.js
index b34c7b3bf0934b206c48ad4acdc16e68a8a9b0d9..ed80afa49b0dee2bd66665f504a47e17da5d1c50 100644
--- a/game/03-JavaScript/debug-menu.js
+++ b/game/03-JavaScript/debug-menu.js
@@ -1,6 +1,5 @@
 /* eslint-disable eqeqeq */
 /* eslint-disable no-eval */
-/* eslint-disable no-undef */
 /* A standard function to reference to avoid declaring an anonymous function repeatedly. */
 const stayOnPassageFn = function () {
 	return V.passage;
diff --git a/game/03-JavaScript/ingame.js b/game/03-JavaScript/ingame.js
index 29847bba25b8405ae27006113b771716433fda49..b18aee89e6353cf950e6651ab5d20a3a6d823794 100644
--- a/game/03-JavaScript/ingame.js
+++ b/game/03-JavaScript/ingame.js
@@ -1,5 +1,3 @@
-/* eslint-disable prettier/prettier */
-/* eslint-disable no-undef */
 function mapMove(moveTo) {
 	const currentPassage = V.passage;
 	const destinationTable = [];
@@ -45,7 +43,7 @@ window.toggleAllHairTraitsFilter = toggleAllHairTraitsFilter;
 function wikifier(widget, ...args) {
 	if (widget == null) return document.createDocumentFragment();
 	return Wikifier.wikifyEval("<<" + widget + (args.length ? " " + args.join(" ") : "") + ">>");
-};
+}
 window.wikifier = wikifier;
 
 function actionsreplace(bodypart) {
@@ -68,6 +66,7 @@ function actionsreplace(bodypart) {
 }
 window.actionsreplace = actionsreplace;
 
+// prettier-ignore
 const combatActionColours = {
 	Default: {
 		brat: [
@@ -336,7 +335,7 @@ function combatSkillCheck(skillname, targetid = 0, npc = "", basedifficulty = 10
 
 	if (arousalfactor * multiplier + skill + trust + rng >= basedifficulty + anger) {
 		return true;
-	} else if (["Alex","Robin"].includes(npc) || npc === "Sydney" && !V.loveDrunk || npc === "Great Hawk" && V.syndromebird || V.consensualGuaranteed) {
+	} else if (["Alex", "Robin"].includes(npc) || (npc === "Sydney" && !V.loveDrunk) || (npc === "Great Hawk" && V.syndromebird) || V.consensualGuaranteed) {
 		return true;
 	} else {
 		return false;
@@ -348,7 +347,7 @@ function hairdressersReset() {
 	$(() =>
 		$("#hairDressers").on("change", ".macro-listbox, .macro-radiobutton, .macro-checkbox", function (e) {
 			Wikifier.wikifyEval("<<replace #hairDressers>><<hairDressersOptions>><</replace>>");
-			Wikifier.wikifyEval("<<replace #currentCost>>To pay: £<<print _currentCost / 100>><</replace>><<numberify \"#passages > .passage\">>");
+			Wikifier.wikifyEval('<<replace #currentCost>>To pay: £<<print _currentCost / 100>><</replace>><<numberify "#passages > .passage">>');
 		})
 	);
 }
@@ -358,7 +357,7 @@ function hairdressersResetAlt() {
 	$(() =>
 		$("#hairDressersSydney").on("click", ".macro-cycle", function (e) {
 			Wikifier.wikifyEval("<<replace #hairDressersSydney>><<hairDressersOptionsSydney>><</replace>>");
-			Wikifier.wikifyEval("<<replace #currentCost>>To pay: £<<print _currentCost / 100>><</replace>><<numberify \"#passages > .passage\">>");
+			Wikifier.wikifyEval('<<replace #currentCost>>To pay: £<<print _currentCost / 100>><</replace>><<numberify "#passages > .passage">>');
 		})
 	);
 }
@@ -508,6 +507,7 @@ function calculateMarkedChance(deckCount, markedCount, depth, atLeast, doLog = f
 window.calculateMarkedChance = calculateMarkedChance;
 
 function shuffle(o) {
+	// prettier-ignore
 	for (
 		let j, x, i = o.length;
 		i;
@@ -567,7 +567,7 @@ function getRobinLocation() {
 		}
 	} else if (V.halloween === 1 && between(Time.hour, 16, 18) && Time.monthDay === 31) {
 		T.robin_location = "halloween";
-	} else if ((Time.isWeekEnd()) && between(Time.hour, 9, 16) && C.npc.Robin.trauma < 80) {
+	} else if (Time.isWeekEnd() && between(Time.hour, 9, 16) && C.npc.Robin.trauma < 80) {
 		T.robin_location = Time.season === "winter" ? "park" : "beach";
 	} else if (V.englishPlay === "ongoing" && V.englishPlayDays === 0 && Time.hour >= 17 && Time.hour < 21) {
 		T.robin_location = "englishPlay";
@@ -628,14 +628,16 @@ window.getRobinCrossdressingStatus = getRobinCrossdressingStatus;
 	Uses same checks as other Park NPC checks
  */
 function isInPark(name) {
-	switch(name.toLowerCase()) {
+	switch (name.toLowerCase()) {
 		case "kylar":
+			// prettier-ignore
 			return C.npc.Kylar.state === "active"
 				&& !["rain", "snow"].includes(V.weather)
 				&& Time.dayState === "day" && V.kylarwatched !== 1;
 		case "robin":
 			return getRobinLocation() === "park";
 		case "whitney":
+			// prettier-ignore
 			return ["active", "rescued"].includes(C.npc.Whitney.state)
 				&& C.npc.Whitney.init === 1 && ["snow", "rain"].includes(V.weather)
 				&& Time.dayState === "day" && !Time.schoolTime
@@ -833,7 +835,7 @@ window.DefaultActions = {
 };
 
 function selectWardrobe(targetLocation = V.wardrobe_location) {
-	return (!targetLocation || targetLocation === "wardrobe" || !V.wardrobes[targetLocation]) ? V.wardrobe : V.wardrobes[targetLocation];
+	return !targetLocation || targetLocation === "wardrobe" || !V.wardrobes[targetLocation] ? V.wardrobe : V.wardrobes[targetLocation];
 }
 window.selectWardrobe = selectWardrobe;
 
@@ -965,6 +967,7 @@ function clothesReturnLocation(item, type) {
 	if (!V.multipleWardrobes) return "wardrobe";
 	const isolated = ["asylum", "prison"];
 	let lastTaken = item.lastTaken;
+	// prettier-ignore
 	if (
 		!lastTaken ||
 		(V.multipleWardrobes !== "all" && !isolated.includes(lastTaken)) ||
@@ -1050,7 +1053,11 @@ function isConnectedToHood(slot) {
 	}
 	if (
 		V.worn[slot].hoodposition &&
-		(V.worn[slot].hoodposition === "down" || (V.worn[slot].hoodposition === "up" && V.worn[slot].outfitPrimary.head !== "broken" && V.worn[slot].outfitPrimary.head !== "split" && V.worn.head.hood === 1))
+		(V.worn[slot].hoodposition === "down" ||
+			(V.worn[slot].hoodposition === "up" &&
+				V.worn[slot].outfitPrimary.head !== "broken" &&
+				V.worn[slot].outfitPrimary.head !== "split" &&
+				V.worn.head.hood === 1))
 	) {
 		return true;
 	}
@@ -1077,7 +1084,11 @@ function clothesIndex(slot, itemToIndex) {
 		let matches = setup.clothes[slot].filter(item => item.variable === itemToIndex.variable);
 		if (matches.length === 0) {
 			/* try to find and item that had its variable changed */
-			matches = setup.clothes[slot].filter(item => Array.isArray(item.oldVariable) && item.oldVariable.find(oldVariableItem => oldVariableItem.name === itemToIndex.name && oldVariableItem.variable === itemToIndex.variable));
+			matches = setup.clothes[slot].filter(
+				item =>
+					Array.isArray(item.oldVariable) &&
+					item.oldVariable.find(oldVariableItem => oldVariableItem.name === itemToIndex.name && oldVariableItem.variable === itemToIndex.variable)
+			);
 			oldVariable = true;
 		}
 		if (matches.length === 1) {
@@ -1090,17 +1101,18 @@ function clothesIndex(slot, itemToIndex) {
 				itemToIndex.variable = recovery.variable;
 				itemToIndex.set = recovery.set;
 				itemToIndex.iconFile = recovery.iconFile;
-				if(recovery.outfitPrimary) {
+				if (recovery.outfitPrimary) {
 					Object.entries(recovery.outfitPrimary).forEach(([key, value]) => {
-						if(itemToIndex.outfitPrimary && (itemToIndex.outfitPrimary[key] === "broken" || itemToIndex.outfitPrimary[key] === "split")){
+						if (itemToIndex.outfitPrimary && (itemToIndex.outfitPrimary[key] === "broken" || itemToIndex.outfitPrimary[key] === "split")) {
 							// Do Nothing
 						} else {
 							itemToIndex.outfitPrimary[key] = value;
 						}
-					})
+					});
 					itemToIndex.outfitPrimary = recovery.outfitPrimary;
 				}
-				if(recovery.outfitSecondary && itemToIndex.outfitSecondary[1] !== "broken" && itemToIndex.outfitSecondary[1] !== "split") itemToIndex.outfitSecondary[1] = recovery.outfitSecondary[1];
+				if (recovery.outfitSecondary && itemToIndex.outfitSecondary[1] !== "broken" && itemToIndex.outfitSecondary[1] !== "split")
+					itemToIndex.outfitSecondary[1] = recovery.outfitSecondary[1];
 			}
 			console.log(`attempting to recover the mismatch, new index is '${recovery.index}'`);
 			return recovery.index;
@@ -1126,8 +1138,10 @@ function currentSkillValue(skill, disableModifiers = 0) {
 	// Prevents infinate loops, any call to `currentSkillValue` in this function should be written like 'currentSkillValue("skillName", disableModifiers + 1)'
 	if (disableModifiers >= 2) return result;
 	if (
+		// prettier-ignore
 		[
-			"skulduggery", "physique", "danceskill", "swimmingskill", "athletics", "willpower", "tending", "science", "maths", "english", "history", "housekeeping"].includes(skill) &&
+			"skulduggery", "physique", "danceskill", "swimmingskill", "athletics", "willpower", "tending", "science", "maths", "english", "history", "housekeeping"
+		].includes(skill) &&
 		V.moorLuck > 0
 	) {
 		result = Math.floor(result * (1 + V.moorLuck / 100));
@@ -1143,10 +1157,13 @@ function currentSkillValue(skill, disableModifiers = 0) {
 			case 2:
 				T.pregnancyModifier = 60;
 				break;
-			case 3: case 4: case 5:
+			case 3:
+			case 4:
+			case 5:
 				T.pregnancyModifier = 78;
 				break;
-			case 6: case 7:
+			case 6:
+			case 7:
 				T.pregnancyModifier = 96;
 				break;
 			default:
@@ -1244,38 +1261,38 @@ function currentSkillValue(skill, disableModifiers = 0) {
 		case "vaginalskill":
 			if (V.earSlime.growth > 100) {
 				if (V.earSlime.focus === "pregnancy") {
-					result = Math.floor(result * (1 + ((V.earSlime.growth - 100) / 500)));
+					result = Math.floor(result * (1 + (V.earSlime.growth - 100) / 500));
 				} else if (V.earSlime.focus === "impregnation") {
-					result = Math.floor(result * (1 - ((V.earSlime.growth - 100) / 400)));
+					result = Math.floor(result * (1 - (V.earSlime.growth - 100) / 400));
 				}
 			}
 			if (playerHeatMinArousal()) {
-				result = Math.floor(result * (1 + (Math.clamp(playerHeatMinArousal(), 0, 4000) / 20000)));
+				result = Math.floor(result * (1 + Math.clamp(playerHeatMinArousal(), 0, 4000) / 20000));
 			}
 			break;
 		case "penileskill":
 			if (V.earSlime.growth > 100) {
 				if (V.earSlime.focus === "impregnation") {
-					result = Math.floor(result * (1 + ((V.earSlime.growth - 100) / 500)));
+					result = Math.floor(result * (1 + (V.earSlime.growth - 100) / 500));
 				} else if (V.earSlime.focus === "pregnancy") {
-					result = Math.floor(result * (1 - ((V.earSlime.growth - 100) / 400)));
+					result = Math.floor(result * (1 - (V.earSlime.growth - 100) / 400));
 				}
 			}
 			if (playerRutMinArousal()) {
-				result = Math.floor(result * (1 + (Math.clamp(playerRutMinArousal(), 0, 4000) / 20000)));
+				result = Math.floor(result * (1 + Math.clamp(playerRutMinArousal(), 0, 4000) / 20000));
 			}
 			break;
 		case "analskill":
 			if (V.earSlime.growth > 100 && !V.player.vaginaExist && V.earSlime.focus === "pregnancy") {
-				result = Math.floor(result * (1 + ((V.earSlime.growth - 100) / 500)));
+				result = Math.floor(result * (1 + (V.earSlime.growth - 100) / 500));
 			}
 			if (playerHeatMinArousal() && canBeMPregnant()) {
-				result = Math.floor(result * (1 + (Math.clamp(playerHeatMinArousal(), 0, 4000) / 20000)));
+				result = Math.floor(result * (1 + Math.clamp(playerHeatMinArousal(), 0, 4000) / 20000));
 			}
 			break;
 		case "seductionskill":
 			if (V.earSlime.growth > 50 && !V.earSlime.defyCooldown) {
-				result = Math.floor(result * (1 + ((V.earSlime.growth - 50) / 600)));
+				result = Math.floor(result * (1 + (V.earSlime.growth - 50) / 600));
 			}
 			break;
 	}
@@ -1290,18 +1307,20 @@ window.playerIsPenetrated = playerIsPenetrated;
 
 /**
  * Overloads:
+ *
  * 	 (minutes)
  * 	getTimeString(hours, minutes)
  * Examples:
+ *
  * 	getTimeString(20) returns "0:20"
  * 	getTimeString(1,5) returns "1:05".
  *
  * @param {...any} args
  */
 function getTimeString(...args) {
-	if(args[0] == null) return;
+	if (args[0] == null) return;
 	const hours = args[1] != null ? args[0] : 0;
-	const minutes = Math.max(args[1] != null ? args[1] : args[0], 0) + (hours * 60);
+	const minutes = Math.max(args[1] != null ? args[1] : args[0], 0) + hours * 60;
 	return Math.clamp(Math.trunc(minutes / 60), 0, 23) + ":" + ("0" + Math.trunc(minutes % 60)).slice(-2);
 }
 window.getTimeString = getTimeString;
@@ -1411,7 +1430,8 @@ function getMoonState() {
 	let moonstate = 0;
 	T.todaysMoonState = getTodaysMoonState();
 
-	if (Time.nightState === T.todaysMoonState) { // if the current time of night matches the time a blood moon will happen, set moonstate
+	if (Time.nightState === T.todaysMoonState) {
+		// if the current time of night matches the time a blood moon will happen, set moonstate
 		moonstate = T.todaysMoonState;
 	}
 	// V.moonstate = moonstate; //commenting this out to make sure this function doesn't modify save variables
@@ -1466,6 +1486,7 @@ function checkTFparts() {
 }
 window.checkTFparts = checkTFparts;
 
+// prettier-ignore
 function getSexesFromRandomGroup() {
 	if (maleChance() <= 0) { /* Only females. */
 		if (V.dgchance <= 0) return SexTypes.ALL_FEMALES;		/* All females, no dickgirls. Always vaginal. */
@@ -1607,7 +1628,10 @@ function getHalloweenCostume() {
 		return "monk";
 	} else if (upper.name === "padded football shirt" && lower.name === "football shorts") {
 		return "football";
-	} else if ((upper.name === "belly dancer's top" && lower.name === "belly dancer's bottoms") || (upper.name === "harem vest" && lower.name === "harem pants"))  {
+	} else if (
+		(upper.name === "belly dancer's top" && lower.name === "belly dancer's bottoms") ||
+		(upper.name === "harem vest" && lower.name === "harem pants")
+	) {
 		return "belly dancer";
 	} else if (V.worn.head.name === "cowboy hat" && lower.name === "cowboy chaps" && V.worn.feet.name === "cowboy boots") {
 		return "cowboy";
@@ -1624,7 +1648,7 @@ function getHalloweenCostume() {
 	} else if (upper.name === "futuristic bodysuit" && lower.name === "futuristic bodysuit pants") {
 		return "futuresuit";
 	} else if (upper.name.includes("nurse") && lower.name.includes("nurse")) {
-	 	return "nurse";
+		return "nurse";
 	} else if (face.name === "eyepatch") {
 		return "eyepatch";
 	} else if (face.name === "medical eyepatch") {
@@ -1634,7 +1658,7 @@ function getHalloweenCostume() {
 	} else if (upper.name === "rag top" && lower.name === "rag skirt") {
 		return "rags";
 
-	/* Transformations */
+		/* Transformations */
 	} else if (T.tf.angelHalo && T.tf.angelWings) {
 		return "angel TF";
 	} else if (T.tf.wolfEars && T.tf.wolfTail) {
@@ -1652,7 +1676,7 @@ function getHalloweenCostume() {
 	} else if (T.tf.foxEars && T.tf.foxTail) {
 		return "fox TF";
 
-	/* Misc outcomes */
+		/* Misc outcomes */
 	} else if (
 		V.worn.upper.type.includes("costume") ||
 		V.worn.lower.type.includes("costume") ||
@@ -1870,69 +1894,58 @@ function dailyConvert() {
 }
 window.dailyConvert = dailyConvert;
 
-function convertHairLengthToStage(hair, length){
-	if (!hair || !length)
-		throw new Error(`Hair AND Length must be provided to be converted: ${hair} / ${length}`);
+function convertHairLengthToStage(hair, length) {
+	if (!hair || !length) throw new Error(`Hair AND Length must be provided to be converted: ${hair} / ${length}`);
 	if (hair === "fringe") {
-		if (length >= 900)
-			return "feet";
-		else if (length >= 700)
-			return "thighs";
-		else if (length >= 600)
-			return "navel";
-		else if (length >= 400)
-			return "chest";
-		else if (length >= 200)
-			return "shoulder";
-		else
-			return "short";
-	}
-	else if (hair === "sides") {
-		if (length >= 900)
-			return "feet";
-		else if (length >= 700)
-			return "thighs";
-		else if (length >= 600)
-			return "navel";
-		else if (length >= 400)
-			return "chest";
-		else if (length >= 200)
-			return "shoulder";
-		else
-			return "short";
+		if (length >= 900) return "feet";
+		else if (length >= 700) return "thighs";
+		else if (length >= 600) return "navel";
+		else if (length >= 400) return "chest";
+		else if (length >= 200) return "shoulder";
+		else return "short";
+	} else if (hair === "sides") {
+		if (length >= 900) return "feet";
+		else if (length >= 700) return "thighs";
+		else if (length >= 600) return "navel";
+		else if (length >= 400) return "chest";
+		else if (length >= 200) return "shoulder";
+		else return "short";
 	}
 }
 
 window.convertHairLengthToStage = convertHairLengthToStage;
 
-function calculateSemenReleased(){
-	if(T.deniedOrgasm) return 0;
+function calculateSemenReleased() {
+	if (T.deniedOrgasm) return 0;
 	let released = 30;
 
-	released += (V.semen_volume / 30);
+	released += V.semen_volume / 30;
 
-	if(V.femaleclimax === 1) released /= 30;
-	if(V.orgasmtrait >= 1) released *= 2.5;
-	if(V.cow >= 6) released *= 2;
+	if (V.femaleclimax === 1) released /= 30;
+	if (V.orgasmtrait >= 1) released *= 2.5;
+	if (V.cow >= 6) released *= 2;
 
 	/* if the player doesn't have enough semen, set $_semen_released to whatever they have left */
-	if(V.semen_amount < released) released = V.semen_amount;
-	if(parseFloat(released.toFixed(1)) === 0 && V.semen_amount < 0.1) V.semen_amount = 0; // Prevents really low floating numbers
+	if (V.semen_amount < released) released = V.semen_amount;
+	if (parseFloat(released.toFixed(1)) === 0 && V.semen_amount < 0.1) V.semen_amount = 0; // Prevents really low floating numbers
 
 	return parseFloat(released.toFixed(1));
 }
 window.calculateSemenReleased = calculateSemenReleased;
 
-function npcSemenMod(penisSize){
-	switch(penisSize) {
-		case 4: return "large";
-		case 1: return "tiny";
-		default: return "";
+function npcSemenMod(penisSize) {
+	switch (penisSize) {
+		case 4:
+			return "large";
+		case 1:
+			return "tiny";
+		default:
+			return "";
 	}
 }
 window.npcSemenMod = npcSemenMod;
 
-function maleChance(override){
+function maleChance(override) {
 	if (V.maleChanceSplit === "f") return V.malechance;
 	const appearence = override || V.player.gender_appearance;
 	if (appearence === "m") return V.maleChanceMale;
@@ -1942,13 +1955,13 @@ function maleChance(override){
 window.maleChance = maleChance;
 
 // gender of the npc, rng (between 1 and 100) of their generation
-function attractedToBothChance(gender, rng){
+function attractedToBothChance(gender, rng) {
 	if (gender === "m") return maleChance("m") >= rng && maleChance("f") >= rng;
 	return maleChance("m") < rng && maleChance("f") < rng;
 }
 window.attractedToBothChance = attractedToBothChance;
 
-function beastMaleChance(override){
+function beastMaleChance(override) {
 	if (V.beastMaleChanceSplit === "f") return V.beastmalechance;
 	const appearence = override || V.player.gender_appearance;
 	if (appearence === "m") return V.beastMaleChanceMale;
@@ -1979,7 +1992,7 @@ window.crimeSumCountHistory = (...args) => crimeSum("countHistory", ...args);
  */
 function onBrowserTabClose(event) {
 	event.preventDefault();
-	event.returnValue = 'Are you sure you want to leave?'; // the string here isn't important, it's mostly not considered by the browser.
+	event.returnValue = "Are you sure you want to leave?"; // the string here isn't important, it's mostly not considered by the browser.
 }
 
 /**
@@ -1987,18 +2000,17 @@ function onBrowserTabClose(event) {
  *
  * @returns {void}
  */
-function toggleConfirmDialogUponTabClose(){
+function toggleConfirmDialogUponTabClose() {
 	if (V.options.confirmDialogUponTabClose === true) {
-		window.addEventListener('beforeunload', onBrowserTabClose);
-	}
-	else if (V.options.confirmDialogUponTabClose === false) {
-		window.removeEventListener('beforeunload', onBrowserTabClose);
+		window.addEventListener("beforeunload", onBrowserTabClose);
+	} else if (V.options.confirmDialogUponTabClose === false) {
+		window.removeEventListener("beforeunload", onBrowserTabClose);
 	}
 }
 
 window.toggleConfirmDialogUponTabClose = toggleConfirmDialogUponTabClose;
 
-function numberOfEarSlime(){
+function numberOfEarSlime() {
 	let result = 0;
 	if (V.parasite.left_ear.name === "slime") result++;
 	if (V.parasite.right_ear.name === "slime") result++;
@@ -2006,27 +2018,27 @@ function numberOfEarSlime(){
 }
 window.numberOfEarSlime = numberOfEarSlime;
 
-function earSlimeMakingMundaneRequests(){
+function earSlimeMakingMundaneRequests() {
 	if (!numberOfEarSlime()) return false;
 	// First rape requests
-	if (V.earSlime.growth + (V.earSlime.promiscuity * 10) >= 80) return false;
+	if (V.earSlime.growth + V.earSlime.promiscuity * 10 >= 80) return false;
 	return true;
 }
 window.earSlimeMakingMundaneRequests = earSlimeMakingMundaneRequests;
 
-function minArousal(){
+function minArousal() {
 	let result = playerHeatMinArousal() + playerRutMinArousal();
 
 	result += Object.values(V.worn).reduce((prev, curr) => {
 		if (curr.type.includes("fetish")) return prev + 150;
 		return prev;
-	}, 0)
+	}, 0);
 
 	return Math.clamp(result, 0, 5000);
 }
 window.minArousal = minArousal;
 
-function minPain(){
+function minPain() {
 	let result = 0;
 
 	if (V.lactating && V.breastfeedingdisable === "f" && V.milkFullPain > 200) {
@@ -2037,10 +2049,13 @@ function minPain(){
 		}
 	}
 
-	if (V.earSlime.defyCooldown && (V.worn.genitals.name === "chastity parasite" || V.parasite.penis.name === "parasite" || V.parasite.clit.name === "parasite")) {
-		result += 25
+	if (
+		V.earSlime.defyCooldown &&
+		(V.worn.genitals.name === "chastity parasite" || V.parasite.penis.name === "parasite" || V.parasite.clit.name === "parasite")
+	) {
+		result += 25;
 	}
 
-	return Math.clamp(result,0,50);
+	return Math.clamp(result, 0, 50);
 }
 window.minPain = minPain;
diff --git a/game/03-JavaScript/ironman.js b/game/03-JavaScript/ironman.js
index ea001d3fd9b03c3285414ca525a2070917a3b497..6460cae3f40134800bb74701a026ff36dd82f0c5 100644
--- a/game/03-JavaScript/ironman.js
+++ b/game/03-JavaScript/ironman.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 /* eslint-disable no-var */
 var IronMan = (Save => {
 	"use strict";
diff --git a/game/03-JavaScript/save.js b/game/03-JavaScript/save.js
index 132f54d3e7c5778ab95b64a438a0838219f834aa..1ada96c3638f2045b27dd02ff61c2a7596b86f9e 100644
--- a/game/03-JavaScript/save.js
+++ b/game/03-JavaScript/save.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 const DoLSave = ((Story, Save) => {
 	"use strict";
 
diff --git a/game/03-JavaScript/sextoys.js b/game/03-JavaScript/sextoys.js
index 910849120bd9e0c1af6c84320854b5cc1150030e..bcc5a17ab6a0733b3ca289ead2a3817826ee2625 100644
--- a/game/03-JavaScript/sextoys.js
+++ b/game/03-JavaScript/sextoys.js
@@ -82,7 +82,7 @@ function existsInSexToyInventory(name, colour) {
 		if (!colour || colour === "any") {
 			return true;
 		}
-		for (let item of V.player.inventory.sextoys[name]) {
+		for (const item of V.player.inventory.sextoys[name]) {
 			if (item.colour === colour) {
 				return true;
 			}
diff --git a/game/03-JavaScript/time-macros.js b/game/03-JavaScript/time-macros.js
index 2ea05be1f22a6fb655207e4b3a6d87a7320a178b..d567601793c089bda6b7c2c1cf4771955c56db3a 100644
--- a/game/03-JavaScript/time-macros.js
+++ b/game/03-JavaScript/time-macros.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 function timeAfterXHours(hours) {
 	const date = new DateTime(Time.date);
 	date.addSeconds(hours * TimeConstants.secondsPerHour);
diff --git a/game/03-JavaScript/time.js b/game/03-JavaScript/time.js
index bcc9fd248a2d47f3b24877a1041cd5f936531c54..0359c5edda66b5e848f8a39a8af279d3eaa98ead 100644
--- a/game/03-JavaScript/time.js
+++ b/game/03-JavaScript/time.js
@@ -51,7 +51,6 @@
 */
 
 /* eslint-disable jsdoc/require-description-complete-sentence */
-/* eslint-disable no-undef */
 const Time = (() => {
 	const moonPhases = {
 		new: {
diff --git a/game/04-Variables/canvasmodel-main.js b/game/04-Variables/canvasmodel-main.js
index 243cbb84177cb0a9aeab8873c989e3c711ab319b..0afe6713a39e3afa2461c23bd9c933d92a710224 100644
--- a/game/04-Variables/canvasmodel-main.js
+++ b/game/04-Variables/canvasmodel-main.js
@@ -6,7 +6,6 @@
 /* eslint-disable camelcase */
 /* eslint-disable spaced-comment */
 /* eslint-disable no-useless-return */
-/* eslint-disable no-undef */
 /* eslint-disable prefer-const */
 /* eslint-disable prettier/prettier */
 /* eslint-disable dot-notation */
@@ -3370,7 +3369,7 @@ Renderer.CanvasModels["main"] = {
 				let path = 'img/clothes/face/' +
 				options.worn_face_setup.variable + '/' +
 				'acc' +
-				(setup.accessory_integrity_img ? '_' + options["worn_" + slot + "_integrity"] : '') +
+				(setup.accessory_integrity_img ? '_' + options.worn_face_integrity : '') +
 				(isAltPosition ? '_alt' : '') + '.png';
 				return gray_suffix(path, options.filters['worn_face_acc']);
 			},
@@ -3417,7 +3416,7 @@ Renderer.CanvasModels["main"] = {
 				let path = 'img/clothes/neck/' +
 				options.worn_neck_setup.variable + '/' +
 				'acc' +
-				(setup.accessory_integrity_img ? '_' + options["worn_" + slot + "_integrity"] : '') +
+				(setup.accessory_integrity_img ? '_' + options.worn_neck_integrity : '') +
 				(isAltPosition ? '_alt' : '') + '.png';
 				return gray_suffix(path, options.filters['worn_neck_acc']);
 			},
diff --git a/game/04-Variables/feats.js b/game/04-Variables/feats.js
index bfed4085fc78a8779054487236314c4ecde0e92f..1d68cb170a679cab39e2a4acadbfe6dd9fe30ee6 100644
--- a/game/04-Variables/feats.js
+++ b/game/04-Variables/feats.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 /*
 	Key points
 	series: "seriesName", //Will only show the first locked feat in a series to the player
diff --git a/game/base-system/effect.js b/game/base-system/effect.js
index e1495ba8e1b082b9d5dff4ac38ebfae77e90ce32..6fd2b34dbd50f073f2211410b3df5d0886e667e4 100644
--- a/game/base-system/effect.js
+++ b/game/base-system/effect.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 function effectsWater() {
 	DOL.Perflog.logWidgetStart("effectsWaterJs");
 	const fragment = document.createDocumentFragment();
@@ -654,7 +653,7 @@ function effects() {
 						V.wardrobes[location]
 							? ` to the ${V.wardrobes[location].name}`
 							: `. (Likely One-off update error, no need to report unless seen multiple times in the same save) ${
-									Array.isArray($rebuy_success) ? JSON.stringify($rebuy_success) : ""
+									Array.isArray(V.rebuy_success) ? JSON.stringify(V.rebuy_success) : ""
 							  }`
 					}.
 				`,
diff --git a/game/base-system/stat-changes.js b/game/base-system/stat-changes.js
index 7e2f2124d5f8bc40fc761b21177cc959aa8b2e57..14ec80ef3b0f4b4cb3d037559a75d827fb9b51dd 100644
--- a/game/base-system/stat-changes.js
+++ b/game/base-system/stat-changes.js
@@ -1,5 +1,4 @@
 /* eslint-disable no-useless-escape */
-/* eslint-disable no-undef */
 
 // eslint-disable-next-line no-var, no-unused-vars
 var statChange = (() => {
diff --git a/game/base-system/text.js b/game/base-system/text.js
index 8be053a7869d66495207781bdfe44156458c6ab1..cfb1dea2cd23c796665414653f79eec17142b03e 100644
--- a/game/base-system/text.js
+++ b/game/base-system/text.js
@@ -1,5 +1,3 @@
-/* eslint-disable no-undef */
-
 function displayMonthday() {
 	return ordinalSuffixOf(Time.monthDay);
 }
diff --git a/game/base-system/widgets.js b/game/base-system/widgets.js
index dcf478c3653be67289f1e985c56f5e9b214385a1..799fa5284dac8f0922ced6b4da3ddbf5ed555bb9 100644
--- a/game/base-system/widgets.js
+++ b/game/base-system/widgets.js
@@ -1,6 +1,6 @@
 /* eslint-disable no-new */
 /* eslint-disable jsdoc/require-description-complete-sentence */
-/* eslint-disable no-undef */
+/* global exportable */ // Not really: just a bug in unused code
 function setfemininitymultiplierfromgender(gender) {
 	if (gender === "f") {
 		T.femininity_multiplier = 1;
@@ -74,7 +74,7 @@ const hairStyleCap = {
 	},
 };
 
-window.calculatePenisBulge = () => {
+function calculatePenisBulge() {
 	if (V.worn.under_lower.type.includes("strap-on")) return (V.worn.under_lower.size || 0) * 3;
 	const compressed = V.player.penisExist && V.worn.genitals.type.includes("hidden");
 	if (!V.player.penisExist || compressed) return 0;
@@ -92,7 +92,8 @@ window.calculatePenisBulge = () => {
 		erectionState = 2;
 	}
 	return Math.clamp((V.player.penissize + 1) * erectionState, 0, Infinity);
-};
+}
+window.calculatePenisBulge = calculatePenisBulge;
 
 /** Calculate the player's gender appearance */
 function genderappearancecheck() {
diff --git a/game/overworld-town/loc-school/library-functions.js b/game/overworld-town/loc-school/library-functions.js
index c933fd3a4bb4ad024e78925ddccd4fa763fdc248..988e540b9cd1ca0ff26b75e98178a5975d88f96c 100644
--- a/game/overworld-town/loc-school/library-functions.js
+++ b/game/overworld-town/loc-school/library-functions.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 /**
  * Determine Kylar's location,
  * when Kylar has a centralised scheduling system, we could avoid logic specific to Kylar in the library-functions file.
diff --git a/game/overworld-town/special-kylar/kylar-functions.js b/game/overworld-town/special-kylar/kylar-functions.js
index f7baf562dbfec4237f2bc8c5d76a33ef7bcfa12e..cf90ce3b557dc2fe31434ba5e759b12a4e320f34 100644
--- a/game/overworld-town/special-kylar/kylar-functions.js
+++ b/game/overworld-town/special-kylar/kylar-functions.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 /**
  * @typedef {object} DolLocation
  * @property {string} area The place they are at.
diff --git a/game/special-masturbation/actions.js b/game/special-masturbation/actions.js
index 67fb3c67e345ee4761d3f4ab99fbaeadfa924b79..f90414b77545f5540e25727d505bbafa9acb64ed 100644
--- a/game/special-masturbation/actions.js
+++ b/game/special-masturbation/actions.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 function masturbationActions() {
 	const fragment = document.createDocumentFragment();
 
diff --git a/game/special-masturbation/audience.js b/game/special-masturbation/audience.js
index ab8ac19c4e7d8a98413e95647538623e1479fdb8..8a7f870d601df38458d9550433721aa367a412a1 100644
--- a/game/special-masturbation/audience.js
+++ b/game/special-masturbation/audience.js
@@ -1,5 +1,3 @@
-/* eslint-disable no-undef */
-
 // eslint-disable-next-line no-unused-vars
 function masturbationAudience() {
 	const br = () => document.createElement("br");
diff --git a/game/special-masturbation/effects.js b/game/special-masturbation/effects.js
index 2c2fc68dd70d47e517c9591c6cb2f973fd73d14b..827b5bf0adae9067b96df97771708ba8bc4869a4 100644
--- a/game/special-masturbation/effects.js
+++ b/game/special-masturbation/effects.js
@@ -1,7 +1,5 @@
-/* eslint-disable no-undef */
-
 // eslint-disable-next-line no-unused-vars
-function masturbationeffects() {
+function masturbationEffects() {
 	const fragment = document.createDocumentFragment();
 	const br = () => document.createElement("br");
 	const span = (text, colour) => {
@@ -133,12 +131,12 @@ function masturbationeffects() {
 	// Reset the record of the players current actions
 	V.masturbationActions = {};
 
-	fragment.append(masturbationeffectsVaginaAnus(otherVariables));
+	fragment.append(masturbationEffectsVaginaAnus(otherVariables));
 
-	fragment.append(masturbationeffectsArms("left", V.leftaction === V.rightaction, otherVariables));
-	fragment.append(masturbationeffectsArms("right", false, otherVariables));
+	fragment.append(masturbationEffectsArms("left", V.leftaction === V.rightaction, otherVariables));
+	fragment.append(masturbationEffectsArms("right", false, otherVariables));
 
-	fragment.append(masturbationeffectsMouth(otherVariables));
+	fragment.append(masturbationEffectsMouth(otherVariables));
 
 	if (otherVariables.additionalEffect.hands === "ballplayeffects" && V.worn.genitals.name !== "chastity parasite") {
 		if (V.arousal >= V.arousalmax * (4 / 5) || (V.earSlime.focus === "impregnation" && V.earSlime.growth >= 100)) {
@@ -264,7 +262,7 @@ function masturbationeffects() {
 	return fragment;
 }
 
-function masturbationeffectsArms(
+function masturbationEffectsArms(
 	arm,
 	doubleAction,
 	{ span, otherElement, additionalEffect, selectedToy, toyDisplay, genitalsExposed, breastsExposed, hymenIntact, earSlimeDefy }
@@ -2679,7 +2677,7 @@ function possessedMasturbation(span, br) {
 	return fragment;
 }
 
-function masturbationeffectsMouth({
+function masturbationEffectsMouth({
 	span,
 	otherElement,
 	additionalEffect,
@@ -3222,7 +3220,7 @@ function deepthroateffects(span) {
 	return fragment;
 }
 
-function masturbationeffectsVaginaAnus({ span, otherElement, additionalEffect, selectedToy, toyDisplay, genitalsExposed, breastsExposed, hymenIntact }) {
+function masturbationEffectsVaginaAnus({ span, otherElement, additionalEffect, selectedToy, toyDisplay, genitalsExposed, breastsExposed, hymenIntact }) {
 	const fragment = document.createDocumentFragment();
 
 	const sWikifier = text => {
@@ -3501,7 +3499,7 @@ function masturbationeffectsVaginaAnus({ span, otherElement, additionalEffect, s
 
 Macro.add("masturbationeffects", {
 	handler() {
-		const fragment = masturbationeffects();
+		const fragment = masturbationEffects();
 		this.output.append(fragment);
 	},
 });
diff --git a/game/special-masturbation/macros-masturbation.js b/game/special-masturbation/macros-masturbation.js
index 486f6fed55b3bfc6f19b4250249278ca49c4282d..9f94db3e7b300201929299a6c92d9b8086c10de9 100644
--- a/game/special-masturbation/macros-masturbation.js
+++ b/game/special-masturbation/macros-masturbation.js
@@ -1,4 +1,3 @@
-/* eslint-disable no-undef */
 function getToyName(index, capitalise = false) {
 	const toy = T.playerToys[index];
 	if (toy == null) {
@@ -22,7 +21,7 @@ function skipToOrgasm(modifiers = "") {
 	do {
 		count++;
 		if (T.corruptionMasturbation) masturbationSlimeControl();
-		masturbationeffects();
+		masturbationEffects();
 		masturbationActions();
 		if (modifiers.includes("timer")) V.timer -= 1;
 
diff --git a/game/special-masturbation/slime-control.js b/game/special-masturbation/slime-control.js
index 8fd46cf6cbf6cdc509cf6445e206f8c3d3e040c0..de733d3b7b9cfd0376b0b082b91a20314d5d10b6 100644
--- a/game/special-masturbation/slime-control.js
+++ b/game/special-masturbation/slime-control.js
@@ -1,5 +1,3 @@
-/* eslint-disable no-undef */
-
 // eslint-disable-next-line no-unused-vars
 function masturbationSlimeControl() {
 	const redText = text => {
diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..54f0cba5dde20965dae99cf629c467750a465a85
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,12 @@
+{
+	"compilerOptions": {
+		"module": "ESNext",
+		"moduleResolution": "Node",
+		"target": "ES2020",
+		"jsx": "react",
+		"allowImportingTsExtensions": true,
+		"strictNullChecks": true,
+		"strictFunctionTypes": true
+	},
+	"exclude": ["node_modules", "**/node_modules/*"]
+}
diff --git a/t3lt.twee-config.yml b/t3lt.twee-config.yml
index 1200665281344d4012dbb0ba7f2e8e3dd3f689d6..a935fce19f29eea300f874a247f43aceedd094a8 100644
--- a/t3lt.twee-config.yml
+++ b/t3lt.twee-config.yml
@@ -42,6 +42,8 @@ sugarcube-2:
     loveInterestDesc: '`"Robin"` | `"Whitney"` | `"Kylar"` | `"Sydney"` | `"Eden"` | `"Avery"` | `"Alex"` | `"Great Hawk"` | `"Black Wolf"`'
     namedNPC: '"Avery"|"Bailey"|"Briar"|"Charlie"|"Darryl"|"Doren"|"Eden"|"Gwylan"|"Harper"|"Jordan"|"Kylar"|"Landry"|"Leighton"|"Mason"|"Morgan"|"River"|"Robin"|"Sam"|"Sirris"|"Whitney"|"Winter"|"Black Wolf"|"Niki"|"Quinn"|"Remy"|"Alex"|"Great Hawk"|"Wren"|"Sydney"|"Ivory Wraith"|"Zephyr"'
     namedNPCDesc: '`"Avery"` | `"Bailey"` | `"Briar"` | `"Charlie"` | `"Darryl"` | `"Doren"` | `"Eden"` | `"Gwylan"` | `"Harper"` | `"Jordan"` | `"Kylar"` | `"Landry"` | `"Leighton"` | `"Mason"` | `"Morgan"` | `"River"` | `"Robin"` | `"Sam"` | `"Sirris"` | `"Whitney"` | `"Winter"` | `"Black Wolf"` | `"Niki"` | `"Quinn"` | `"Remy"` | `"Alex"` | `"Great Hawk"` | `"Wren"` | `"Sydney"` | `"Ivory Wraith"` | `"Zephyr"`'
+    plantTypes: '"apple"|"baby_bottle_of_breast_milk"|"banana"|"bird_egg"|"blackberry"|"blood_lemon"|"bottle_of_breast_milk"|"bottle_of_milk"|"bottle_of_semen"|"broccoli"|"cabbage"|"carnation"|"chicken_egg"|"daisy"|"garlic_bulb"|"ghostshroom"|"lemon"|"lily"|"lotus"|"mushroom"|"onion"|"orange"|"orchid"|"peach"|"pear"|"plum"|"plumeria"|"poppy"|"potato"|"red_rose"|"strange_flower"|"strawberry"|"truffle"|"tulip"|"turnip"|"white_rose"|"wild_carrot"|"wild_honeycomb"|"wolfshroom"'
+    plantTypesDesc: '`"apple"` | `"baby_bottle_of_breast_milk"` | `"banana"` | `"bird_egg"` | `"blackberry"` | `"blood_lemon"` | `"bottle_of_breast_milk"` | `"bottle_of_milk"` | `"bottle_of_semen"` | `"broccoli"` | `"cabbage"` | `"carnation"` | `"chicken_egg"` | `"daisy"` | `"garlic_bulb"` | `"ghostshroom"` | `"lemon"` | `"lily"` | `"lotus"` | `"mushroom"` | `"onion"` | `"orange"` | `"orchid"` | `"peach"` | `"pear"` | `"plum"` | `"plumeria"` | `"poppy"` | `"potato"` | `"red_rose"` | `"strange_flower"` | `"strawberry"` | `"truffle"` | `"tulip"` | `"turnip"` | `"white_rose"` | `"wild_carrot"` | `"wild_honeycomb"` | `"wolfshroom"`'
     pronouns: he, him, girl, girlfriend, sir, lass
     virginityTypes: '"vaginal"|"penile"|"anal"|"oral"|"kiss"|"handholding"'
     virginityTypesDesc: '`"vaginal"` | `"penile"` | `"anal"` | `"oral"` | `"kiss"` | `"handholding"`'
@@ -13360,7 +13362,19 @@ sugarcube-2:
     tending_harvest:
       name: tending_harvest
     tending_pick:
-      name: tending_pick
+      description: |-
+        Gives the pc a random number of plants, modified by traits
+        
+        `<<tending_pick type min? max?>>`
+        - **type**: `string` - type of plant to pick
+          - %plantTypesDesc%
+        - **min** _optional_: `number` - min number of plants to pick
+          - Defaults to `1`
+        - **max** _optional_: `object` - max number of plants to pick
+          - Defaults to `5`
+      parameters:
+        - '%plantTypes%'
+        - '%plantTypes% &+ number &+ number'
     tending_season_notice:
       name: tending_season_notice
     tending_text:
@@ -13984,6 +13998,10 @@ sugarcube-2:
       tags: ["refactor"]
     underwater:
       name: underwater
+    underwearTypeText:
+      description: |-
+        Prints "underwear" or more specific type as appropriate
+      tags: ["text"]
     underworld_nickname:
       description: |-
         Prints pc's nickname used in the underworld
@@ -14539,6 +14557,22 @@ sugarcube-2:
       name: whitneyRescueEnd
     whitneyRescueExit:
       name: whitneyRescueExit
+    whitneyShoppingData:
+      description: |-
+        Stores Whitney's clothes and reactions from statics into temporary variables
+      tags: ["temp"]
+    whitneyShoppingPicker:
+      description: |-
+        Replaces pc's worn clothes with a temporary choice from Whitney
+
+        Relies on temporary variables from `<<whitneyShoppingData>>`
+      tags: ["temp"]
+    whitneyShoppingPickerChoice:
+      description: |-
+        Replaces pc's worn clothes with a permanent choice from Whitney
+
+        Relies on temporary variables from `<<whitneyShoppingData>>`
+      tags: ["temp"]
     wHunt:
       name: wHunt
     wife: