code:
/* eslint-disable camelcase */
globalThis.rulesAutosurgery = (function() {
	"use strict";

	let r;
	return rulesAutoSurgery;

	/**
	 * @param {App.Entity.SlaveState} slave
	 * @returns {string}
	 */
	function rulesAutoSurgery(slave) {
		r = "";
		const surgeries = [];
		const thisSurgery = ProcessHGTastes(slave);
		if (slave.health.health >= -20) {
			CommitSurgery(slave, thisSurgery, surgeries);
		}
		if (surgeries.length > 0) {
			PrintResult(slave, thisSurgery, surgeries);
		}
		return r;
	}

	/**
	 * @param {App.Entity.SlaveState} slave
	 * @param {App.RA.RuleSetters[]} ruleset
	 * @returns {App.RA.RuleSurgerySettings}
	 */
	function autoSurgerySelector(slave, ruleset) {
		const surgery = App.RA.newRule.surgery();
		ruleset.forEach(rule => {
			Object.keys(rule.surgery)
				.filter(key => rule.surgery[key] !== null)
				.forEach(key => {
					surgery[key] = rule.surgery[key];
				});
		});
		return surgery;
	}

	/**
	 * @param {App.Entity.SlaveState} slave
	 * @returns {App.RA.RuleSurgerySettings}
	 */
	function ProcessHGTastes(slave) {
		let thisSurgery = App.RA.newRule.surgery();
		switch (V.HGTastes) {
			case 1:
				thisSurgery.lactation = 0;
				thisSurgery.cosmetic = 1;
				thisSurgery.faceShape = "cute";
				thisSurgery.lips = App.RA.makeTarget('==', 10);
				thisSurgery.hips = 0;
				thisSurgery.hipsImplant = 0;
				thisSurgery.butt = App.RA.makeTarget('==', 0);
				thisSurgery.accent = 0;
				thisSurgery.shoulders = 0;
				thisSurgery.shouldersImplant = 0;
				thisSurgery.boobs = App.RA.makeTarget('==', 0);
				thisSurgery.holes = 0;
				break;
			case 2:
				thisSurgery.lactation = 0;
				thisSurgery.cosmetic = 1;
				thisSurgery.faceShape = "cute";
				thisSurgery.lips = App.RA.makeTarget('==', 60);
				thisSurgery.hips = 0;
				thisSurgery.hipsImplant = 0;
				thisSurgery.butt = App.RA.makeTarget('==', 4);
				thisSurgery.accent = 0;
				thisSurgery.shoulders = 0;
				thisSurgery.shouldersImplant = 0;
				thisSurgery.boobs = App.RA.makeTarget('==', 1200);
				thisSurgery.holes = 0;
				break;
			case 3:
				thisSurgery.lactation = 0;
				thisSurgery.cosmetic = 1;
				thisSurgery.faceShape = "cute";
				thisSurgery.lips = App.RA.makeTarget('==', 95);
				thisSurgery.hips = 0;
				thisSurgery.hipsImplant = 0;
				thisSurgery.butt = App.RA.makeTarget('==', 8);
				thisSurgery.accent = 0;
				thisSurgery.shoulders = 0;
				thisSurgery.shouldersImplant = 0;
				thisSurgery.boobs = App.RA.makeTarget('==', 10000);
				thisSurgery.holes = 2;
				break;
			case 4:
				thisSurgery.lactation = 1;
				thisSurgery.cosmetic = 1;
				thisSurgery.faceShape = "cute";
				thisSurgery.lips = App.RA.makeTarget('==', 10);
				thisSurgery.hips = 3;
				thisSurgery.hipsImplant = 0;
				thisSurgery.butt = App.RA.makeTarget('==', 0);
				thisSurgery.accent = 0;
				thisSurgery.shoulders = 0;
				thisSurgery.shouldersImplant = 0;
				thisSurgery.boobs = App.RA.makeTarget('==', 0);
				thisSurgery.holes = 0;
				break;
			default:
				thisSurgery = autoSurgerySelector(
					slave,
					V.defaultRules
						.filter(x => ruleApplied(slave, x) && x.set.autoSurgery === 1)
						.map(x => x.set));
				if ((thisSurgery.hips !== null) && (thisSurgery.butt !== null)) {
					if (slave.hips < -1) {
						if (App.RA.shallGrow(2, thisSurgery.butt)) {
							thisSurgery.butt = App.RA.makeTarget('==', 2);
						}
					} else if (slave.hips < 0) {
						if (App.RA.shallGrow(4, thisSurgery.butt)) {
							thisSurgery.butt = App.RA.makeTarget('==', 4);
						}
					} else if (slave.hips > 0) {
						if (App.RA.shallGrow(8, thisSurgery.butt)) {
							thisSurgery.butt = App.RA.makeTarget('==', 8);
						}
					} else if (slave.hips > 1) {
						// true
					} else {
						if (App.RA.shallGrow(6, thisSurgery.butt)) {
							thisSurgery.butt = App.RA.makeTarget('==', 6);
						}
					}
				}
				break;
		}
		return thisSurgery;
	}

	/**
	 * @param {App.Entity.SlaveState} slave
	 * @param {App.RA.RuleSurgerySettings} thisSurgery
	 * @param {string[]} surgeries
	 */
	function CommitSurgery(slave, thisSurgery, surgeries) {
		const {his, him} = getPronouns(slave);

		/**
		 * Performs an individual surgery procedure
		 * @param {string} desc
		 * @param {slaveOperation} proc
		 * @param {number} [healthCost=10] normal health cost
		 */
		function commitProcedure(desc, proc, healthCost = 10) {
			surgeries.push(desc);
			proc(slave);
			cashX(forceNeg(V.surgeryCost), "slaveSurgery", slave);
			surgeryDamage(slave, healthCost);
		}

		/**
		 *
		 * @param {string} bodyPart
		 * @param {!App.RA.NumericTarget} target
		 */
		function bodyPartSizing(bodyPart, target) {
			const shallShrink = App.RA.shallShrink(slave[`${bodyPart}Implant`], target);
			let shallGrow = false;
			let options;
			let sorter;
			if (shallShrink) {
				if (target.val === 0) {
					commitProcedure(`surgery to remove ${his} ${bodyPart} implants`, slave => {
						slave[bodyPart] -= slave[`${bodyPart}Implant`];
						slave[`${bodyPart}Implant`] = 0;
						slave[`${bodyPart}ImplantType`] = "none";
					});
					return;
				}
				options = {reduction: true, replace: true};
				sorter = (left, right) => -right.targetEffect / right.costs + left.targetEffect / left.costs;
			} else if (App.RA.shallGrow(slave[`${bodyPart}Implant`], target)) {
				shallGrow = true;
				options = {augmentation: true, replace: true};
				sorter = (left, right) => right.targetEffect / right.costs - left.targetEffect / left.costs;
			}
			if (!shallShrink && !shallGrow) { return; }

			const surgeryOptions = App.Medicine.Surgery.sizingProcedures.bodyPart(bodyPart, slave, options)
				.filter(surgery => surgery.action !== undefined)
				.sort(sorter);
			for (const so of surgeryOptions) {
				if ((shallShrink && App.RA.shallShrink(slave[`${bodyPart}Implant`], target, so.targetEffect)) ||
					(shallGrow && App.RA.shallGrow(slave[`${bodyPart}Implant`], target, so.targetEffect))) {
					surgeries.push(`surgery to ${so.description}`);
					App.Medicine.Surgery.commit(so, slave);
					break;
				}
			}
		}

		// NOTE: App.RA.shallShrink() and App.RA.shallGrow() return 'false' when target is 'null'
		// Hence they have to be first conditions in the '&&' chains to avoid type errors
		// (reading properties of the 'null' object)
		if (slave.health.health < -20 && surgeries.length >= 3) {
			return `<span class='red'>Either this slave is extremely unwell or they are assigned to have more than two surgeries.</span>`;
		}

		if (thisSurgery.eyes === 1 && anyVisionEquals(slave, 1)) {
			// possibly two surgeries at once, in turn health cost is halved
			if (getLeftEyeVision(slave) === 1) {
				commitProcedure(`surgery to correct ${his} left vision`, s => { eyeSurgery(s, "left", "fix"); }, 5);
			}
			if (getRightEyeVision(slave) === 1) {
				commitProcedure(`surgery to correct ${his} right vision`, s => { eyeSurgery(s, "right", "fix"); }, 5);
			}
		} else if (thisSurgery.eyes === -1 && anyVisionEquals(slave, 2)) {
			// possibly two surgeries at once, in turn health cost is halved
			if (getLeftEyeVision(slave) === 2) {
				commitProcedure(`surgery to blur ${his} left vision`, s => { eyeSurgery(s, "left", "blur"); }, 5);
			}
			if (getRightEyeVision(slave) === 2) {
				commitProcedure(`surgery to blur ${his} right vision`, s => { eyeSurgery(s, "right", "blur"); }, 5);
			}
		} else if (slave.hears === -1 && thisSurgery.hears === 0) {
			commitProcedure(`surgery to correct ${his} hearing`, s => { s.hears = 0; });
		} else if (slave.hears === 0 && thisSurgery.hears === -1) {
			commitProcedure(`surgery to muffle ${his} hearing`, s => { s.hears = -1; });
		} else if (slave.smells === -1 && thisSurgery.smells === 0) {
			commitProcedure(`surgery to correct ${his} sense of smell`, s => { s.smells = 0; });
		} else if (slave.smells === 0 && thisSurgery.smells === -1) {
			commitProcedure(`surgery to muffle ${his} sense of smell`, s => { s.smells = -1; });
		} else if (slave.tastes === -1 && thisSurgery.tastes === 0) {
			commitProcedure(`surgery to correct ${his} sense of taste`, s => { s.tastes = 0; });
		} else if (slave.tastes === 0 && thisSurgery.tastes === -1) {
			commitProcedure(`surgery to muffle ${his} sense of taste`, s => { s.tastes = -1; });
		}

		if (slave.lactation === 2 && thisSurgery.lactation === 0) {
			commitProcedure(`surgery to remove ${his} lactation implants`, s => { s.lactation = 0; });
		} else if (slave.lactation !== 2 && (thisSurgery.lactation === 1)) {
			commitProcedure("lactation inducing implanted drugs", s => {
				s.lactation = 2;
				s.rules.lactation = "none";
			});
		} else if ((slave.boobShape === "saggy" || slave.boobShape === "downward-facing") && thisSurgery.cosmetic > 0 && slave.breastMesh !== 1) {
			commitProcedure("a breast lift", s => { s.boobShape = "normal"; });
		} else if ((slave.boobShape === "normal" || slave.boobShape === "wide-set") && thisSurgery.cosmetic > 0 && slave.breastMesh !== 1) {
			commitProcedure("more interestingly shaped breasts", slave => {
				if (slave.boobs > 800) {
					slave.boobShape = "torpedo-shaped";
				} else {
					slave.boobShape = "perky";
				}
			});
		} else if (thisSurgery.boobs) {
			bodyPartSizing("boobs", thisSurgery.boobs);
		}

		if (thisSurgery.butt !== null) {
			bodyPartSizing("butt", thisSurgery.butt);
		}

		if (slave.anus > 3 && thisSurgery.cosmetic > 0) {
			commitProcedure("a restored anus", slave => {
				slave.anus = 3;
				if (slave.skill.anal > 10) {
					slave.skill.anal -= 10;
				}
			});
		} else if (slave.vagina > 3 && thisSurgery.cosmetic > 0) {
			commitProcedure("a restored pussy", slave => {
				slave.vagina = 3;
				if (slave.skill.vaginal > 10) {
					slave.skill.vaginal -= 10;
				}
			});
		} else if (slave.anus > 0 && V.surgeryUpgrade === 1 && thisSurgery.holes === 2) {
			commitProcedure("a virgin anus", slave => {
				slave.anus = 0;
				if (slave.skill.anal > 10) {
					slave.skill.anal -= 10;
				}
			});
		} else if (slave.vagina > 0 && V.surgeryUpgrade === 1 && thisSurgery.holes === 2) {
			commitProcedure("a virgin pussy", slave => {
				slave.vagina = 0;
				if (slave.skill.vaginal > 10) {
					slave.skill.vaginal -= 10;
				}
			});
		} else if (slave.anus > 1 && thisSurgery.holes === 1) {
			commitProcedure("a tighter anus", slave => {
				slave.anus = 1;
				if (slave.skill.anal > 10) {
					slave.skill.anal -= 10;
				}
			});
		} else if (slave.vagina > 1 && thisSurgery.holes === 1) {
			commitProcedure("a tighter pussy", slave => {
				slave.vagina = 1;
				if (slave.skill.vaginal > 10) {
					slave.skill.vaginal -= 10;
				}
			});
		}

		if (slave.prostate === 2 && thisSurgery.prostate === 1) {
			commitProcedure(`surgery to remove ${his} prostate implant`, s => { s.prostate = 1; });
		} else if (slave.prostate === 1 && thisSurgery.prostate === 2) {
			commitProcedure("a precum production enhancing drug implant", s => { s.prostate = 2; });
		} else if (slave.balls > 0 && slave.vasectomy === 0 && thisSurgery.vasectomy === true) {
			commitProcedure("vasectomy", s => { s.vasectomy = 1; });
			V.surgeryType = "vasectomy";
		} else if (slave.balls > 0 && slave.vasectomy === 1 && thisSurgery.vasectomy === false) {
			commitProcedure("undo vasectomy", s => { s.vasectomy = 0; });
			V.surgeryType = "vasectomy undo";
		}

		if (slave.faceImplant <= 15 && slave.face <= 95 && thisSurgery.cosmetic > 0) {
			commitProcedure("a nicer face", slave => {
				if (slave.faceShape === "masculine") { slave.faceShape = "androgynous"; }
				slave.faceImplant += 25 - 5 * Math.trunc(V.PC.skill.medicine / 50) - 5 * V.surgeryUpgrade;
				slave.face = Math.clamp(slave.face + 20, -100, 100);
			});
		} else if (slave.faceImplant <= 15 && slave.ageImplant !== 1 && slave.visualAge >= 25 && thisSurgery.cosmetic > 0) {
			commitProcedure("an age lift", slave => {
				slave.faceImplant += 25 - 5 * Math.trunc(V.PC.skill.medicine / 50) - 5 * V.surgeryUpgrade;
				applyAgeImplant(slave);
			});
		} else if (((slave.underArmHStyle !== "bald" && slave.underArmHStyle !== "hairless") || (slave.pubicHStyle !== "bald" && slave.pubicHStyle !== "hairless")) && thisSurgery.bodyhair === 2) {
			commitProcedure("body hair removal", slave => {
				if (slave.underArmHStyle !== "hairless") { slave.underArmHStyle = "bald"; }
				if (slave.pubicHStyle !== "hairless") { slave.pubicHStyle = "bald"; }
			}, 0);
		} else if ((slave.bald === 0 || slave.hStyle !== "bald" || slave.eyebrowHStyle !== "bald") && thisSurgery.hair === 2) {
			commitProcedure("hair removal", slave => {
				slave.eyebrowHStyle = "bald";
				slave.hStyle = "bald";
				slave.bald = 1;
			}, 0);
		} else if (slave.weight >= 10 && thisSurgery.cosmetic > 0) {
			commitProcedure("liposuction", s => { s.weight -= 50; });
		} else if ((slave.bellySagPreg > 0 || slave.bellySag > 0) && (thisSurgery.cosmetic > 0 || thisSurgery.tummy > 0 )) {
			commitProcedure("a tummy tuck", slave => {
				slave.bellySag = 0;
				slave.bellySagPreg = 0;
			}, 20);
		} else if (slave.voice === 1 && slave.voiceImplant === 0 && thisSurgery.cosmetic > 0) {
			commitProcedure("a feminine voice", slave => {
				slave.voice += 1;
				slave.voiceImplant += 1;
			});
		} else if (App.RA.shallShrink(slave.lipsImplant, thisSurgery.lips) && thisSurgery.lips.val === 0) {
			commitProcedure(`surgery to remove ${his} lip implants`, slave => {
				slave.lips -= slave.lipsImplant;
				slave.lipsImplant = 0;
				if (slave.skill.oral > 10) {
					slave.skill.oral -= 10;
				}
			});
		} else if (App.RA.shallGrow(slave.lipsImplant, thisSurgery.lips, 10) && slave.lips <= 95) {
			commitProcedure("bigger lips", slave => {
				slave.lipsImplant += 10;
				slave.lips += 10;
				if (slave.skill.oral > 10) {
					slave.skill.oral -= 10;
				}
			});
		} else if (slave.scar.hasOwnProperty("belly") && slave.scar.belly["c-section"] > 0 && thisSurgery.cosmetic > 0) {
			commitProcedure("surgery to remove a c-section scar", s => { App.Medicine.Modification.removeScar(s, "belly", "c-section"); });
		} else if (slave.faceImplant <= 45 && slave.face <= 95 && thisSurgery.cosmetic === 2) {
			commitProcedure("a nicer face", slave => {
				if (slave.faceShape === "masculine") { slave.faceShape = "androgynous"; }
				slave.faceImplant += 25 - 5 * Math.trunc(V.PC.skill.medicine / 50) - 5 * V.surgeryUpgrade;
				slave.face = Math.clamp(slave.face + 20, -100, 100);
			});
		} else if (slave.faceImplant <= 45 && slave.ageImplant !== 1 && slave.visualAge >= 25 && thisSurgery.cosmetic === 2) {
			commitProcedure("an age lift", slave => {
				applyAgeImplant(slave);
				slave.faceImplant += 25 - 5 * Math.trunc(V.PC.skill.medicine / 50) - 5 * V.surgeryUpgrade;
			});
		} else if (slave.voice < 3 && slave.voiceImplant === 0 && thisSurgery.cosmetic === 2) {
			commitProcedure("a bimbo's voice", slave => {
				slave.voice += 1;
				slave.voiceImplant += 1;
			});
		}

		if (slave.waist >= -10 && thisSurgery.cosmetic > 0) {
			commitProcedure("a narrower waist", s => { s.waist -= 20; });
		} else if (slave.waist >= -95 && V.seeExtreme === 1 && thisSurgery.cosmetic === 2) {
			commitProcedure("a narrower waist", s => { s.waist = Math.clamp(s.waist - 20, -100, 100); });
		} else if (thisSurgery.hips !== null && slave.hips < 3 && V.surgeryUpgrade === 1 && (slave.hips < thisSurgery.hips)) {
			commitProcedure("wider hips", slave => {
				slave.hips++;
				slave.hipsImplant++;
			});
		}

		if (slave.bellyImplant < 0 && V.bellyImplants > 0 && thisSurgery.bellyImplant === "install" && slave.womb.length === 0 && slave.broodmother === 0) {
			const proc = slave => {
				slave.bellyImplant = 100;
				slave.preg = -2;
			};
			if (slave.ovaries === 1 || slave.mpreg === 1) {
				V.surgeryType = "bellyIn";
				commitProcedure("belly implant", proc, 10);
			} else {
				V.surgeryType = "bellyInMale";
				commitProcedure("male belly implant", proc, 50);
			}
			bellyIn(slave);
		} else if (slave.bellyImplant >= 0 && thisSurgery.bellyImplant === "remove") {
			commitProcedure("belly implant removal", slave => {
				slave.preg = 0;
				slave.bellyImplant = -1;
				slave.cervixImplant = 0;
			});
			V.surgeryType = "bellyOut";
		}

		if (slave.horn !== "none" && thisSurgery.horn === 1) {
			commitProcedure(`surgery to remove ${his} implanted horns`, s => { s.horn = "none"; });
		} else if (slave.horn !== "curved succubus horns" && thisSurgery.horn === 2) {
			commitProcedure(`surgery to implant ${him} with curved succubus horns`, s => { s.horn = "curved succubus horns"; s.hornColor = "white"; });
		} else if (slave.horn !== "backswept horns" && thisSurgery.horn === 3) {
			commitProcedure(`surgery to implant ${him} with backswept horns`, s => { s.horn = "backswept horns"; s.hornColor = "white"; });
		} else if (slave.horn !== "cow horns" && thisSurgery.horn === 4) {
			commitProcedure(`surgery to implant ${him} with cow horns`, s => { s.horn = "cow horns"; s.hornColor = "white"; });
		} else if (slave.horn !== "one long oni horn" && thisSurgery.horn === 5) {
			commitProcedure(`surgery to implant ${him} with one long oni horn`, s => { s.horn = "one long oni horn"; s.hornColor = "white"; });
		} else if (slave.horn !== "two long oni horns" && thisSurgery.horn === 6) {
			commitProcedure(`surgery to implant ${him} with two long oni horns`, s => { s.horn = "two long oni horns"; s.hornColor = "white"; });
		} else if (slave.horn !== "small horns" && thisSurgery.horn === 7) {
			commitProcedure(`surgery to implant ${him} with small horns`, s => { s.horn = "small horns"; s.hornColor = "white"; });
		}

		if (slave.earShape !== "normal" && thisSurgery.earShape === 1) {
			commitProcedure(`surgery to restore ${his} modified ears`, s => { s.earShape = "normal"; });
		} else if (slave.earShape !== "pointy" && thisSurgery.earShape === 2) {
			commitProcedure(`surgery to modify ${his} ears into a pair of small pointy ears`, s => { s.earShape = "pointy"; });
		} else if (slave.earShape !== "elven" && thisSurgery.earShape === 3) {
			commitProcedure(`surgery to modify ${his} ears into a pair of elven ears`, s => { s.earShape = "elven"; });
		} else if (slave.earShape !== "ushi" && thisSurgery.earShape === 4) {
			commitProcedure(`surgery to modify ${his} ears into a pair of bovine-like ears`, s => { s.earShape = "ushi"; });
		}
	}

	/**
	 * @param {App.Entity.SlaveState} slave
	 * @param {object} thisSurgery
	 * @param {string[]} surgeries
	 */
	function PrintResult(slave, thisSurgery, surgeries) {
		let surgeriesDisplay = "";
		if (surgeries.length === 1) {
			surgeriesDisplay = surgeries[0];
		} else {
			surgeriesDisplay = surgeries.slice(0, surgeries.length - 1).join(", ");
			surgeriesDisplay += `, and ${surgeries[surgeries.length - 1]}`;
		}
		r += `${capFirstChar(V.assistant.name)}, ordered to apply surgery, gives ${slave.slaveName} ${surgeries.length > 1 ? `${surgeries.length} surgeries` : 'a single operation'}: <span class="lime">${surgeriesDisplay}.</span>`;
	}

	/**
	 * @param {App.Entity.SlaveState} slave
	 */
	function bellyIn(slave) {
		// less hacky version of calling surgery degradation silently
		if (slave.devotion > 50) {
			slave.devotion += 4;
		} else if (slave.devotion >= -20) {
			slave.trust -= 5;
		} else {
			slave.trust -= 5;
			slave.devotion -= 5;
		}
	}
})();

repo_name: NovX/fc

license: mit


code:
/**
 * @param {App.Entity.PlayerState|App.Entity.SlaveState} entity
 * @param {boolean} player
 * @returns {HTMLDivElement}
 */
App.Medicine.Modification.eyeSelector = function(entity, player = false) {
	const {He, him, his} = getPronouns(entity);

	let selectedSide = "none";
	let selectedIris = "none";
	let selectedPupil = "none";
	let selectedSclera = "none";

	let removeDiv = document.createElement("div");
	removeDiv.classList.add("choices");
	let applyDiv = document.createElement("div");

	const container = document.createElement("div");
	container.append(
		`${player ? "You have" : `${He} has`} ${App.Desc.eyesColorLong(entity)}, ${hasBothEyes(
			entity) ? "they are" : "it is"} ${App.Desc.eyesType(entity)}.`,
		removeDiv, "You have a number of contact lenses in various colors available. ",
		App.UI.DOM.makeElement("span", `You can change what ${player ? "your" : his} eyes look like.`, "note"),
		assembleLinks(), applyDiv
	);
	updateRemoveDiv();
	updateApplyDiv();
	return container;

	function assembleLinks() {
		const sides = ["left", "right", "both"];
		const irisColors = ["amber", "black", "blue", "brown", "green", "hazel", "orange", "pale-grey", "pink", "red",
			"sky-blue", "turquoise", "white", "yellow"];
		const pupilShapes = ["none", "circular", "almond-shaped", "bright", "catlike", "demonic", "devilish",
			"goat-like", "heart-shaped", "hypnotic", "serpent-like", "star-shaped", "teary", "vacant", "wide-eyed"];
		const scleraColors = ["none", "white", "amber", "black", "blue", "brown", "green", "hazel", "orange",
			"pale-grey", "pink", "red", "sky-blue", "turquoise", "yellow"];
		const div = document.createDocumentFragment();
		div.append(
			assembleList("Side: ", sides, value => selectedSide = value, selectedIris),
			assembleList("Iris: ", irisColors, value => selectedIris = value, selectedSide),
			assembleList("Pupil: ", pupilShapes, value => selectedPupil = value, selectedPupil),
			assembleList("Sclera: ", scleraColors, value => selectedSclera = value, selectedSclera)
		);
		return div;
	}

	/**
	 * @param {string} name
	 * @param {Array<string>} list
	 * @param {Function}callback
	 * @param {string} selected
	 * @returns {HTMLDivElement}
	 */
	function assembleList(name, list, callback, selected) {
		const links = [];

		for (let i = 0; i < list.length; i++) {
			addToggle(list[i], callback, links, list[i] === selected);
		}

		const div = document.createElement("div");
		div.classList.add("choices");
		div.append(name, App.UI.DOM.arrayToList(links, " | ", " | "));
		return div;
	}

	/**
	 * @param {string} value
	 * @param {Function} callback
	 * @param {Array<HTMLAnchorElement>} links
	 * @param {boolean} [disabled]
	 */
	function addToggle(value, callback, links, disabled = false) {
		const a = document.createElement("a");
		a.append(capFirstChar(value));
		if (disabled) {
			a.classList.add("disabled");
		}
		a.onclick = () => {
			for (let link of links) {
				link.classList.remove("disabled");
			}
			a.classList.add("disabled");
			callback(value);
			updateRemoveDiv();
			updateApplyDiv();
		};
		links.push(a);
	}

	function updateApplyDiv() {
		$(applyDiv).empty();
		if (selectedSide !== "none" && selectedIris !== "none") {
			// make the following easier to read
			let both = selectedSide === "both";
			let leftGlass = !hasLeftEye(entity) || getLeftEyeType(entity) === 2;
			let rightGlass = !hasRightEye(entity) || getRightEyeType(entity) === 2;

			// base eye
			let r = player ? "" : ` ${him}`;
			if (both) {
				if (leftGlass && rightGlass) {
					r += ` ${selectedIris} glass eyes`;
				} else if (leftGlass || rightGlass) {
					r += ` a glass eye and a ${selectedIris} lens`;
				} else {
					r += ` ${selectedIris} lenses`;
				}
			} else {
				r += " a";
				if ((selectedSide === "left" && leftGlass) || (selectedSide === "right" && rightGlass)) {
					r += ` ${selectedIris} glass eye`;
				} else {
					r += ` ${selectedIris} lens`;
				}
			}
			// pupil & sclera
			if (selectedPupil !== "none" || selectedSclera !== "none") {
				r += " with";
				if (selectedPupil !== "none") {
					r += ` ${both ? selectedPupil : addA(selectedPupil)}`;
					if (both) {
						r += " pupils";
					} else {
						r += " pupil";
					}
					if (selectedSclera !== "none") {
						r += " and";
					}
				}
				if (selectedSclera !== "none") {
					r += ` ${selectedSclera}`;
					if (both) {
						r += " sclerae";
					} else {
						r += " sclera";
					}
				}
			}
			if (!both) {
				r += ` for ${player ? "your" : his} ${selectedSide} eye`;
			}
			r += "?";

			const a = document.createElement("a");
			a.append(player ? "Take" : "Give");
			a.onclick = applyLink;
			applyDiv.append(a, r);
			if (!player) {
				applyDiv.append(" ",
					App.UI.DOM.makeElement("span", "This is independent from eyewear choices.", "note"));
			}
		}
	}

	function applyLink() {
		// make sure the eye exists; give glass eye if there is none
		if ((selectedSide === "left" || selectedSide === "both") && getLeftEyeType(entity) === 0) {
			eyeSurgery(entity, "left", "glass");
		}
		if ((selectedSide === "right" || selectedSide === "both") && getRightEyeType(entity) === 0) {
			eyeSurgery(entity, "right", "glass");
		}

		// apply modifications
		setEyeColorFull(entity, selectedIris,
			selectedPupil === "none" ? "" : selectedPupil,
			selectedSclera === "none" ? "" : selectedSclera,
			selectedSide);
		cashX(forceNeg(V.modCost), "slaveMod", entity);

		App.UI.reload();
	}

	function updateRemoveDiv() {
		$(removeDiv).empty();
		const links = [];
		let _n = 0;
		// remove lenses
		if (hasLeftEye(entity) && getLeftEyeColor(entity) !== getGeneticEyeColor(entity, "left")) {
			_n++;
			links.push(removeLink("Remove left lens", () => resetEyeColor(entity, "left")));
		}
		if (hasRightEye(entity) && getRightEyeColor(entity) !== getGeneticEyeColor(entity, "right")) {
			_n++;
			links.push(removeLink("Remove right lens", () => resetEyeColor(entity, "right")));
		}
		if (_n === 2) {
			links.push(removeLink("Remove both lenses", () => resetEyeColor(entity, "both")));
		}
		// remove glass eyes
		_n = 0;
		if (getLeftEyeType(entity) === 2) {
			_n++;
			links.push(removeLink("Remove left glass eye", () => eyeSurgery(entity, "left", "remove")));
		}
		if (getRightEyeType(entity) === 2) {
			_n++;
			links.push(removeLink("Remove right glass eye", () => eyeSurgery(entity, "right", "remove")));
		}
		if (_n === 2) {
			links.push(removeLink("Remove both glass eyes", () => eyeSurgery(entity, "both", "remove")));
		}
		if (links.length > 0) {
			removeDiv.append(App.UI.DOM.arrayToList(links, " | ", " | "));
		}
	}

	function removeLink(text, callback) {
		const a = document.createElement("a");
		a.append(text);
		a.onclick = () => {
			callback();
			App.UI.reload();
		};
		return a;
	}
};

/**
 * Update ears in salon
 * @param {App.Entity.SlaveState} slave
 * @param {object} params
 * @param {number|string} [params.primaryEarColor]
 * @param {string} [params.secondaryEarColor]
 * @returns {node}
 */
App.Medicine.Salon.ears = function(slave, {primaryEarColor = 0, secondaryEarColor = ""} = {}) {
	const frag = new DocumentFragment();
	let updatePrimary = (newVal) => { primaryEarColor = newVal; apply(); };
	let updateSecondary = (newVal) => { secondaryEarColor = newVal; apply(); };

	if (slave.earT !== "none" && slave.earTColor !== "hairless") {
		const {His, his} = getPronouns(slave);
		let div;
		let p;
		frag.append(`${His} fluffy ears are ${slave.earTColor}.`);

		div = document.createElement("div");
		div.classList.add("choices");
		if (slave.earTColor !== slave.hColor) {
			div.append(
				App.UI.DOM.link(
					"Match current hair",
					() => {
						slave.earTColor = slave.hColor;
						App.Art.refreshSlaveArt(slave, 3, "art-frame");
						apply();
					}
				)
			);
			div.append(" or ");
			App.UI.DOM.appendNewElement("span", div, "choose a new one: ", "note");
		} else {
			App.UI.DOM.appendNewElement("span", div, `Choose a dye color before dyeing ${his} ears:`, "note");
		}
		frag.append(div);

		div = document.createElement("div");
		div.classList.add("choices");
		div.append(`Colors:`);
		div.append(createList(App.Medicine.Modification.Color.Primary, updatePrimary));
		frag.append(div);

		div = document.createElement("div");
		div.classList.add("choices");
		div.append(`Highlights:`);
		div.append(createList(App.Medicine.Modification.Color.Secondary, updateSecondary));
		frag.append(div);

		if (primaryEarColor !== 0) {
			p = document.createElement("p");
			p.classList.add("choices");
			p.append(
				App.UI.DOM.link(
					`Color ${his} ears`,
					() => {
						slave.earTColor = (primaryEarColor + secondaryEarColor);
						App.Art.refreshSlaveArt(slave, 3, "art-frame");
						cashX(forceNeg(V.modCost), "slaveMod", slave);
						App.Medicine.Salon.ears(slave); // discard selections after locking them in.
					}
				)
			);
			p.append(` ${primaryEarColor}${secondaryEarColor} now?`);
			frag.append(p);
		}
	}
	return jQuery("#salon-ears").empty().append(frag);

	function createList(array, method) {
		const links = [];
		for (const item of array) {
			const title = item.title || capFirstChar(item.value);
			links.push(
				App.UI.DOM.link(
					title,
					() => method(item.value)
				)
			);
		}
		return App.UI.DOM.generateLinksStrip(links);
	}
	function apply() {
		App.Art.refreshSlaveArt(slave, 3, "art-frame");
		App.Medicine.Salon.ears(
			slave,
			{
				primaryEarColor: primaryEarColor,
				secondaryEarColor: secondaryEarColor,
			}
		);
	}
};

/**
 * Update hair in salon
 * @param {App.Entity.SlaveState} slave
 * @param {object} params
 * @param {number|string} [params.primaryHairColor]
 * @param {string} [params.secondaryHairColor]
 * @returns {node}
 */
App.Medicine.Salon.hair = function(slave, {primaryHairColor = 0, secondaryHairColor = ""} = {}) {
	const frag = new DocumentFragment();
	let updatePrimary = (newVal) => { primaryHairColor = newVal.value; apply(); };
	let updateSecondary = (newVal) => { secondaryHairColor = newVal.value; apply(); };
	const {His, his, He, him} = getPronouns(slave);

	if (slave.bald !== 1) {
		frag.append(hairDye());
		frag.append(hairStyle());
		frag.append(hairLength());
		frag.append(hairMaint());
	} else {
		// Bald
		if (slave.hStyle === "bald") {
			frag.append(`${He} is completely bald. `);
		} else {
			frag.append(wigDye());
		}
		frag.append(wigStyle());
		frag.append(wigLength());
	}
	return jQuery("#salon-hair").empty().append(frag);

	function hairDye() {
		const frag = new DocumentFragment();
		let div;
		let p;
		frag.append(`${His} hair is ${slave.hColor}.`);

		div = document.createElement("div");
		div.classList.add("choices");
		if (slave.origHColor !== slave.hColor) {
			div.append(
				App.UI.DOM.link(
					"Restore natural color",
					() => {
						slave.hColor = slave.origHColor;
						App.Art.refreshSlaveArt(slave, 3, "art-frame");
						cashX(forceNeg(V.modCost), "slaveMod", slave);
						apply();
					}
				)
			);
			div.append(" or ");
			App.UI.DOM.appendNewElement("span", div, "choose a new one: ", "note");
		} else {
			App.UI.DOM.appendNewElement("span", div, `Choose a dye color before dyeing ${his} hair:`, "note");
		}
		frag.append(div);

		div = document.createElement("div");
		div.classList.add("choices");
		div.append(`Colors:`);
		div.append(createList(App.Medicine.Modification.Color.Primary, updatePrimary));
		frag.append(div);

		div = document.createElement("div");
		div.classList.add("choices");
		div.append(`Highlights:`);
		div.append(createList(App.Medicine.Modification.Color.Secondary, updateSecondary));
		frag.append(div);

		if (primaryHairColor !== 0) {
			p = document.createElement("p");
			p.classList.add("choices");
			p.append(
				App.UI.DOM.link(
					`Color ${his} hair`,
					() => {
						slave.hColor = (primaryHairColor + secondaryHairColor);
						App.Art.refreshSlaveArt(slave, 3, "art-frame");
						cashX(forceNeg(V.modCost), "slaveMod", slave);
						App.Medicine.Salon.hair(slave); // discard selections after locking them in.
					}
				)
			);
			p.append(` ${primaryHairColor}${secondaryHairColor} now?`);
			frag.append(p);
		}
		return frag;
	}

	function hairStyle() {
		const frag = new DocumentFragment();
		let div;
		let method;
		div = document.createElement("div");
		if (slave.hStyle !== "shaved") {
			div.append(`${His} ${slave.hStyle} hair is ${lengthToEitherUnit(slave.hLength)} long. `);
		} else {
			div.append(`${His} hair is shaved smooth. `);
		}
		App.UI.DOM.appendNewElement("span", div, `General hairstyles will conform to hair length and clothing choices.`, "note");
		frag.append(div);

		// Normal styles
		div = document.createElement("div");
		div.classList.add("choices");
		method = (newVal) => {
			slave.hStyle = newVal.value;
			cashX(forceNeg(V.modCost), "slaveMod", slave);
			apply();
		};
		if (slave.hLength > 1) {
			div.append(`Style ${his} hair:`);
			div.append(createList(App.Medicine.Modification.hairStyles.Normal, method));
		} else {
			App.UI.DOM.appendNewElement("span", div, `${His} hair is too short to style meaningfully`, "note");
		}
		frag.append(div);

		// Short styles, includes cutting
		div = document.createElement("div");
		div.classList.add("choices");
		method = (newVal) => {
			slave.hStyle = newVal.value;
			slave.hLength = newVal.hLength;
			cashX(forceNeg(V.modCost), "slaveMod", slave);
			apply();
		};
		if (slave.hLength > 1) {
			div.append(`Cut and style ${his} hair:`);
			div.append(createList(App.Medicine.Modification.hairStyles.Cut, method));
		}
		frag.append(div);

		return frag;
	}

	function hairLength() {
		const frag = new DocumentFragment();
		let div = document.createElement("div");
		div.classList.add("choices");
		let method = (newVal) => {
			if (newVal.hasOwnProperty("onApplication")) {
				newVal.onApplication(slave);
			}
			if (newVal.hasOwnProperty("hLength")) {
				slave.hLength = newVal.hLength;
			}
			apply();
		};
		const oldHLength = (V.showInches === 2) ? Math.round(slave.hLength / 2.54) : slave.hLength;

		App.UI.DOM.appendNewElement("span", div, `Cut or lengthen ${his} hair:`);
		div.append(createList(App.Medicine.Modification.hairStyles.Length, method));
		div.append(" | Custom length: ");
		div.append(
			App.UI.DOM.makeTextBox(
				oldHLength,
				v => {
					v = Math.max(v, 0); // Positive hair length only
					// If they entered "inches," convert
					if (V.showInches === 2) {
						v = Math.round(v * 2.54);
					}
					slave.hLength = v;
					cashX(forceNeg(V.modCost), "slaveMod", slave);
					apply();
				},
				true
			)
		);
		if (V.showInches === 1) {
			div.append(`cm (${cmToInchString(slave.hLength)})`);
		} else if (V.showInches === 2) {
			div.append(`inches`);
		}

		frag.append(div);

		return frag;
	}

	function hairMaint() {
		let div = document.createElement("div");
		div.classList.add("choices");
		div.append(`Have ${his} hair carefully maintained at its current length: `);
		let haircuts;
		let text;
		if (slave.haircuts === 1) {
			text = "Cease maintenance";
			haircuts = 0;
		} else {
			text = "Begin maintenance";
			haircuts = 1;
		}
		div.append(
			App.UI.DOM.link(
				text,
				() => {
					slave.haircuts = haircuts;
					apply();
				}
			)
		);
		return div;
	}

	function wigDye() {
		const frag = new DocumentFragment();
		let div;
		let p;
		frag.append(`${His} current wig is ${slave.hColor}. `);

		if (slave.hStyle !== "bald") {
			frag.append(
				App.UI.DOM.link(
					"Remove wig",
					() => {
						slave.hStyle = "bald";
						slave.hLength = 0;
						// I'm not going to charge you for taking off a fucking wig.
						apply();
					}
				)
			);
			frag.append(" or ");
			App.UI.DOM.appendNewElement("span", frag, "choose a new one: ", "note");
		} else {
			App.UI.DOM.appendNewElement("span", frag, `Choose a wig color:`, "note");
		}

		div = document.createElement("div");
		div.classList.add("choices");
		div.append(`Colors:`);
		div.append(createList(App.Medicine.Modification.Color.Primary, updatePrimary));
		frag.append(div);

		div = document.createElement("div");
		div.classList.add("choices");
		div.append(`Highlights:`);
		div.append(createList(App.Medicine.Modification.Color.Secondary, updateSecondary));
		frag.append(div);

		if (primaryHairColor !== 0) {
			p = document.createElement("p");
			p.classList.add("choices");
			p.append(
				App.UI.DOM.link(
					`Change`,
					() => {
						slave.earTColor = (primaryHairColor + secondaryHairColor);
						App.Art.refreshSlaveArt(slave, 3, "art-frame");
						cashX(forceNeg(V.modCost), "slaveMod", slave);
						App.Medicine.Salon.hair(slave); // discard selections after locking them in.
					}
				)
			);
			p.append(` ${his} wig color to ${primaryHairColor}${secondaryHairColor} now?`);
			frag.append(p);
		}
		return frag;
	}

	function wigLength() {
		const frag = new DocumentFragment();
		if (slave.hStyle === "bald") {
			return frag;
		}
		let div = document.createElement("div");
		div.classList.add("choices");
		const array = [];
		for (const number of [10, 30, 60, 90, 120, 150]) {
			const obj = {};
			obj.title = lengthToEitherUnit(number);
			obj.hLength = number;
			array.push(obj);
		}
		let method = (newVal) => {
			slave.hLength = newVal.hLength;
			apply();
		};
		const oldHLength = (V.showInches === 2) ? Math.round(slave.hLength / 2.54) : slave.hLength;
		App.UI.DOM.appendNewElement("span", div, `Set wig length to:`, "choices");
		div.append(createList(array, method));
		div.append(" | Custom length: ");
		div.append(
			App.UI.DOM.makeTextBox(
				oldHLength,
				v => {
					v = Math.max(v, 10); // Wigs must be at least 10 cm
					// If they entered "inches," convert
					if (V.showInches === 2) {
						v = Math.round(v * 2.54);
					}
					slave.hLength = v;
					cashX(forceNeg(V.modCost), "slaveMod", slave);
					apply();
				},
				true
			)
		);
		if (V.showInches === 1) {
			div.append(`cm (${cmToInchString(slave.hLength)})`);
		} else if (V.showInches === 2) {
			div.append(`inches`);
		}

		frag.append(div);

		return frag;
	}

	function wigStyle() {
		const frag = new DocumentFragment();
		let div = document.createElement("div");
		div.classList.add("choices");
		const method = (newVal) => {
			slave.hStyle = newVal.value;
			cashX(forceNeg(V.modCost), "slaveMod", slave);
			apply();
		};

		if (slave.hStyle !== "bald") {
			frag.append(`${His} ${slave.hStyle} wig is ${lengthToEitherUnit(slave.hLength)} long. `);
		} else {
			frag.append(`${He} is not wearing a wig. `);
		}
		App.UI.DOM.appendNewElement("span", frag, `General hairstyles will conform to hair length and clothing choices.`, "note");

		div = document.createElement("div");
		div.classList.add("choices");
		if (slave.hStyle === "bald") {
			div.append(`Give ${him} a wig:`);
		} else {
			div.append(`Set wig style:`);
		}
		div.append(createList(App.Medicine.Modification.hairStyles.Normal, method));
		frag.append(div);
		return frag;
	}

	function createList(array, method) {
		const links = [];
		for (const item of array) {
			if (item.hasOwnProperty("requirements")) {
				if (item.requirements(slave) === false) {
					continue;
				}
			}
			const title = item.title || capFirstChar(item.value);
			links.push(
				App.UI.DOM.link(
					title,
					() => method(item)
				)
			);
		}
		return App.UI.DOM.generateLinksStrip(links);
	}

	function apply() {
		App.Art.refreshSlaveArt(slave, 3, "art-frame");
		App.Medicine.Salon.hair(
			slave,
			{
				primaryHairColor: primaryHairColor,
				secondaryHairColor: secondaryHairColor,
			}
		);
	}
};

/**
 * Update hair in salon
 * @param {App.Entity.SlaveState} slave
 * @param {object} params
 * @param {number|string} [params.primaryTailColor]
 * @param {string} [params.secondaryTailColor]
 * @returns {node}
 */
App.Medicine.Salon.tail = function(slave, {primaryTailColor = 0, secondaryTailColor = ""} = {}) {
	const frag = new DocumentFragment();
	let updatePrimary = (newVal) => { primaryTailColor = newVal.value; apply(); };
	let updateSecondary = (newVal) => { secondaryTailColor = newVal.value; apply(); };
	const {His, his} = getPronouns(slave);

	if (slave.tail !== "none") {
		frag.append(tailDye());
	}

	return jQuery("#salon-tail").empty().append(frag);

	function tailDye() {
		const frag = new DocumentFragment();
		let div;
		let p;
		frag.append(`${His} tail is ${slave.tailColor}.`);

		div = document.createElement("div");
		div.classList.add("choices");
		if (slave.origHColor !== slave.hColor) {
			div.append(
				App.UI.DOM.link(
					"Match current hair",
					() => {
						slave.tailColor = slave.hColor;
						App.Art.refreshSlaveArt(slave, 3, "art-frame");
						cashX(forceNeg(V.modCost), "slaveMod", slave);
						apply();
					}
				)
			);
			div.append(" or ");
			App.UI.DOM.appendNewElement("span", div, "choose a new one: ", "note");
		} else {
			App.UI.DOM.appendNewElement("span", div, `Choose a dye color before dyeing ${his} tail:`, "note");
		}
		frag.append(div);

		div = document.createElement("div");
		div.classList.add("choices");
		div.append(`Colors:`);
		div.append(createList(App.Medicine.Modification.Color.Primary, updatePrimary));
		frag.append(div);

		div = document.createElement("div");
		div.classList.add("choices");
		div.append(`Highlights:`);
		div.append(createList(App.Medicine.Modification.Color.Secondary, updateSecondary));
		frag.append(div);

		if (primaryTailColor !== 0) {
			p = document.createElement("p");
			p.classList.add("choices");
			p.append(
				App.UI.DOM.link(
					`Color ${his} tail`,
					() => {
						slave.tailColor = (primaryTailColor + secondaryTailColor);
						App.Art.refreshSlaveArt(slave, 3, "art-frame");
						cashX(forceNeg(V.modCost), "slaveMod", slave);
						App.Medicine.Salon.tail(slave); // discard selections after locking them in.
					}
				)
			);
			p.append(` ${primaryTailColor}${secondaryTailColor} now?`);
			frag.append(p);
		}
		return frag;
	}

	function createList(array, method) {
		const links = [];
		for (const item of array) {
			if (item.hasOwnProperty("requirements")) {
				if (item.requirements(slave) === false) {
					continue;
				}
			}
			const title = item.title || capFirstChar(item.value);
			links.push(
				App.UI.DOM.link(
					title,
					() => method(item)
				)
			);
		}
		return App.UI.DOM.generateLinksStrip(links);
	}

	function apply() {
		App.Art.refreshSlaveArt(slave, 3, "art-frame");
		App.Medicine.Salon.tail(
			slave,
			{
				primaryTailColor: primaryTailColor,
				secondaryTailColor: secondaryTailColor,
			}
		);
	}
};


code:
globalThis.VCheck = (function() {
	"use strict";
	/* eslint-disable no-unused-vars*/
	let he;
	let him;
	let his;
	let hers;
	let himself;
	let boy;
	let He;
	let His;
	/* eslint-enable */

	return {
		Anal: AnalVCheck,
		Vaginal: VaginalVCheck,
		Both: BothVCheck,
		Simple: SimpleVCheck,
		Partner: PartnerVCheck
	};

	function setScopedPronouns(slave) {
		({
			he, him, his, hers, himself, boy, He, His
		} = getPronouns(slave));
	}

	/** call as VCheck.Anal()
	 * @param {number} [times=1] is how many times to increment the anal counts.
	 * @param {App.Entity.SlaveState} [slave=V.activeSlave]
	 * @returns {string}
	 */
	function AnalVCheck(times = 1, slave = V.activeSlave) {
		let r = '';
		setScopedPronouns(slave);

		if (canDoAnal(slave) && slave.anus === 0) {
			r += `<span class="lime">This breaks in ${slave.slaveName}'s virgin ass.</span> `;
			if (slave.devotion > 50 || slave.career === "a slave since birth") {
				r += `Since it's ${his} first time, you gently ease yourself into ${his} butthole and gradually increase the intensity of your thrusts. Before long ${he}'s moaning loudly as you continue working away at ${his} butthole. `;
				if (slave.tankBaby === 2) {
					r += `${He} thinks of losing ${his} anal virginity to ${his} ${WrittenMaster(slave)} a <span class="hotpink">necessity.</span> ${He} expects ${his} asshole to be seeing a lot more attention now.`;
				} else {
					r += `${He} thinks of losing ${his} anal virginity to you as a <span class="hotpink">connection</span> with ${his} beloved ${WrittenMaster(slave)}. `;
					if ((slave.fetishKnown && slave.fetish === "buttslut") || (slave.energy > 95) || (slave.attrXX >= 85 && V.PC.dick === 0)) {
						r += `${He} can't wait to be fucked in the ass by you again.`;
					} else {
						r += `${He} looks forward to having ${his} asshole fucked by you again.`;
					}
				}
				slave.devotion += 4;
			} else if (slave.devotion > 20) {
				r += `Since it's ${his} first time, you gently ease yourself into ${his} butthole and gradually increase the intensity of your thrusts. ${His} moans become louder and louder as you continue working away at ${his} butthole. ${He} accepts the pain and humiliation of anal sex as part of ${his} sexual servitude, though ${he} hopes that ${his} next time will be less painful.`;
			} else if (slave.devotion >= -20) {
				r += `You force yourself into ${his} butthole. ${He} sobs and cries with disgust while you continue thrusting into ${his} ass. ${He} fears ${his} next anal sex, remembering the pain of losing ${his} anal virginity. ${He} dreads having ${his} ass violated by you again.`;
				slave.trust -= 5;
			} else {
				r += `You force yourself into ${his} butthole. ${He} sobs and cries with disgust while you continue thrusting into ${his} ass. ${He} hates and fears you for violating ${his} virgin butt. ${He} dreads having ${his} ass fucked by you again.`;
				slave.trust -= 5;
				slave.devotion -= 5;
			}
			slave.anus = 1;
		}
		if (canDoAnal(slave)) {
			if (canImpreg(slave, V.PC)) {
				r += knockMeUp(slave, 10, 1, -1, 1);
			}
			seX(slave, "anal", V.PC, "penetrative", times);
		}
		return r;
	}

	/** call as VCheck.Vaginal()
	 * @param {number} [times=1] is how many times to increment the vaginal counts.
	 * @param {App.Entity.SlaveState} [slave=V.activeSlave]
	 * @returns {string}
	 */
	function VaginalVCheck(times = 1, slave = V.activeSlave) {
		let r = '';
		setScopedPronouns(slave);

		if (canDoVaginal(slave) && slave.vagina === 0) {
			r += `<span class="lime">This breaks in ${slave.slaveName}'s virgin pussy.</span> `;
			if (slave.devotion > 50 || slave.career === "a slave since birth") {
				r += `You ease yourself into ${his} pussy, since it's ${his} first time, then gradually speed up your thrusts while ${he} slowly learns to move ${his} hips along with you. ${He} moans loudly. `;
				if (slave.tankBaby === 2) {
					r += `${He} thinks of losing ${his} virginity to ${his} ${WrittenMaster(slave)} a necessity to be happy. ${He} expects ${his} pussy to be seeing a lot more attention in the future.`;
				} else {
					r += `<span class="hotpink">${He} enjoys losing ${his} cherry to you,</span> and `;
					if ((slave.fetishKnown && slave.fetish === "pregnancy") || (slave.energy > 95) || (slave.attrXY >= 85 && V.PC.dick > 0)) {
						r += `can't wait to have ${his} pussy fucked by you again.`;
					} else {
						r += `looks forward to having ${his} pussy fucked by you again.`;
					}
				}
				slave.devotion += 4;
			} else if (slave.devotion > 20) {
				r += `You ease yourself into ${his} pussy, since it's ${his} first time, then gradually speed up your thrusts while ${he} slowly learns to move ${his} hips along with you. ${He} accepts losing ${his} virginity to ${his} owner and ${he} looks forward to having ${his} pussy fucked by you again.`;
			} else if (slave.devotion >= -20) {
				r += `You force yourself into ${his} pussy. ${He} sobs and cries with disgust while you continue thrusting into ${his} fuck hole. ${He} hates losing ${his} virginity this way and fears the next time you'll conquer ${him}. ${He} dreads getting violated by you again.`;
				slave.trust -= 5;
				slave.devotion -= 5;
			} else {
				r += `You force yourself into ${his} pussy. ${He} sobs and cries with disgust while you continue working ${his} fuck hole. ${He} tries to struggle, but you only pound harder. ${He} hates and fears you for robbing ${his} of ${his} virginity. ${He} dreads getting fucked by you again.`;
				slave.trust -= 10;
				slave.devotion -= 15;
			}
			slave.vagina = 1;
		}
		if (canDoVaginal(slave)) {
			if (canImpreg(slave, V.PC)) {
				r += knockMeUp(slave, 10, 0, -1, 1);
			}
			seX(slave, "vaginal", V.PC, "penetrative", times);
		}
		return r;
	}

	/** call as VCheck.Both()
	 * @param {number} [analTimes=1] how many times to increment the anal counts, if there is no vagina available.
	 * @param {number} [bothTimes=1] how many times to increment both holes counts (usually it is half of analTimes).
	 * @param {App.Entity.SlaveState} [slave=V.activeSlave]
	 * @returns {string}
	 */
	function BothVCheck(analTimes = 1, bothTimes = 1, slave = V.activeSlave) {
		let r = '';
		setScopedPronouns(slave);

		if (canDoVaginal(slave)) {
			if (slave.vagina === 0) {
				if (canDoAnal(slave) && slave.anus === 0) {
					r += `<span class="lime">This breaks in ${slave.slaveName}'s virgin holes.</span> `;
					if (slave.devotion > 50 || slave.career === "a slave since birth") {
						r += `As it's ${his} first time, you ease yourself into ${his} pussy before gradually working your way into ${his} ass and alternate between the two holes while ${he} moans. `;
						if (slave.tankBaby === 2) {
							r += `${He} believes you taking ${his} virgin holes <span class="hotpink">will keep ${him} from suffering.</span> ${He} expects you to require ${his} holes as tribute again soon.`;
						} else {
							r += `<span class="hotpink">${He}'s so devoted ${he} enjoys being fucked in both holes for the first time.</span> ${He} looks forward to having ${his} holes fucked by you again.`;
						}
						slave.devotion += 4;
					} else if (slave.devotion <= 20) {
						r += `You force yourself into ${his} pussy before working your way into ${his} ass. ${He} sobs and cries with disgust while you alternate between the two holes. ${He} hates losing ${his} virginity and anal virginity in one brutal incident. ${He} dreads having ${his} holes violated by you again.`;
						slave.trust -= 5;
						slave.devotion -= 5;
					} else {
						r += `As it's ${his} first time, you ease yourself into ${his} pussy before gradually working your way into ${his} ass and alternate between the two holes while ${he} moans. ${He} accepts being fucked in both holes for the first time. ${He} looks forward to having ${his} holes fucked by you again.`;
					}
					slave.anus = 1;
				} else {
					r += `<span class="lime">This breaks in ${slave.slaveName}'s virgin pussy.</span> `;
					if (slave.devotion > 50 || slave.career === "a slave since birth") {
						r += `As it's ${his} first time, you ease yourself into ${his} pussy and gradually speed up your thrusts while ${he} slowly learns to move ${his} hips along with you. ${He} moans loudly. `;
						if (slave.tankBaby === 2) {
							r += `${He} thinks of losing ${his} virginity to ${his} ${WrittenMaster(slave)} a necessity to be happy. ${He} expects ${his} pussy to be seeing a lot more attention in the future.`;
						} else {
							r += `<span class="hotpink">${He} enjoys losing ${his} cherry to you.</span> ${He} looks forward to having ${his} pussy fucked by you again.`;
						}
						slave.devotion += 4;
					} else if (slave.devotion <= 20) {
						r += `You force yourself into ${his} pussy. ${He} sobs and cries with disgust while you continue working ${his} fuck hole. ${He} hates and fears you for taking ${his} virginity. ${He} dreads having ${his} pussy violated by you again.`;
						slave.trust -= 5;
						slave.devotion -= 5;
					} else {
						r += `As it's ${his} first time, you ease yourself into ${his} pussy before gradually increasing the intensity of your thrusts while ${he} softly moans. ${He} accepts losing ${his} virginity to ${his} owner and ${he} looks forward to having ${his} pussy fucked by you again.`;
					}
				}
				slave.vagina = 1;
			} else if (canDoAnal(slave) && slave.anus === 0) {
				r += `<span class="lime">This breaks in ${slave.slaveName}'s virgin ass.</span> `;
				if (slave.devotion > 50 || slave.career === "a slave since birth") {
					r += `As it's ${his} first time, you ease yourself into ${his} butthole and gradually speed up your thrusts while ${he} slowly learns to move ${his} hips along with you. ${He} moans loudly. `;
					if (slave.tankBaby === 2) {
						r += `${He} thinks of losing ${his} anal virginity to ${his} ${WrittenMaster(slave)} a <span class="hotpink">necessity.</span> ${He} expects ${his} asshole to be seeing a lot more attention now.`;
					} else {
						r += `${He} thinks of losing ${his} anal virginity to you as a connection with ${his} beloved ${WrittenMaster(slave)}. ${He} looks forward to having ${his} asshole fucked by you again.`;
					}
					slave.devotion += 4;
				} else if (slave.devotion > 20) {
					r += `As it's ${his} first time, you ease yourself into ${his} butthole and gradually increase the intensity of your thrusts. ${He} accepts the pain and humiliation of anal sex as part of ${his} sexual servitude, though ${he} hopes that ${his} next time will be less painful.`;
				} else if (slave.devotion >= -20) {
					r += `You force yourself into ${his} butthole. ${He} sobs and cries with disgust while you continue thrusting into ${his} ass. ${He} fears ${his} next anal sex, remembering the pain of losing ${his} anal virginity. ${He} dreads having ${his} ass violated by you again.`;
					slave.trust -= 5;
				} else {
					r += `You force yourself into ${his} butthole. ${He} sobs and cries with disgust while you continue thrusting into ${his} ass. ${He} hates and fears you for violating ${his} virgin butt. ${He} dreads having ${his} ass fucked by you again.`;
					slave.trust -= 5;
					slave.devotion -= 5;
				}
				slave.anus = 1;
			}
			if (canDoAnal(slave)) {
				seX(slave, "vaginal", V.PC, "penetrative", bothTimes);
				seX(slave, "anal", V.PC, "penetrative", bothTimes);
				if (canImpreg(slave, V.PC)) {
					r += knockMeUp(slave, 10, 2, -1, 1);
				}
			} else {
				seX(slave, "vaginal", V.PC, "penetrative", bothTimes);
				if (canImpreg(slave, V.PC)) {
					r += knockMeUp(slave, 10, 0, -1, 1);
				}
			}
		} else if (canDoAnal(slave)) {
			if (slave.anus === 0) {
				r += `<span class="lime">This breaks in ${slave.slaveName}'s virgin ass.</span> `;
				if (slave.devotion > 50 || slave.career === "a slave since birth") {
					r += `As it's ${his} first time, you ease yourself into ${his} butthole and gradually speed up your thrusts while ${he} slowly learns to move ${his} hips along with you. ${He} moans loudly. `;
					if (slave.tankBaby === 2) {
						r += `${He} thinks of losing ${his} anal virginity to ${his} ${WrittenMaster(slave)} a <span class="hotpink">necessity.</span> ${He} expects ${his} asshole to be seeing a lot more attention now.`;
					} else {
						r += `${He} thinks of losing ${his} anal virginity to you as a connection with ${his} beloved ${WrittenMaster(slave)}. ${He} looks forward to having ${his} asshole fucked by you again.`;
					}
					slave.devotion += 4;
				} else if (slave.devotion > 20) {
					r += `As it's ${his} first time, you ease yourself into ${his} butthole and gradually increase the intensity of your thrusts. ${He} accepts the pain and humiliation of anal sex as part of ${his} sexual servitude, though ${he} hopes that ${his} next time will be less painful.`;
				} else if (slave.devotion >= -20) {
					r += `You force yourself into ${his} butthole. ${He} sobs and cries with disgust while you continue thrusting into ${his} ass. ${He} fears ${his} next anal sex, remembering the pain of losing ${his} anal virginity. ${He} dreads having ${his} ass violated by you again.`;
					slave.trust -= 5;
				} else {
					r += `You force yourself into ${his} butthole. ${He} sobs and cries with disgust while you continue thrusting into ${his} ass. ${He} hates and fears you for violating ${his} virgin butt. ${He} dreads having ${his} ass fucked by you again.`;
					slave.trust -= 5;
					slave.devotion -= 5;
				}
				slave.anus = 1;
			}
			seX(slave, "anal", V.PC, "penetrative", analTimes);
			if (canImpreg(slave, V.PC)) {
				r += knockMeUp(slave, 10, 1, -1, 1);
			}
		}
		return r;
	}

	/** call as VCheck.Simple()
	 * @param {number} [times=1] how many times to increment either the Vaginal or the Anal counts, if there is no Vagina available.
	 * @param {App.Entity.SlaveState} [slave=V.activeSlave]
	 * @returns {string}
	 */
	function SimpleVCheck(times = 1, slave = V.activeSlave) {
		if (canDoVaginal(slave)) {
			return VaginalVCheck(times, slave);
		} else if (canDoAnal(slave)) {
			return AnalVCheck(times, slave);
		}
	}

	/* call as VCheck.Partner()
	Before using this function, set $partner to the index of the partner in the $slaves array
	analTimes is how many times to increment the Anal counts, if there is no Vagina available.
	bothTimes is how many times to increment both holes counts (usually it is half of Anal).
	In both cases if left undefined it will assume it to be 1.
	This also checks for a valid Vagina/Accessory, though most code I've seen does this already, you
	never know when someone might use the routine and forget to do such.
	*/
	function PartnerVCheck(analTimes = 1, bothTimes = 1) {
		const partner = V.slaves[V.partner];
		let r = '';
		if (partner === undefined) {
			return `<span class="red">PartnerVCheck called with invalid partner '${V.partner}' from passage ${passage()}.</span>`;
		}
		setScopedPronouns(partner);

		if (canDoVaginal(partner)) {
			if (partner.vagina === 0) {
				if (canDoAnal(partner) && partner.anus === 0) {
					r += `Since it's ${partner.slaveName}'s first time, you take your time and gently ease yourself into ${his} pussy before gradually working your way into ${his} butthole, alternating between ${his} holes. This breaks in ${partner.slaveName}'s virgin holes. `;
					partner.vagina = 1;
					partner.anus = 1;
				} else {
					r += `Since it's ${partner.slaveName}'s first time, you take your time and gently ease yourself into ${his} pussy before gradually increasing the intensity of your thrusts. This breaks in ${partner.slaveName}'s virgin pussy. `;
					partner.vagina = 1;
				}
			} else if (canDoAnal(partner) && partner.anus === 0) {
				r += `Since it's ${partner.slaveName}'s first time, you take your time and gently ease yourself into ${his} butthole before gradually increasing the intensity of your thrusts into ${his} ass. This breaks in ${partner.slaveName}'s virgin ass. `;
				partner.anus = 1;
			}

			if (canDoAnal(partner)) {
				actX(partner, "vaginal", bothTimes);
				actX(partner, "anal", bothTimes);
				if (canImpreg(partner, V.PC)) {
					r += knockMeUp(partner, 10, 2, -1);
				}
			} else {
				actX(partner, "vaginal", bothTimes);
				if (canImpreg(partner, V.PC)) {
					r += knockMeUp(partner, 10, 0, -1);
				}
			}
		} else if (canDoAnal(partner)) {
			if (partner.anus === 0) {
				r += `Since it's ${partner.slaveName}'s first time, you take your time and gently ease yourself into ${his} butthole before gradually increasing the intensity of your thrusts into ${his} ass. This breaks in ${partner.slaveName}'s virgin ass. `;
				partner.anus = 1;
			}
			actX(partner, "anal", analTimes);
			if (canImpreg(partner, V.PC)) {
				r += knockMeUp(partner, 10, 1, -1);
			}
		}
		return r;
	}
})();

globalThis.SimpleSexAct = (function() {
	"use strict";

	return {
		Player: SimpleSexActPlayer,
		Slave: SimpleSlaveFucking,
		Slaves: SimpleSlaveSlaveFucking,
	};

	/**
	 * fuckCount is how many times to increment either the Vaginal, Anal, or Oral counts, depending on availability of slave.
	 * If count is left undefined it will assume it to be 1.
	 * Intended to be a simple "I want to fuck x and not have to code a bunch of logic for it".
	 * @param {App.Entity.SlaveState} slave
	 * @param {number} [fuckCount=1]
	 * @returns {string}
	 */
	function SimpleSexActPlayer(slave, fuckCount = 1) {
		let fuckTarget = 0;
		let r = "";

		for (let i = 0; i < fuckCount; i++) {
			fuckTarget = jsRandom(1, 100);
			if (slave.nipples === "fuckable" && V.PC.dick > 0 && fuckTarget > 80) {
				actX(slave, "mammary");
			} else if (canDoVaginal(slave) && slave.vagina > 0 && fuckTarget > 33) {
				actX(slave, "vaginal");
				if (canImpreg(slave, V.PC)) {
					r += knockMeUp(slave, 10, 0, -1, 1);
				}
			} else if (canDoAnal(slave) && slave.anus > 0 && fuckTarget > 10) {
				actX(slave, "anal");
				if (canImpreg(slave, V.PC)) {
					r += knockMeUp(slave, 10, 1, -1, 1);
				}
			} else {
				actX(slave, "oral");
			}
		}
		return r;
	}

	/**
	 * count is how many times to increment either the Vaginal, Anal, or Oral counts, depending on availability of slave.
	 * If count is left undefined it will assume it to be 1.
	 * Intended to be a simple "x got fucked y times and I don't want to keep coding it".
	 * Pregnancy chance is handled in saLongTermEffects.tw.
	 * @param {App.Entity.SlaveState} slave
	 * @param {number} fuckCount
	 */
	function SimpleSlaveFucking(slave, fuckCount = 1) {
		let fuckTarget = 0;

		for (let i = 0; i < fuckCount; i++) {
			fuckTarget = jsRandom(1, 100);
			if (slave.nipples === "fuckable" && fuckTarget > 80) {
				actX(slave, "mammary");
			} else if (canDoVaginal(slave) && slave.vagina > 0 && fuckTarget > 33) {
				actX(slave, "vaginal");
			} else if (canDoAnal(slave) && slave.anus > 0 && fuckTarget > 10) {
				actX(slave, "anal");
			} else {
				actX(slave, "oral");
			}
		}
	}

	/**
	 * count is how many times to increment either the Vaginal, Anal, or Oral counts, depending on availability of slave.
	 * If count is left undefined it will assume it to be 1.
	 * Intended to be a simple "x got fucked y times by z and I don't want to keep coding it".
	 * @param {App.Entity.SlaveState} subSlave
	 * @param {App.Entity.SlaveState} domSlave
	 * @param {number} fuckCount
	 * @returns {string}
	 */
	function SimpleSlaveSlaveFucking(subSlave, domSlave, fuckCount = 1) {
		let fuckTarget = 0;
		let r = "";
		let slave1ID;
		let slave2ID;
		let sex = "";

		for (let j = 0; j < fuckCount; j++) {
		// there is a reason randomization happens inside cycle - to spread fuck around, otherwise cycle isn't even needed
			fuckTarget = jsRandom(1, 100);
			if (subSlave.nipples === "fuckable" && canPenetrate(domSlave) && fuckTarget > 80) {
				sex = "mammary";
				if (passage() === "SA serve your other slaves") {
					if (subSlave.ID === V.slaves[V.i].ID) {
						slave1ID = V.slaves[V.i].ID;
						slave2ID = domSlave.ID;
					} else {
						slave1ID = subSlave.ID;
						slave2ID = V.slaves[V.i].ID;
					}
				} else {
					slave1ID = subSlave.ID;
					slave2ID = domSlave.ID;
				}
			} else if (canDoVaginal(subSlave) && subSlave.vagina > 0 && canPenetrate(domSlave) && fuckTarget > 33) {
				sex = "vaginal";
				if (passage() === "SA serve your other slaves") {
					if (subSlave.ID === V.slaves[V.i].ID) {
						slave1ID = V.slaves[V.i].ID;
						slave2ID = domSlave.ID;
					} else {
						slave1ID = subSlave.ID;
						slave2ID = V.slaves[V.i].ID;
					}
				} else {
					slave1ID = subSlave.ID;
					slave2ID = domSlave.ID;
				}
				if (canImpreg(subSlave, domSlave)) {
					r += knockMeUp(subSlave, 3, 0, domSlave.ID, 1);
				}
			} else if (canDoAnal(subSlave) && subSlave.anus > 0 && canPenetrate(domSlave) && fuckTarget > 10) {
				// i think would impregnate from anal here even without .mpreg? same in original widget too
				sex = "anal";
				if (canImpreg(subSlave, domSlave) && subSlave.mpreg === 1) {
					r += knockMeUp(subSlave, 3, 1, domSlave.ID, 1);
				}
				if (passage() === "SA serve your other slaves") {
					if (subSlave.ID === V.slaves[V.i].ID) {
						slave1ID = V.slaves[V.i].ID;
						slave2ID = domSlave.ID;
					} else {
						slave1ID = subSlave.ID;
						slave2ID = V.slaves[V.i].ID;
					}
				} else {
					slave1ID = subSlave.ID;
					slave2ID = domSlave.ID;
				}
			} else {
				actX(subSlave, "oral");
			}
			if (typeof slave1ID === Number && typeof slave2ID === Number) {
				seX(V.slaves[slave1ID], sex, V.slaves[slave2ID], "penetrative");
			}
		}
		return r;
	}
})();

/**
 * Increments a slave's personal counter and the global counter for a particular action.
 * @param {App.Entity.SlaveState} slave
 * @param {string} act oral, anal, etc
 * @param {number} count
 */
globalThis.actX = function(slave, act, count = 1) {
	switch (act) {
		case "PCChildrenFathered":
			break;
		case "PCKnockedUp":
			break;
		case "anal":
			V.analTotal += count;
			break;
		case "births":
			V.birthsTotal += count;
			break;
		case "birthsTotal":
			break;
		case "cum":
			V.cumTotal += count;
			break;
		case "laborCount":
			break;
		case "mammary":
			V.mammaryTotal += count;
			break;
		case "milk":
			V.milkTotal += count;
			break;
		case "oral":
			V.oralTotal += count;
			break;
		case "penetrative":
			V.penetrativeTotal += count;
			break;
		case "pitKills":
			V.pitKillsTotal += count;
			break;
		case "publicUse":
			break;
		case "slavesFathered":
			break;
		case "slavesKnockedUp":
			break;
		case "vaginal":
			V.vaginalTotal += count;
			break;
		default:
			// Act was likely entered incorrectly.
			return;
	}
	if (act === "birth") { // Annoyingly at the moment, V.birthsTotal means all the births in your arc. Within the slave counter though, .births is all births in your arc and .birthsTotal includes births outside your arch.
		slave.counter.birthsTotal += count;
	}
	slave.counter[act] += count;
};

/**
 * Sex is between two. This is a handy wrapper for actX that emphasizes that.
 * @param {App.Entity.SlaveState} slave1 always a slave
 * @param {string} act1 oral, anal, etc
 * @param {App.Entity.SlaveState | string} slave2 slave or PC or "public"
 * @param {string} act2 oral, anal, etc
 * @param {number} count
 */
globalThis.seX = function(slave1, act1, slave2, act2, count = 1) {
	// Slave 1 does their normal thing
	actX(slave1, act1, count);

	// Slave 2 does their normal thing. If "Slave 2" is the public, then increment the public counter for slave 1 instead.
	if (slave2 === "public") {
		actX(slave1, "publicUse", count);
	} else if (typeof slave2 === 'string') {
		// someday we may track "slaves" and "assistant"
	} else {
		actX(slave2, act2, count);
	}
};