code:
globalThis.generateRelatedSlave = (function() {
	let sourceID;

	/**
	 * Generate a very similar relative for an existing slave (for use in Household Liquidators, for example).
	 * @param {App.Entity.SlaveState} slave - the source relative. Note: this slave is NOT changed, calling code is responsible for setting up the source end of the relationship!
	 * @param {string} relationship - the relationship that the new relative has with the source. Currently supports "parent", "child", "older sibling", "younger sibling", "twin", and applicable gender-specific variants of those (i.e. mother/father, daughter/son, older/younger brother/sister).
	 * @param {boolean} oppositeSex - set to true if the new relative should be the opposite sex of the old one (otherwise it will be the same sex).  will be ignored if gender is implied by relationship.
	 * @returns {App.Entity.SlaveState} - new relative
	 */
	function generateRelative(slave, relationship, oppositeSex=false) {
		let relative = prepareClone(slave);

		({relationship, oppositeSex} = interpretGenderSpecificRelationship(slave, relationship, oppositeSex));
		if (relationship === "twin") {
			makeTwin(relative);
		} else if (relationship === "child") {
			makeChild(relative, slave.genes);
		} else if (relationship === "parent") {
			makeParent(relative);
		} else if (relationship === "younger sibling") {
			makeYoungerSibling(relative);
		} else if (relationship === "older sibling") {
			makeOlderSibling(relative);
		} else {
			throw(`Unknown relationship: ${relationship}`);
		}
		if (oppositeSex) {
			if (slave.genes === "XX") {
				changeSexToXY(relative);
			} else if (slave.genes === "XY") {
				changeSexToXX(relative);
			} else {
				// we'll assume futa are their own opposites and don't need tweaking
			}
		}
		generateGivenName(relative); // must happen *after* any possible sex change
		// perform age-related adjustment for all relatives *except* same-sex twins (preserve identicality)
		if (relative.actualAge !== slave.actualAge || oppositeSex) {
			ageFixup(relative);
		}
		setHealth(slave, slave.health.condition);

		return relative;
	}

	/**
	 * Split a gender-specific relationship into a gender-neutral relationship and a base-slave-relative gender toggle
	 * @param {App.Entity.SlaveState} slave
	 * @param {string} relationship
	 * @param {boolean} oppositeSex - original value of oppositeSex (will be copied unchanged if not overridden)
	 * @returns {{relationship: string, oppositeSex: boolean}}
	 */
	function interpretGenderSpecificRelationship(slave, relationship, oppositeSex) {
		/** @param {string} genes - expected genes for this case
		 * @returns {boolean} - whether genes match slave or not */
		function isOppositeSex(genes) { return slave.genes !== genes; }

		if (relationship === "daughter") {
			return {relationship: "child", oppositeSex: isOppositeSex("XX")};
		} else if (relationship === "son") {
			return {relationship: "child", oppositeSex: isOppositeSex("XY")};
		} else if (relationship === "mother") {
			return {relationship: "parent", oppositeSex: isOppositeSex("XX")};
		} else if (relationship === "father") {
			return {relationship: "parent", oppositeSex: isOppositeSex("XY")};
		} else {
			const bits = relationship.split(" ");
			if (bits.length === 2) {
				if (bits[0] === "younger" || bits[0] === "older") {
					if (bits[1] === "sister") {
						return {relationship: `${bits[0]} sibling`, oppositeSex: isOppositeSex("XX")};
					} else if (bits[1] === "brother") {
						return {relationship: `${bits[0]} sibling`, oppositeSex: isOppositeSex("XY")};
					}
				}
			}
		}
		return {relationship: relationship, oppositeSex: oppositeSex};
	}

	/**
	 * Clone the original slave and do some common preparations to it.
	 * @param {App.Entity.SlaveState} slave - the source relative
	 * @returns {App.Entity.SlaveState} - the new relative
	 */
	function prepareClone(slave) {
		let relative = clone(slave);

		// regenerate accent
		nationalityToAccent(relative);

		// fuzz trust/devotion
		relative.devotion += random(-5, 5);
		relative.oldDevotion = relative.devotion;
		relative.trust += random(-5, 5);
		relative.oldTrust = relative.trust;

		// fuzz attraction and energy
		relative.attrXX += random(-20, 20);
		relative.attrXX = Math.clamp(relative.attrXX, 0, 100);
		relative.attrXY += random(-20, 20);
		relative.attrXY = Math.clamp(relative.attrXX, 0, 100);
		relative.energy += random(-20, 20);

		// store old ID and set the new ID
		sourceID = slave.ID;
		relative.ID = generateSlaveID();

		return relative;
	}

	/**
	 * Generate a new given name for the slave (keeping the surname).
	 * @param {App.Entity.SlaveState} slave - the new relative to be renamed
	 */
	function generateGivenName(slave) {
		const surname = slave.slaveSurname;
		const birthSurname = slave.birthSurname;
		nationalityToName(slave);
		slave.slaveSurname = surname;
		slave.birthSurname = birthSurname;
	}

	/**
	 * Finish configuring an identical twin
	 * @param {App.Entity.SlaveState} slave - the new twin
	 */
	function makeTwin(slave) {
		/* twins are physically identical, change only mental traits. */
		randomiseFetishFlaws(slave);
	}

	/**
	 * Finish configuring a sibling
	 * @param {App.Entity.SlaveState} slave - the new sibling
	 */
	function makeYoungerSibling(slave) {
		// reduce age
		slave.actualAge -= random(2, 6);
		slave.actualAge = Math.max(slave.actualAge, V.minimumSlaveAge);
		slave.visualAge = slave.actualAge;
		slave.physicalAge = slave.actualAge;
		slave.ovaryAge = slave.actualAge;
		slave.birthWeek = random(0, 51);

		fuzzPhysicalTraits(slave);

		randomiseFetishFlaws(slave);
	}

	/**
	 * Finish configuring a sibling
	 * @param {App.Entity.SlaveState} slave - the new sibling
	 */
	function makeOlderSibling(slave) {
		// increase age
		const maxDifference = (V.retirementAge - 1) - slave.actualAge;
		const ageDifference = Math.min(random(2, 6), maxDifference);
		fastForward(slave, ageDifference);
		slave.birthWeek = random(0, 51);

		fuzzPhysicalTraits(slave);

		randomiseFetishFlaws(slave);
	}

	/**
	 * Finish configuring a child
	 * @param {App.Entity.SlaveState} slave - the new child
	 * @param {string} parentSex - the sex of the parent
	 */
	function makeChild(slave, parentSex) {
		slave.mother = parentSex === "XX" ? sourceID : 0;
		slave.father = parentSex !== "XX" ? sourceID : 0;

		// select age
		const parentAge = slave.actualAge;
		let maxAge = Math.min(22, Math.max(V.minimumSlaveAge, parentAge - 11));
		let minAge = Math.min(Math.max(8, V.minimumSlaveAge), maxAge);
		if (V.pedo_mode === 1) {
			minAge = V.minimumSlaveAge;
		}
		slave.actualAge = random(minAge, maxAge);
		slave.visualAge = slave.actualAge;
		slave.physicalAge = slave.actualAge;
		slave.ovaryAge = slave.actualAge;
		slave.birthWeek = random(0, 51);

		// child always has less devotion/trust
		slave.devotion -= 10;
		slave.trust -= 10;

		// child always has less boobs/butt than mother
		if (parentSex === "XX") {
			slave.boobs -= 100;
			slave.butt -= 1;
		}

		fuzzPhysicalTraits(slave);

		// daughter has never had children and is likely a virgin
		if (slave.genes === "XX") {
			slave.vagina = either(0, 0, 0, 1);
			slave.counter.birthsTotal = 0;
		}

		randomiseFetishFlaws(slave);
	}

	/**
	 * Finish configuring a parent
	 * @param {App.Entity.SlaveState} slave - the new parent
	 */
	function makeParent(slave) {
		slave.mother = 0;
		slave.father = 0;

		// select age
		const childAge = slave.actualAge;
		let minAge = childAge + Math.max(11, V.minimumSlaveAge - 2);
		let maxAge = Math.min(V.retirementAge - 1, childAge + 42);
		if (maxAge < minAge) {
			throw "Cannot generate parent (slave too old)";
		}
		const targetAge = random(minAge, maxAge);
		fastForward(slave, targetAge - childAge);
		slave.birthWeek = random(0, 51);

		// parent always has less devotion/trust
		slave.devotion -= 10;
		slave.trust -= 10;

		// mother always has more boobs/butt
		if (slave.genes === "XX") {
			slave.boobs += 100;
			slave.butt += 1;
		}

		fuzzPhysicalTraits(slave);

		// mother has had one child (at least)
		if (slave.genes === "XX") {
			slave.vagina = Math.max(slave.vagina, 1);
			slave.counter.birthsTotal = 1;
		}

		randomiseFetishFlaws(slave);
	}

	/**
	 * Fuzz some physical traits so we don't start out identical
	 * @param {App.Entity.SlaveState} slave
	 */
	function fuzzPhysicalTraits(slave) {
		// fuzz boobs/butt
		if (slave.boobs > 200) {
			slave.boobs += either(-100, 0, 100);
		}
		if (slave.butt > 1) {
			slave.butt += random(-1, 1);
		}
	}

	/**
	 * Randomize fetish and flaws
	 * @param {App.Entity.SlaveState} slave
	 */
	function randomiseFetishFlaws(slave) {
		slave.fetishStrength = random(0, 90);
		slave.fetish = either("buttslut", "cumslut", "dom", "humiliation", "masochist", "none", "none", "none", "none", "none", "none", "pregnancy", "sadist", "submissive");
		slave.behavioralFlaw = either("anorexic", "arrogant", "bitchy", "devout", "gluttonous", "hates men", "hates women", "hates women", "liberated", "masochist", "none", "none", "none", "odd");
		if (slave.behavioralFlaw === "devout") {
			slave.sexualFlaw = either("apathetic", "none", "repressed", "shamefast");
		} else {
			slave.sexualFlaw = either("apathetic", "crude", "hates anal", "hates oral", "hates penetration", "idealistic", "judgemental", "none", "none", "none", "none", "repressed", "shamefast");
		}
		randomizeAttraction(slave);
	}

	/**
	 * Fix age-related factors including physical immaturity, height, pregnancy, and health
	 * Must be after age is recomputed, obviously; should not be needed for twins
	 * @param {App.Entity.SlaveState} slave - the new relative
	 */
	function ageFixup(slave) {
		// adjust for age
		if (slave.physicalAge <= 15) {
			ageAdjustYoungRelative(slave);
		}
		slave.height = Math.trunc(Height.random(slave));

		// reset pregnancy
		WombFlush(slave);
		if (V.arcologies[0].FSRepopulationFocusSMR === 1 && canGetPregnant(slave)) {
			slave.preg = random(1, 38);
			slave.pregWeek = slave.preg;
			slave.pregKnown = 1;
			slave.pregType = setPregType(slave);
			if (slave.vagina === 0) {
				slave.vagina = 1;
			}
		}
		SetBellySize(slave);

		// reset lactation
		slave.lactation = 0;
		slave.lactationDuration = 0;
	}

	/**
	 * When generating a younger relative by cloning an older one (for example, for Household Liquidators),
	 * clamp certain physical parameters of the younger relative appropriately for their physical age.
	 * Generally these adjustments should match the age limiters found in generateNewSlave.js.
	 * @param {App.Entity.SlaveState} slave - the slave to adjust
	 */
	function ageAdjustYoungRelative(slave) {
		/* breast size */
		const origBoobs = slave.boobs;
		if (slave.physicalAge <= 10) {
			slave.boobs = Math.clamp(slave.boobs, 0, 100);
		} else if (slave.physicalAge <= 12) {
			slave.boobs = Math.clamp(slave.boobs, 0, 300);
		} else if (slave.physicalAge <= 14) {
			slave.boobs = Math.clamp(slave.boobs, 0, 400);
		} else if (slave.physicalAge <= 15) {
			slave.boobs = Math.clamp(slave.boobs, 0, 450);
		}

		/* if we've reduced breast size because of age, reapply minimum weight modifiers */
		if (origBoobs > slave.boobs && V.weightAffectsAssets !== 0) {
			if (slave.weight > 190) {
				slave.boobs += 300;
			} else if (slave.weight > 160) {
				slave.boobs += 200;
			} else if (slave.weight > 30) {
				slave.boobs += 100;
			}
		}

		/* if we've managed to *increase* breast size, just put it back */
		if (origBoobs < slave.boobs) {
			slave.boobs = origBoobs;
		}

		/* breast shape - preserve if it would have been valid, otherwise reset to normal (don't reroll) */
		const AllowedBoobShapes = [];
		if (slave.boobs > 250 && slave.boobs < 800) {
			AllowedBoobShapes.push("perky");
			AllowedBoobShapes.push("downward-facing");
		}
		if (slave.boobs > 400 && slave.boobs < 1200) {
			AllowedBoobShapes.push("torpedo-shaped");
			AllowedBoobShapes.push("wide-set");
		}
		if (!AllowedBoobShapes.includes(slave.boobShape)) {
			slave.boobShape = "normal";
		}

		/* voice */
		if (slave.physicalAge <= 16 && slave.voice <= 1) {
			slave.voice = 2;
		}

		/* XX genitals */
		if (slave.physicalAge < 20 && slave.vagina > 1) {
			slave.vagina = 1;
		}

		if (slave.physicalAge <= 13 && slave.clit > 1) {
			slave.clit = 1;
		}

		if (slave.physicalAge <= 13 && slave.labia > 1) {
			slave.labia = 1;
		} else if (slave.physicalAge <= 15 && slave.labia > 2) {
			slave.labia = 2;
		}

		/* XY genitals */
		if (slave.physicalAge <= 13) {
			if (slave.geneticQuirks.wellHung === 2 && slave.physicalAge >= 8 && slave.dick > 4) {
				slave.dick = 4;
			} else if (slave.dick > 3) {
				slave.dick = 3;
			}
			if (slave.balls > 3) {
				slave.balls = 3;
				slave.scrotum = slave.balls;
			}
		} else if (slave.physicalAge <= 15) {
			if (slave.geneticQuirks.wellHung === 2 && slave.dick > 5) {
				slave.dick = 5;
			} else if (slave.dick > 3) {
				slave.dick = 3;
			}
			if (slave.balls > 4) {
				slave.balls = 4;
				slave.scrotum = slave.balls;
			}
		}

		/* teeth */
		if (slave.physicalAge < 6) {
			slave.teeth = "baby";
		} else if (slave.physicalAge < 12) {
			slave.teeth = "mixed";
		}

		/* reset puberty status */
		generatePuberty(slave);
	}

	function fastForward(slave, years) {
		for (let i = 0; i < years; ++i) {
			ageSlave(slave, true);
		}
	}

	/**
	 * Give a slave a realistic chance to activate a sexlinked genetic quirk which her opposite-sex relative was only a carrier for.
	 * @param {App.Entity.SlaveState} slave - the slave to adjust
	 * @param {string} quirk - the sex-linked quirk to test
	 */
	function activateSexlinkedGeneticQuirk(slave, quirk) {
		if (slave.geneticQuirks[quirk] === 1) {
			if (random(1, 4) > 3) {
				slave.geneticQuirks[quirk] = 2;
			}
		}
	}

	/**
	 * Make a slave a carrier for a genetic sexlinked quirk which her opposite-sex relative had active.
	 * @param {App.Entity.SlaveState} slave - the slave to adjust
	 * @param {string} quirk - the sex-linked quirk to test
	 */
	function deactivateSexlinkedGeneticQuirk(slave, quirk) {
		if (slave.geneticQuirks[quirk] === 2) {
			slave.geneticQuirks[quirk] = 1;
		}
	}

	/**
	 * Changes the new relative's sex from XY to XX.
	 * @param {App.Entity.SlaveState} slave - the slave to adjust
	 */
	function changeSexToXX(slave) {
		slave.genes = "XX";
		slave.pronoun = App.Data.Pronouns.Kind.female;

		// activate/deactivate sexlinked genetic quirks
		["wellHung"].forEach((q) => deactivateSexlinkedGeneticQuirk(slave, q));
		["fertility", "hyperFertility", "macromastia", "gigantomastia"].forEach((q) => activateSexlinkedGeneticQuirk(slave, q));

		// alter body proportions
		slave.hips = Math.clamp(slave.hips + 1, -2, 2);
		slave.butt++;
		slave.waist = Math.clamp(slave.waist - random(15, 25), -100, 100);
		slave.face = Math.clamp(slave.face + random(15, 25), -100, 100);
		slave.boobs = either(200, 300, 400, 450, 500, 550, 600, 700);

		// alter genitals
		slave.dick = 0;
		slave.balls = 0;
		slave.scrotum = 0;
		slave.prostate = 0;
		slave.vagina = 1; // no virgins here?
		slave.clit = either(0, 0, 0, 0, 0, 0, 1, 1, 2);
		slave.labia = either(0, 0, 0, 1, 1, 1, 1, 2, 2, 3);
		slave.ovaries = 1;
		slave.preg = -1; // might get pregnant from repop check later on anyway

		// swap attraction
		[slave.attrXX, slave.attrXY] = [slave.attrXY, slave.attrXX];

		// adjust voice and hair
		slave.voice = Math.min(3, slave.voice + 1);
		slave.hLength = 60;

		// rotate hormone balance around a center of 10 (60 becomes -40 and vice versa)
		slave.hormoneBalance -= (slave.hormoneBalance - 10) * 2;

		// regenerate piercings (would be nice to just call generateXXMods here)
		slave.earPiercing = jsEither([0, 1]);
		slave.nosePiercing = jsEither([0, 0, 0, 1]);
		slave.eyebrowPiercing = jsEither([0, 0, 0, 0, 0, 1]);
		slave.clitPiercing = jsEither([0, 0, 0, 0, 0, 1]);
		slave.dickPiercing = 0;
		slave.lipsPiercing = jsEither([0, 0, 0, 0, 0, 1]);
		slave.navelPiercing = jsEither([0, 0, 0, 1]);
		slave.nipplesPiercing = jsEither([0, 0, 0, 0, 1]);
	}

	/**
	 * Changes the new relative's sex from XX to XY.
	 * @param {App.Entity.SlaveState} slave - the slave to adjust
	 */
	function changeSexToXY(slave) {
		slave.genes = "XY";
		slave.pronoun = App.Data.Pronouns.Kind.male;

		// activate/deactivate sexlinked genetic quirks
		["wellHung"].forEach((q) => activateSexlinkedGeneticQuirk(slave, q));
		["fertility", "hyperFertility", "macromastia", "gigantomastia"].forEach((q) => deactivateSexlinkedGeneticQuirk(slave, q));

		// alter body proportions
		slave.hips = Math.clamp(slave.hips - 1, -2, 2);
		slave.butt = Math.max(0, slave.butt - 1);
		slave.waist = Math.clamp(slave.waist + random(15, 25), -100, 100);
		slave.face = Math.clamp(slave.face - random(15, 25), -100, 100);
		slave.boobs = either(100, 200);

		// alter genitals
		slave.vagina = -1;
		slave.clit = 0;
		slave.labia = 0;
		slave.ovaries = 0;
		slave.preg = -2;
		if (slave.geneticQuirks.wellHung === 2) {
			slave.dick = either(5, 5, 6);
		} else {
			slave.dick = either(1, 2, 2, 2, 3, 3, 3, 4, 4, 5);
		}
		slave.balls = either(1, 2, 2, 2, 3, 3, 3, 4, 4, 5);
		slave.scrotum = slave.balls;
		slave.prostate = 1;

		// swap attraction
		[slave.attrXX, slave.attrXY] = [slave.attrXY, slave.attrXX];

		// adjust voice and hair
		slave.voice = Math.max(1, slave.voice - 1);
		slave.hLength = 10;

		// rotate hormone balance around a center of 10 (60 becomes -40 and vice versa)
		slave.hormoneBalance -= (slave.hormoneBalance - 10) * 2;

		// regenerate piercings (would be nice to just call generateXYMods here)
		slave.earPiercing = jsEither([0, 0, 0, 1]);
		slave.nosePiercing = jsEither([0, 0, 0, 0, 1]);
		slave.eyebrowPiercing = jsEither([0, 0, 0, 0, 0, 1]);
		slave.clitPiercing = jsEither([0, 0, 0, 0, 0, 1]);
		slave.lipsPiercing = jsEither([0, 0, 0, 0, 0, 1]);
		slave.navelPiercing = jsEither([0, 0, 0, 0, 1]);
		slave.nipplesPiercing = jsEither([0, 0, 0, 0, 1]);
	}

	return generateRelative;
})();

repo_name: KnightBoulegard/fc

license: mit


code:
/*
* <<htag>> macro
* A simple macro which allows to create wrapping html elements with dynamic IDs.
*	idea blatantly robbed from the spanMacroJS.tw but expanded to a more generic
*	case, allowing <div>, <button> or whatever you want. elements, default is for
*	the div though. In addition, you can pass an object in as the first argument
*	instead of an id, and each of the object's attributes will become attributes
*	of the generate tag.
*
* Usage: <<htag id>>...<</htag>>
* Usage: <<htag id tag>>...<</htag>>
* Usage: <<htag attributes>>...<</htag>>
* Usage: <<htag attributes tag>>...<</htag>>
*/
Macro.add('htag', {
	tags: null,
	handler() {
		const payload = this.payload[0].contents.replace(/(^\n+|\n+$)/, '');
		let htag = 'div';
		let attributes;

		function munge(val, key) {
			return `${key}="${val}"`;
		}

		if (this.args.length === 0) { return this.error('invalid syntax, format: <<htag [id [ tag ] | attributes [ tag ] >>'); }
		if (this.args.length > 1) { htag = String(this.args[1]).trim(); }
		if (typeof this.args[0] === "object") {
			attributes = $.map(this.args[0], munge).join(" ");
		} else {
			attributes = `id="${String(this.args[0]).trim()}"`;
		}
		if (Config.debug) {
			this.debugView.modes({
				block: true
			});
		}

		jQuery(`<${htag} ${attributes} />`)
			.wiki(payload)
			.appendTo(this.output);
	}
});


code:
/**
 * checks for illness and reduces the effectiveness of the slave accordingly -- 10, 25, 50, 85, 95% reduced
 * also checks for tiredness and reduces effectiveness from that as well -- 25% reduction at 90+ and 10% at 60-90
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
globalThis.healthPenalty = function(slave) {
	const H = slave.health;
	let penalty = 100;
	if (slave.assignment !== Job.GLORYHOLE) {
		if (H.illness > 0) {
			penalty *= (100 - Math.min((Math.pow(H.illness, 2) * 5 + 5), 95)) / 100;
		}
		if (H.tired > 90) {
			penalty *= 0.75;
		} else if (H.tired > 60) {
			penalty *= 0.90;
		}
	}
	penalty = Math.trunc(penalty) / 100;
	return penalty;
};

/**
 * All things hurting a slave go through this to update short term damage and slave health.
 * @param {App.Entity.SlaveState} slave
 * @param {number} damage
 * @returns {void}
 */
globalThis.healthDamage = function(slave, damage) {
	const H = slave.health;
	damage = Math.max(Math.trunc(damage), 0);
	H.shortDamage += damage;
	H.condition -= damage;
	updateHealth(slave);
};

/**
 * All things directly curing wounds on a slave go through this to update short term damage and slave health.
 * Use sparingly and for direct medical treatment by qualified professionals only; improveCondition should be used instead for drugs, natural healing, etc.
 * @param {App.Entity.SlaveState} slave
 * @param {number} cure
 * @returns {void}
 */
globalThis.healthCure = function(slave, cure) {
	const H = slave.health;
	cure = Math.max(Math.trunc(cure), 0);
	if (cure > H.shortDamage) {
		cure = H.shortDamage;
	}
	H.shortDamage -= cure;
	H.condition += cure;
	updateHealth(slave);
};

/**
 * Surgical procedures also depend on the PC's medicine skill
 * @param {App.Entity.SlaveState} slave
 * @param {number} damage
 * @returns {void}
 */
globalThis.surgeryDamage = function(slave, damage) {
	const playerSkillFactor = 1 + Math.clamp(Math.pow(V.PC.skill.medicine / 100, 2), 0, 1);
	healthDamage(slave, Math.trunc(damage / playerSkillFactor));
};

/**
 * All things improving a slave's condition go through this to update condition and slave health.
 * @param {App.Entity.SlaveState} slave
 * @param {number} condition
 * @returns {void}
 */
globalThis.improveCondition = function(slave, condition) {
	const H = slave.health;
	H.condition += Math.max(Math.trunc(condition), 0);
	updateHealth(slave);
};

/**
 * Updates slave.health.health
 * @param {App.Entity.SlaveState} slave
 * @returns {void}
 */
globalThis.updateHealth = function(slave) {
	const H = slave.health;
	const condition = H.condition;

	// Converting the other variables to better fit the same scale as .condition
	const damage = (40 - H.shortDamage - H.longDamage) * 2.5; // 100 / -inf
	const tired = (40 - H.tired) * 2.5; // 100 / -150
	const illness = (5 - Math.pow(H.illness, 2)) * 20; // 100 / -400

	// Assigning weights to the different components and aggregating
	H.health = condition * 0.6 + damage * 0.2 + tired * 0.1 - illness * 0.1;
};

/**
 * A function for setting valid health values for new slaves, comes with a set of defaults.
 * Not meant for use with existing slaves (consider healthDamage(), surgeryDamage() and improveCondition() first), but sometimes useful still.
 * @param {App.Entity.SlaveState} slave
 * @param {number} condition
 * @param {number} [shortDamage]
 * @param {number} [longDamage]
 * @param {number} [illness]
 * @param {number} [tired]
 * @returns {void}
 */
globalThis.setHealth = function(slave, condition = -101, shortDamage = -1, longDamage = -1, illness = -1, tired = -1) {
	const H = slave.health;

	// Making sure all values get set to either a default or their desired value
	if (condition < -100) {
		H.condition = jsRandom(-50, 50); // Default value
	} else {
		H.condition = condition;
	}
	if (shortDamage < 0) {
		H.shortDamage = Math.max(normalRandInt(0, 3), 0);
	} else {
		H.shortDamage = shortDamage;
	}
	if (longDamage < 0) {
		H.longDamage = Math.max(normalRandInt(0, 3), 0);
	} else {
		H.longDamage = longDamage;
	}
	if (illness < 0 || illness > 5) {
		H.illness = Math.max(normalRandInt(0, 0.5), 0);
	} else {
		H.illness = illness;
	}
	if (tired < 0 || tired > 100) {
		H.tired = jsRandom(20, 50);
	} else {
		H.tired = tired;
	}

	// Adjusting long term damage for age
	if (slave.physicalAge > 29) {
		for (let i = 1; i < (slave.physicalAge - 29); i++) {
			H.longDamage += Math.trunc((i + 4 + jsRandom(1, 15)) / 20);
		}
	}

	// Making sure their overall health doesn't go too low (and would lead to a very likely death)
	if (H.condition - H.shortDamage - H.longDamage < -100) {
		let excess = -100 + H.condition + H.shortDamage + H.longDamage;
		if (H.shortDamage + H.longDamage < 50) { // As long as the damage doesn't get too crazy we'll compensate condition for it
			H.condition += excess;
		} else if (H.longDamage < 50) { // As long as long damage doesn't get too high we'll compensate by improving shortDamage and condition
			if (H.shortDamage - Math.ceil(excess / 2) > 0) {
				H.shortDamage -= Math.ceil(excess / 2);
				H.condition += Math.ceil(excess / 2);
			} else {
				excess -= H.shortDamage;
				H.shortDamage = 0;
				H.condition += excess;
			}
		} else { // If such high longDamage was intentional, they really should have done better basic math to ensure these slaves wouldn't just die immediately
			if (H.shortDamage - Math.ceil(excess / 3) > 0 && H.longDamage - Math.ceil(excess / 3) > 0) {
				H.longDamage -= Math.ceil(excess / 3);
				H.shortDamage -= Math.ceil(excess / 3);
				H.condition += Math.ceil(excess / 3);
			} else {
				excess -= H.shortDamage;
				H.shortDamage = 0;
				if (H.longDamage - Math.ceil(excess / 2) > 0) {
					H.longDamage -= Math.ceil(excess / 2);
					H.condition += Math.ceil(excess / 2);
				} else {
					excess -= H.longDamage;
					H.longDamage = 0;
					H.condition += excess;
				}
			}
		}
	}

	// Finally giving slaves their overall health value
	H.health = H.condition - H.shortDamage - H.longDamage;
};


code:
/** creates an array from App.Data.HeroSlaves that will be similar to the old $heroSlaves.
 * @returns {Array}
 */
App.Utils.buildHeroArray = function() {
	let array;
	$.wiki(`<<include "custom Slaves Database">>`);
	if (V.seeExtreme === 1) {
		array = App.Data.HeroSlaves.D.concat(
			App.Data.HeroSlaves.DD,
			App.Data.HeroSlaves.DF,
			App.Data.HeroSlaves.Dextreme,
			App.Data.HeroSlaves.DDextreme,
			App.Data.HeroSlaves.DFextreme,
			V.heroSlaves
		);
	} else {
		array = App.Data.HeroSlaves.D.concat(
			App.Data.HeroSlaves.DD,
			App.Data.HeroSlaves.DF,
			V.heroSlaves
		);
	}
	delete V.heroSlaves;

	for (let hero = 0; hero < array.length; hero++) {
		if (V.seePreg !== 1 && [900089, 900102].includes(array[hero].ID)) {
			array.splice(hero, 1);
			hero--;
			continue;
		}
		if (V.heroSlavesPurchased.includes(array[hero].ID)) {
			array.splice(hero, 1);
			hero--;
			continue;
		}
	}
	return array;
};

/**
 * @param {App.Entity.SlaveState} heroSlave
 * @returns {App.Entity.SlaveState}
 */
App.Utils.getHeroSlave = function(heroSlave) {
	function repairLimbs(slave) {
		if (slave.hasOwnProperty("removedLimbs")) {
			if (slave.removedLimbs[0] === 1) {
				removeLimbs(slave, "left arm");
			}
			if (slave.removedLimbs[1] === 1) {
				removeLimbs(slave, "right arm");
			}
			if (slave.removedLimbs[2] === 1) {
				removeLimbs(slave, "left leg");
			}
			if (slave.removedLimbs[3] === 1) {
				removeLimbs(slave, "right leg");
			}
			delete slave.removedLimbs;
		}
	}

	if (!heroSlave.hasOwnProperty("birthWeek")) {
		heroSlave.birthWeek = random(0, 51);
	}
	// Nationalities, races, surnames random fill
	if (!heroSlave.nationality) {
		// Check for a pre-set race and if the nationality fits, else regenerate
		if (heroSlave.race && setup.filterRacesLowercase.includes(heroSlave.race)) {
			raceToNationality(heroSlave);
		} else {
			heroSlave.nationality = hashChoice(V.nationalities);
		}
	}
	if (!heroSlave.race || !setup.filterRacesLowercase.includes(heroSlave.race)) {
		nationalityToRace(heroSlave);
	}
	if (!heroSlave.birthSurname && heroSlave.birthSurname !== "") {
		heroSlave.birthSurname = (setup.surnamePoolSelector[heroSlave.nationality + "." + heroSlave.race] ||
			setup.surnamePoolSelector[heroSlave.nationality] ||
			setup.whiteAmericanSlaveSurnames).random();
	}
	if (!heroSlave.birthName && heroSlave.birthName !== "") {
		heroSlave.birthName = (setup.namePoolSelector[heroSlave.nationality + "." + heroSlave.race] ||
			setup.namePoolSelector[heroSlave.nationality] ||
			setup.whiteAmericanSlaveNames).random();
	}
	generatePronouns(heroSlave);
	if (heroSlave.geneMods === undefined) {
		heroSlave.geneMods = {};
	}
	if (heroSlave.geneMods.NCS === undefined) {
		heroSlave.geneMods.NCS = 0;
	}
	if (heroSlave.geneMods.rapidCellGrowth === undefined) {
		heroSlave.geneMods.rapidCellGrowth = 0;
	}

	// WombInit(heroSlave);
	const newSlave = BaseSlave();
	deepAssign(newSlave, heroSlave);
	V.heroSlaveID = heroSlave.ID;
	newSlave.ID = generateSlaveID();
	repairLimbs(newSlave);
	generatePuberty(newSlave);
	newSlave.weekAcquired = V.week;
	if (!newSlave.pubicHColor) {
		newSlave.pubicHColor = newSlave.hColor;
	}
	if (!newSlave.underArmHColor) {
		newSlave.underArmHColor = newSlave.hColor;
	}
	if (newSlave.override_Race !== 1) {
		newSlave.origRace = newSlave.race;
	}
	if (newSlave.override_Eye_Color !== 1) {
		resetEyeColor(newSlave, "both");
	}
	if (newSlave.override_H_Color !== 1) {
		newSlave.hColor = getGeneticHairColor(newSlave);
	}
	if (newSlave.override_Arm_H_Color !== 1) {
		newSlave.underArmHColor = getGeneticHairColor(newSlave);
	}
	if (newSlave.override_Pubic_H_Color !== 1) {
		newSlave.pubicHColor = getGeneticHairColor(newSlave);
	}
	if (newSlave.override_Brow_H_Color !== 1) {
		newSlave.eyebrowHColor = getGeneticHairColor(newSlave);
	}
	if (newSlave.override_Skin !== 1) {
		newSlave.skin = getGeneticSkinColor(newSlave);
	}

	SetBellySize(newSlave);

	/* special slaves exceptions to keep siblings sensible */
	if (newSlave.mother === -9999 && newSlave.father === -9998) {
		/* The twins — Camille & Kennerly */
		for (let k = 0; k < V.slaves.length; k++) {
			if (areSisters(V.slaves[k], newSlave) > 0) {
				newSlave.actualAge = V.slaves[k].actualAge, newSlave.physicalAge = newSlave.actualAge, newSlave.visualAge = newSlave.actualAge, newSlave.ovaryAge = newSlave.actualAge, newSlave.birthWeek = V.slaves[k].birthWeek;
			}
		}
	}
	if (newSlave.mother === -9997 && newSlave.father === -9996) {
		/* The siblings — Elisa & Martin */
		for (let k = 0; k < V.slaves.length; k++) {
			if (areSisters(V.slaves[k], newSlave) > 0) {
				if (newSlave.birthName === "Elisa") {
					newSlave.actualAge = V.slaves[k].actualAge - 1, newSlave.physicalAge = newSlave.actualAge, newSlave.visualAge = newSlave.actualAge, newSlave.ovaryAge = newSlave.actualAge;
				} else if (newSlave.birthName === "Martin") {
					newSlave.actualAge = V.slaves[k].actualAge + 1, newSlave.physicalAge = newSlave.actualAge, newSlave.visualAge = newSlave.actualAge, newSlave.ovaryAge = newSlave.actualAge;
				}
			}
		}
	}
	if (newSlave.mother === -9995 && newSlave.father === -9994) {
		/* The fruit siblings — Green & Purple Grape */
		for (let k = 0; k < V.slaves.length; k++) {
			if (areSisters(V.slaves[k], newSlave) > 0) {
				if (newSlave.birthName === "Green Grape") {
					newSlave.actualAge = V.slaves[k].actualAge - 5, newSlave.physicalAge = newSlave.actualAge, newSlave.visualAge = newSlave.actualAge, newSlave.ovaryAge = newSlave.actualAge;
				} else if (newSlave.birthName === "Purple Grape") {
					newSlave.actualAge = V.slaves[k].actualAge + 5, newSlave.physicalAge = newSlave.actualAge, newSlave.visualAge = newSlave.actualAge, newSlave.ovaryAge = newSlave.actualAge;
				}
			}
		}
	}

	nationalityToAccent(newSlave);
	return newSlave;
};
/**
 * Marks limbs to be removed when going trough App.Utils.getHeroSlave.
 * Does not actually remove limbs, only use on slaves that go through App.Utils.getHeroSlave!!
 * @param {{}}hero
 * @param {string} limb
 */
App.Utils.removeHeroLimbs = function(hero, limb = "all") {
	if (!hero.hasOwnProperty("removedLimbs")) {
		hero.removedLimbs = [0, 0, 0, 0];
	}

	switch (limb) {
		case "all":
			hero.removedLimbs = [1, 1, 1, 1];
			break;
		case "left arm":
			hero.removedLimbs[0] = 1;
			break;
		case "right arm":
			hero.removedLimbs[1] = 1;
			break;
		case "left leg":
			hero.removedLimbs[2] = 1;
			break;
		case "right leg":
			hero.removedLimbs[3] = 1;
			break;
	}
};


code:
globalThis.isItemAccessible = (function() {
	return {
		array: array,
		entry: entry,
	};

	/**
	 * Checks whether an item is accessible
	 * @param {string} string Name of wearable item
	 * @param {string} [category="clothing"] that item is in clothing, collar, etc
	 * @param {App.Entity.SlaveState} [slave]
	 * @returns {boolean|string} Returns true if item is accessible, and false if it is not. If the slave param is set, it may sometimes return a string instead of false, explaining why the item can't be used with that slave.
	 */
	function entry(string, category = "clothing", slave) {
		if (V.cheatMode === 1) {
			return true;
		}
		let niceDB = [];
		let harshDB = [];
		switch (category) {
			case "clothing":
			case "clothes":
				niceDB = App.Data.misc.niceClothes;
				harshDB = App.Data.misc.harshClothes;
				break;
			case "collar":
				niceDB = App.Data.misc.niceCollars;
				harshDB = App.Data.misc.harshCollars;
				break;
			case "bellyAccessory":
				niceDB = App.Data.misc.bellyAccessories;
				break;
			case "buttplug":
				niceDB = App.Data.misc.buttplugs;
				break;
			case "buttplugAttachment":
				niceDB = App.Data.misc.buttplugAttachments;
				break;
			case "vaginalAccessory":
				niceDB = App.Data.misc.vaginalAccessories;
				break;
			case "vaginalAttachment":
				niceDB = App.Data.misc.vaginalAttachments;
				break;
			case "dickAccessory":
				niceDB = App.Data.misc.vaginalAccessories;
				break;
			case "shoes":
				niceDB = App.Data.misc.shoes;
				break;
			case "chastity":
				niceDB = App.Data.misc.chastityDevices;
				break;
			default:
				console.log(`made a category for ${category} automatically, may need to define this by hand`);
				niceDB = App.Data.misc[category];
				break;
		}
		let item = niceDB.find((i) => i.value === string);
		if (!item && (typeof harshDB !== 'undefined')) {
			item = harshDB.find((i) => i.value === string);
		}
		if (!item) {
			console.log(`${string} is not a registered piece of clothing! Check App.Data.misc.${category}`);
			return false; /* couldn't be found */
		}
		return isAvailable(item, category, slave);
	}
	/**
	 * Returns array of wearable clothing in format [name, value], basically player facing / game data.
	 * @param {Array} db Array to look in (such as App.Data.misc.niceClothes)
	 * @returns {Array}
	 */
	function array(db) {
		const array = [];
		db.forEach((i) => {
			if (V.cheatMode || isAvailable(i)) {
				let name = i.name;
				if (i.fs) {
					name = i.name + ` (FS)`;
				}
				array.push([name, i.value]);
			}
		});
		return array;
	}

	function isAvailable(item, category, slave) {
		let slaveResults;
		if (slave) {
			slaveResults = isAvailableForSlave(item, category, slave);
			if (slaveResults === false) {
				return slaveResults;
			}
		}
		if (!(item.hasOwnProperty("fs")) && !(item.hasOwnProperty("rs"))) {
			// No restriction, this clothing item is available to everyone
			return true;
		}
		if (item.hasOwnProperty("rs")) {
			if (typeof item.rs === 'object' && item.rs !== null) {
				let fail = false;
				// Scan rs requirments one by one if they are an object
				Object.keys(item.rs).forEach((requirement) => {
					if (typeof item.rs[requirement] === 'object' && item.rs[requirement] !== null) {
						let eitherCheck = 0;
						if (requirement === "either") { // If we find the "either" nested object, we need to see if just one of the conditions in it are true.
							Object.keys(item.rs.either).forEach((nestedRequirement) => {
								if (typeof item.rs.either[nestedRequirement] === 'object' && item.rs.either[nestedRequirement] !== null) { // And yes sometimes even in either we have a nested object, to check V.PC mostly, so check for it.
									let insideEitherCheck = 0;
									Object.keys(item.rs.either[nestedRequirement]).forEach((insideNestedRequirement) => {
										if (V[nestedRequirement][insideNestedRequirement] === item.rs.either[nestedRequirement][insideNestedRequirement]) {
											insideEitherCheck++;
										}
									});
									if (insideEitherCheck > 0) {
										eitherCheck++;
									}
									if (V[nestedRequirement] === item.rs.either[nestedRequirement]) {
										eitherCheck++;
									}
								} else {
									if (item.rs.either[nestedRequirement] === V[nestedRequirement]) {
										eitherCheck++;
									}
								}
							});
						} else {
							Object.keys(item.rs.either).forEach((nestedRequirement) => { // Otherwise we are probably recording a nested V variable to be checked.
								if (V[requirement][nestedRequirement] === item.rs[requirement][nestedRequirement]) {
									eitherCheck++;
								}
							});
						}
						if (eitherCheck === 0) {
							fail = true;
						}
					} else {
						if (V[requirement] !== item.rs[requirement]) {
							fail = true;
						}
					}
				});
				if (fail) {
					return false;
				} else {
					return true;
				}
			} else { // string
				if (V[item.rs] < 1) {
					return false;
				} else {
					return true;
				}
			}
		}
		if (item.hasOwnProperty("fs")) {
			if (!(V.arcologies[0][item.fs] > 0)) {
				return false;
			}
		}
		if (slaveResults) { // If we still haven't returned true or false, then we display why this particular slave can't use the item.
			return slaveResults;
		}
		return true;
	}

	function isAvailableForSlave(item, category, slave) {
		switch (category) {
			case "clothing":
			case "clothes":
				break;
			case "collar":
				break;
			case "bellyAccessory": {
				switch (item.value) {
					case "a support band": {
						if (slave.belly > 10000) {
							return true;
						} else {
							return `Slave belly too small for this`;
						}
					}
					case "a small empathy belly":
					case "a medium empathy belly":
					case "a large empathy belly":
					case "a huge empathy belly": {
						if (slave.belly < 1500 && slave.weight < 130) {
							return true;
						} else {
							return `Slave belly too big for this`;
						}
					}
					default:
						return true;
				}
			}
			case "buttplug": {
				switch (item.value) {
					case "huge plug": {
						if (slave.anus < 2) {
							return `Slave's anus is too small for this right now`;
						} else {
							return true;
						}
					}
					case "long plug":
					case "long, large plug": {
						if (!(slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) {
							return "Elites frown on this";
						} else {
							return true;
						}
					}
					case "long, huge plug": {
						if (slave.anus < 2) {
							return `Slave's anus is too small for this right now`;
						} else if (!(slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) {
							return "Elites frown on this";
						} else {
							return true;
						}
					}
					default:
						return true;
				}
			}
			case "buttplugAttachment":
				break;
			case "vaginalAccessory": {
				if (slave.vagina < 0) {
					return false;
				}
				switch (item.value) {
					case "huge dildo": {
						if (slave.vagina < 2) {
							return `Slave's vagina is too small for this right now`;
						} else {
							return true;
						}
					}
					case "long dildo":
					case "long, large dildo": {
						if (!(slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) {
							return "Elites frown on this";
						} else {
							return true;
						}
					}
					case "long, huge dildo": {
						if (slave.vagina < 2) {
							return `Slave's vagina is too small for this right now`;
						} else if (!(slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) {
							return "Elites frown on this";
						} else {
							return true;
						}
					}
					default:
						return true;
				}
			}
			case "vaginalAttachment": {
				if (slave.vagina < 0) {
					return false;
				}
				switch (item.value) {
					case "vibrator":
						if (slave.vaginalAccessory === "none") {
							return "No vaginal accessory to attach it to";
						} else if (slave.vaginalAccessory === "bullet vibrator" || slave.vaginalAccessory === "smart bullet vibrator") {
							return "Vaginal accessory already vibrates";
						} else {
							return true;
						}
				}
				break;
			}
			case "dickAccessory":
				if (slave.dick < 1) {
					return false;
				}
				return true;
			case "shoes":
				break;
			case "chastity": {
				switch (item.value) {
					case "chastity belt":
					case "combined chastity belt": {
						if (slave.vagina > -1) {
							return true;
						} else {
							return false;
						}
					}
					case "chastity cage":
					case "combined chastity cage": {
						if (slave.dick > 0) {
							return true;
						} else {
							return false;
						}
					}
					case "genital chastity":
					case "full chastity": {
						if (slave.vagina > -1 && slave.dick > 0) {
							return true;
						} else {
							return false;
						}
					}
					case "choose own chastity": {
						if (slave.choosesOwnChastity !== 1 && slave.devotion > 20 && slave.trust > 0) {
							return true;
						}
						return false;
					}
					case "revoke choosing own chastity": {
						if (slave.choosesOwnChastity > 0) {
							return true;
						}
						return false;
					}
					default:
						return true;
				}
			}
			default:
				break;
		}
		return true;
	}
})();

/**
 * @param {App.Entity.SlaveState} slave
 * @param {string} prosthetic
 * @returns {boolean}
 */
globalThis.isProstheticAvailable = function(slave, prosthetic) {
	return slave.readyProsthetics.findIndex(function(p) { return p.id === prosthetic; }) !== -1;
};

/**
 * @param {App.Entity.SlaveState} slave
 * @param {string} prosthetic
 */
globalThis.addProsthetic = function(slave, prosthetic) {
	if (!isProstheticAvailable(slave, prosthetic)) {
		let limb = prostheticToLimb(prosthetic);
		if (limb > 0) {
			let p = {
				id: prosthetic,
				arm: {left: new App.Entity.LimbState(), right: new App.Entity.LimbState()},
				leg: {left: new App.Entity.LimbState(), right: new App.Entity.LimbState()}
			};
			p.arm.left.type = limb;
			p.arm.right.type = limb;
			p.leg.left.type = limb;
			p.leg.right.type = limb;
			slave.readyProsthetics.push(p);
		} else {
			slave.readyProsthetics.push({id: prosthetic});
		}
	}
};

/**
 * @param {App.Entity.SlaveState} slave
 * @param {string} prosthetic
 * @returns {{}}
 */
globalThis.findProsthetic = function(slave, prosthetic) {
	return slave.readyProsthetics.find(p => p.id === prosthetic);
};

/**
 * @param {string} prosthetic
 * @returns {number}
 */
globalThis.prostheticToLimb = function(prosthetic) {
	switch (prosthetic) {
		case "basicL":
			return 2;
		case "sexL":
			return 3;
		case "beautyL":
			return 4;
		case "combatL":
			return 5;
		case "cyberneticL":
			return 6;
		default:
			return 0;
	}
};

/**
 *
 * @param {number} limb
 * @returns {string}
 */
globalThis.limbToProsthetic = function(limb) {
	switch (limb) {
		case 2:
			return "basicL";
		case 3:
			return "sexL";
		case 4:
			return "beautyL";
		case 5:
			return "combatL";
		case 6:
			return "cyberneticL";
		default:
			return "";
	}
};


code:
/**
 * @returns {DocumentFragment}
 */
App.MainView.errors = function() {
	const fragment = document.createDocumentFragment();

	/**
	 * @returns {HTMLParagraphElement}
	 */
	function newError() {
		const error = document.createElement("p");
		fragment.append(error);
		return error;
	}

	// check for correct version
	if (V.releaseID >= 1000 || ["0.9", "0.8", "0.7", "0.6"].includes(V.ver)) {
		if (V.releaseID < App.Version.release) {
			newError().append(App.UI.DOM.makeElement("span", "INCOMPATIBILITY WARNING:", "major-warning"),
				` Your saved game was created using version: ${V.ver}, build: ${V.releaseID}. You must run `,
				App.UI.DOM.passageLink("Backwards Compatibility", "Backwards Compatibility"));
		}
	} else {
		newError().append(App.UI.DOM.makeElement("span", "INCOMPATIBLE SAVE WARNING:", "major-warning"),
			` Your saved game was created using version: ${V.ver}, and you are using a later version which New Game Plus cannot reconcile. Please start a new game.`);
	}

	// check for correct rules
	if (V.defaultRules.length > 0 && (V.defaultRules[0].condition === undefined || V.defaultRules[0].set === undefined)) {
		const error = newError();
		error.append(App.UI.DOM.makeElement("span", "INCOMPATIBILITY WARNING:", "major-warning"),
			" The rules assistant format has changed. In the Options Menu, please ");
		const ra = document.createElement("strong");
		ra.append("Reset RA Rules");
		error.append(ra);
	}

	// check for NaN
	if (V.NaNArray.length > 0) {
		const error = newError();
		error.id = "NaNArray";
		error.append(App.UI.DOM.makeElement("span", "ERROR: The following variables are NaN! Please report this.", "error"));
		V.NaNArray.forEach(e => {
			const div = document.createElement("div");
			div.append(e);
			error.append(div);
		});
		error.append(App.UI.DOM.link("Hide NaN variables until next week",
			() => {
				error.outerHTML = "";
				V.NaNArray = [];
			})
		);
	}

	// check custom slaves
	if (App.Utils.IsCustomSlaveMutated(V.customSlave)) {
		newError().append(App.UI.DOM.makeElement("span", "ERROR: Your custom slave order has taken on a mutated life of its own and has been summarily shot. Refile your custom slave order, if necessary, and notify the appropriate authorities if you see this message again.", "error"));
		V.customSlave = new App.Entity.CustomSlaveOrder();
	}
	if (App.Utils.IsCustomSlaveMutated(V.huskSlave)) {
		newError().append(App.UI.DOM.makeElement("span", "ERROR: Your husk slave order has taken on a mutated life of its own and has been summarily shot. Refile your husk slave order, if necessary, and notify the appropriate authorities if you see this message again.", "error"));
		V.huskSlave = new App.Entity.CustomSlaveOrder();
	}

	return fragment;
};

/**
 * @returns {Text}
 */
App.MainView.fcnn = function() {
	let text;

	if (V.FCNNstation !== 1 && V.week >= 90) {
		text = "FCNN: FCNN service has been temporarily suspended. Please stand by.";
	} else {
		text = V.fcnn.random();
	}

	return document.createTextNode(`${text} `);
};

App.MainView.useFucktoys = function() {
	const fragment = document.createDocumentFragment();
	for (const slave of V.slaves) {
		if (slave.assignment !== Job.FUCKTOY) {
			continue;
		}
		fragment.append(App.MainView.useFucktoy(slave));
	}
	return fragment;
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {HTMLDivElement}
 */
App.MainView.useFucktoy = function(slave) {
	const fragment = document.createDocumentFragment();

	function setEnvironment() {
		V.activeSlave = slave;
		V.nextButton = "Back";
		V.nextLink = "AS Dump";
		V.returnTo = passage();
	}

	const {him, his} = getPronouns(slave);

	const div = document.createElement("div");
	div.classList.add("note");

	div.append(App.Interact.ToyChest(slave));

	div.append(" In the coming week you plan to concentrate on ");

	if (slave.toyHole !== "all her holes") {
		div.append(`${his} ${slave.toyHole}`);
	} else {
		div.append(`all of ${his} holes equally`);
	}

	if (slave.fuckdoll === 0) {
		div.append(", but for now:");

		const optionDiv = document.createElement("div");
		optionDiv.classList.add("indent");

		optionDiv.append(App.UI.DOM.passageLink(`Use ${his} mouth`, "FLips", setEnvironment),
			" | ", App.UI.DOM.passageLink("Play with " + his + " tits", "FBoobs", setEnvironment));

		if (canDoVaginal(slave)) {
			optionDiv.append(" | ", App.UI.DOM.passageLink(`Fuck ${him}`, "FVagina", setEnvironment));
			if (canDoAnal(slave)) {
				optionDiv.append(" | ", App.UI.DOM.passageLink(`Use ${his} holes`, "FButt", setEnvironment));
			}
		}
		if (canDoAnal(slave)) {
			optionDiv.append(" | ", App.UI.DOM.passageLink(`Fuck ${his} ass`, "FAnus", setEnvironment));
		}
		if (canDoVaginal(slave) || canDoAnal(slave)) {
			if (slave.belly >= 300000) {
				optionDiv.append(" | ", App.UI.DOM.passageLink(`Fuck ${him} over ${his} belly`, "FBellyFuck", setEnvironment));
			}
		}
		if (canPenetrate(slave)) {
			optionDiv.append(" | ", App.UI.DOM.passageLink(`Ride ${him}`, "FDick", setEnvironment));
		}
		optionDiv.append(" | ", App.UI.DOM.passageLink(`Abuse ${him}`, "FAbuse", setEnvironment));

		div.append(optionDiv);
	} else {
		div.append(".");
	}
	fragment.append(div);

	return div;
};

/**
 * @returns {HTMLDivElement}
 */
App.MainView.useGuard = function() {
	const guard = S.Bodyguard;

	const outerDiv = document.createElement("div");

	if (!guard || guard.assignment !== "guard you") {
		return outerDiv;
	}

	App.UI.DOM.appendNewElement("span", outerDiv, App.Interact.guardPose(guard), "scene-intro");

	function setEnvironment() {
		V.activeSlave = guard;
		V.nextButton = "Back";
		V.nextLink = "AS Dump";
		V.returnTo = passage();
	}

	const {him, his} = getPronouns(guard);
	const optionDiv = document.createElement("div");
	optionDiv.classList.add("indent");

	optionDiv.append(
		App.UI.DOM.passageLink(`Use ${his} mouth`, "FLips", setEnvironment),
		" | ",
		App.UI.DOM.passageLink(`Play with ${his} tits`, "FBoobs", setEnvironment)
	);

	if (canDoVaginal(guard)) {
		optionDiv.append(" | ", App.UI.DOM.passageLink(`Fuck ${him}`, "FVagina", setEnvironment));
		if (canDoAnal(guard)) {
			optionDiv.append(" | ", App.UI.DOM.passageLink(`Use ${his} holes`, "FButt", setEnvironment));
		}
		if (guard.belly >= 300000) {
			optionDiv.append(" | ", App.UI.DOM.passageLink(`Fuck ${him} over ${his} belly`, "FBellyFuck", setEnvironment));
		}
	}

	if (canPenetrate(guard)) {
		optionDiv.append(" | ", App.UI.DOM.passageLink(`Ride ${him}`, "FDick", setEnvironment));
	}
	if (canDoAnal(guard)) {
		optionDiv.append(" | ", App.UI.DOM.passageLink(`Fuck ${his} ass`, "FAnus", setEnvironment));
	}
	optionDiv.append(" | ", App.UI.DOM.passageLink(`Abuse ${him}`, "Gameover", () => { V.gameover = "idiot ball"; }));

	outerDiv.append(optionDiv);

	return outerDiv;
};

App.MainView.full = function() {
	function mainMenu() {
		const div = document.createElement("div");

		div.append(App.UI.DOM.makeElement("span", "MAIN MENU", "name"));
		const span = document.createElement("span");
		span.classList.add("tab", "note");
		span.append(App.UI.DOM.passageLink("Summary Options", "Summary Options"));

		if (V.rulesAssistantMain !== 0) {
			const raLink = document.createElement("span");
			raLink.id = "RAButton";
			raLink.append(" | ", App.UI.DOM.passageLink("Rules Assistant Options", "Rules Assistant"),
				" ", App.UI.DOM.makeElement("span", "[R]", ["clear-formatting", "hotkey"]));
			span.append(raLink);

			if (V.rulesAssistantAuto !== 1) {
				span.append(" | ", App.UI.DOM.passageLink("Apply Rules Assistant at week end", "Main", () => { V.rulesAssistantAuto = 1; }));
			} else {
				span.append(" | ", App.UI.DOM.passageLink("Stop applying Rules Assistant at week end", "Main", () => { V.rulesAssistantAuto = 0; }));
			}
			span.append(" | ");
			if (DefaultRulesError()) {
				span.append(App.UI.DOM.makeElement("span", "WARNING: One or more rules' custom conditions has errors!", "warning"));
			}
			span.append(App.UI.DOM.passageLink("Re-apply Rules Assistant now (this will only check slaves in the Penthouse)", "Main", () => {
				for (const slave of V.slaves) {
					if (assignmentVisible(slave) && slave.useRulesAssistant === 1) {
						DefaultRules(slave);
					}
				}
			}));
		}
		div.append(" ", span);
		return div;
	}

	const fragment = document.createDocumentFragment();

	fragment.append(App.Reminders.list({maxFuture: 5, link: true}));

	if (V.seeFCNN === 1) {
		const div = document.createElement("div");
		div.classList.add("main-fcnn");
		div.append(App.MainView.fcnn(),
			App.UI.DOM.passageLink("Hide", passage(), () => { V.seeFCNN = 0; })
		);
		fragment.append(div);
	}

	fragment.append(mainMenu());

	fragment.append(App.UI.SlaveList.penthousePage());

	if (V.fucktoyInteractionsPosition === 0) {
		fragment.append(App.MainView.useFucktoys());
	}
	if (V.useSlaveSummaryOverviewTab === 0) {
		fragment.append(App.MainView.useGuard());
	}

	return fragment;
};


code:
/**
 * Basic application of scar
 * @param {App.Entity.SlaveState} slave
 * @param {string} scar
 * @param {string} design
 * @param {number} weight
 */
App.Medicine.Modification.addScar = function(slave, scar, design, weight) {
	/*
	V.scarApplied = 1;
	V.degradation += 10;
	surgeryDamage(slave, 10); // dangerous to uncomment this as sometimes many scars are applied at once.
	cashX(forceNeg(surgery.costs), "slaveSurgery", slave);
	surgeryDamage(slave, (V.PC.skill.medicine >= 100) ? Math.round(surgery.healthCosts / 2) : surgery.healthCosts);*/
	if (!weight) {
		weight = 1;
	}
	if (!slave.scar.hasOwnProperty(scar)) {
		slave.scar[scar] = new App.Entity.scarState();
	}
	if (!slave.scar[scar].hasOwnProperty(design)) {
		slave.scar[scar][design] = weight;
	} else {
		slave.scar[scar][design] += weight;
	}
};
/**
 * Basic application of scar
 * @param {App.Entity.SlaveState} slave
 * @param {string} scar
 * @param {string} design
 */
App.Medicine.Modification.removeScar = function(slave, scar, design) {
	/*
	V.scarApplied = 1;
	V.degradation += 10;
	surgeryDamage(slave, 10); //dangerous to uncomment this as sometimes many scars are applied at once.
	cashX(forceNeg(surgery.costs), "slaveSurgery", slave);
	surgeryDamage(slave, (V.PC.skill.medicine >= 100) ? Math.round(surgery.healthCosts / 2) : surgery.healthCosts);*/
	if (slave.scar.hasOwnProperty(scar)) { // if scar object exists for this body part
		if (slave.scar[scar].hasOwnProperty(design)) { // if object has this kind of scar (might be custom)
			if (["burn", "c-section", "chain", "cutting", "exotic", "generic", "menacing", "surgical", "whip"].includes(design)) {
				slave.scar[scar][design] = 0;
			} else {
				delete slave.scar[scar][design]; // scar was custom
			}
		}
		// remove the scar object entirely if no entry is scarred:
		let weights = Object.values(slave.scar[scar]);
		let total = 0;
		let i;
		for (i = 0; i < weights.length; i++) {
			total += weights[i];
		}
		if (total === 0) {
			delete slave.scar[scar];
		}
	}
};

/**
 * Slave is whipped over the entire body, and strains in manacles so much that the wrists and ankles scar if present.
 * @param {App.Entity.SlaveState} slave
 * @param {number} weight
 */
App.Medicine.Modification.addScourged = function(slave, weight) {
	let scarArray = ["left breast", "right breast", "back", "lower back", "left buttock", "right buttock"];
	let i = 0;
	// Whip
	if (getLeftArmID(slave) === 1) {
		scarArray.push("left upper arm");
	}
	if (getRightArmID(slave) === 1) {
		scarArray.push("right upper arm");
	}
	if (getLeftLegID(slave) === 1) {
		scarArray.push("left thigh");
	}
	if (getRightLegID(slave) === 1) {
		scarArray.push("right thigh");
	}

	for (i = 0; i < scarArray.length; i++) {
		App.Medicine.Modification.addScar(slave, scarArray[i], "whip", weight);
	}
	// Manacles
	scarArray = [];
	if (getLeftArmID(slave) === 1) {
		scarArray.push("left wrist");
	}
	if (getRightArmID(slave) === 1) {
		scarArray.push("right wrist");
	}
	if (getLeftLegID(slave) === 1) {
		scarArray.push("left ankle");
	}
	if (getRightLegID(slave) === 1) {
		scarArray.push("right ankle");
	}
	for (i = 0; i < scarArray.length; i++) {
		App.Medicine.Modification.addScar(slave, scarArray[i], "chain", weight);
	}
};

/**
 * Scars a slave over a large section of their body.
 * @param {App.Entity.SlaveState} slave
 * @param {string} location full, upper, lower, left or right
 * @param {string} type whip, burn, surgical, generic
 * @param {number} weight
 */
App.Medicine.Modification.addBulkScars = function(slave, location, type, weight) {
	let scarArray = [];

	/* Divide slave into quarters, and add each quarter as needed. */

	/* Top left */
	if (["full", "left", "upper"].includes(location)) {
		scarArray.push("left breast");
		if (getLeftArmID(slave) === 1) {
			scarArray.push("left upper arm", "left lower arm", "left hand");
		}
	}

	/* Top right */
	if (["full", "right", "upper"].includes(location)) {
		scarArray.push("right breast");
		if (getRightArmID(slave) === 1) {
			scarArray.push("right upper arm", "right lower arm", "right hand");
		}
	}

	/* Lower left */
	if (["full", "left", "lower"].includes(location)) {
		scarArray.push("left buttock");
		if (getLeftLegID(slave) === 1) {
			scarArray.push("left thigh", "left calf", "left foot");
		}
	}

	/* Lower Right */
	if (["full", "lower", "right"].includes(location)) {
		scarArray.push("right buttock");
		if (getRightLegID(slave) === 1) {
			scarArray.push("right thigh", "right calf", "right foot");
		}
	}

	/* Extra */
	if (["full", "upper"].includes(location)) {
		scarArray.push("back", "lower back");
	}

	let i = 0;
	for (i = 0; i < scarArray.length; i++) {
		App.Medicine.Modification.addScar(slave, scarArray[i], type, weight);
	}
};

/**
 * Adds a piercing to a slave.
 * @param {App.Entity.SlaveState} slave
 * @param {string} location
 * @param {number} weight
 * @returns {string} slave reaction
 */
App.Medicine.Modification.setPiercing = function(slave, location, weight) {
	if (weight === 3) {
		// smart piercings cost more
		cashX(forceNeg(V.SPcost), "slaveMod", slave);
	} else {
		cashX(forceNeg(V.modCost), "slaveMod", slave);
	}

	// reaction
	const {He, he, His, his, him} = getPronouns(slave);
	const delta = weight - slave[`${location}Piercing`];
	let r = ` `;
	if (location === "tongue") {
		if (slave.fetishKnown && slave.fetish === "cumslut" && slave.fetishStrength > 10) {
			r += `Since ${he}'s an oral whore, ${he}'s `;
			if (delta > 0) {
				if (weight > 1) {
					r += `<span class="devotion inc">very happy</span> you took the time to personally pierce ${his} tongue so heavily. `;
				} else {
					r += `<span class="devotion inc">happy</span> to get a tongue piercing from you personally. `;
				}
				r += `After all, ${he}'s pretty sure it's a sign there will be plenty of ${V.PC.dick > 0 ? `dick for ${him} to suck` : `pussy for ${him} to eat`} in the future. `;
				slave.devotion += 2 * delta;
			} else if (slave.devotion <= 95) {
				r += `<span class="devotion dec">saddened</span> you decided to take out ${his} tongue piercings. ${He}'s afraid this means something about ${his} future as a suck slut. `;
				slave.devotion += 2 * delta;
			} else {
				r += `disapointed to lose ${his} tongue piercings, but accepts your will for ${his} body. `;
			}
		}
		if (slave.sexualFlaw === "hates oral" && weight > 0) {
			r += `${He} has trouble thinking of ${his} mouth as a warm, wet, playful hole, so ${his} new piercings will be therapeutic. Having something to suck on at all times should force ${him} past ${his} hang-ups. `;
		}
	} else if (location === "nipples" || location === "areolae") {
		if (slave.fetishKnown && slave.fetish === "boobs" && slave.fetishStrength > 10) {
			r += `Since ${he}'s fixated on ${his} boobs, ${he}'s `;
			if (delta > 0) {
				if (weight > 1) {
					r += `<span class="devotion inc">very happy</span> you took the time to personally pierce them so heavily. `;
				} else {
					r += `<span class="devotion inc">happy</span> to get them pierced by you personally. `;
				}
				r += `As far as ${he}'s concerned, more attention on ${his} tits is always good. `;
				slave.devotion += 2 * delta;
			} else if (slave.devotion <= 95) {
				r += `<span class="devotion dec">saddened</span> you decided to take out ${his} piercings. ${He} doesn't like anything that makes ${his} tits less distinctive. `;
				slave.devotion += 2 * delta;
			} else {
				r += `disapointed to lose ${his} the piercings in ${his} ${location}, but accepts your will for ${his} body. `;
			}
		}
	} else if (location === "corset") {
		if (slave.fetishKnown && slave.fetish === "masochist" && slave.fetishStrength > 10) {
			r += `Since ${he}'s a pain whore `;
			if (delta > 0) {
				r += `${he} <span class="devotion inc">got off</span> on you giving ${him} a corset piercing personally. ${He} loved the feeling of the metal entering ${his} flesh. `;
				slave.devotion += 2 * delta;
			} else if (slave.devotion <= 95) {
				r += `<span class="devotion dec">saddened</span> you decided to take out ${his} corset piercings. ${He}'ll miss the constant slight discomfort they gave ${him}. `;
				slave.devotion += 2 * delta;
			} else {
				r += `disapointed to lose ${his} corset piercings, but accepts your will for ${his} body. `;
			}
		}
	} else if (location === "vagina") {
		if (slave.fetishKnown && slave.fetish === "pregnancy" && slave.fetishStrength > 10) {
			r += `Since ${he}'s a whore for impregnation, ${he}'s `;
			if (delta > 0) {
				if (weight > 1) {
					r += `<span class="devotion inc">very happy</span> you took the time to personally pierce ${his} pussylips so heavily. `;
				} else {
					r += `<span class="devotion inc">happy</span> to get pussy piercings from you personally. `;
				}
				r += `Like most pregnancy fetishists, ${he}'s a little desperate for dick, and anything that points towards more bareback sex is appealing to ${him}. `;
				slave.devotion += 2 * delta;
			} else if (slave.devotion <= 95) {
				r += `saddened you decided to take out ${his} pussy piercings. Like most pregnancy fetishists, ${he}'s a little desperate for dick, and ${he}'s afraid of anything that points towards less attention focused on ${his} cunt. `;
				slave.devotion += 2 * delta;
			} else {
				r += `disapointed to lose ${his} pussy piercings, but accepts your will for ${his} body. `;
			}
		}
		if (slave.sexualFlaw === "hates penetration" && weight > 0) {
			r += `${He} has trouble thinking of ${his} vagina as a fuckhole rather than something special to protect and cherish, so ${his} new piercings will be therapeutic. The constant, inescapable stimulation of ${his} labia should force ${him} past ${his} hang-ups. `;
		}
	} else if (location === "dick") {
		if (slave.fetishKnown && slave.fetish === "pregnancy" && slave.fetishStrength > 10) {
			r += `Since ${he}'s fixated on using ${his} cock to impregnate other slaves, ${he}'s `;
			if (delta > 0) {
				if (weight > 1) {
					r += `<span class="devotion inc">very happy</span> you took the time to personally stick so much metal into ${his} dick`;
					if (slave.scrotum > 0) {
						r += ` and ballsack`;
					}
					r += `. `;
				} else {
					r += `<span class="devotion inc">happy</span> to get penis piercings from you personally. `;
				}
				r += `${He}'s extremely proud of ${his} newly decorated member. `;
				slave.devotion += 2 * delta;
			} else if (slave.devotion <= 95) {
				r += `saddened you decided to take out ${his} dick piercings. ${He} passionately loves using ${his} penis, and ${he}'s concerned you're planning to let ${him} do less of that. `;
				slave.devotion += 2 * delta;
			} else {
				r += `disapointed to lose ${his} dick piercings, but accepts your will for ${his} body. `;
			}
		}
	} else if (location === "anus") {
		if (slave.fetishKnown && slave.fetish === "buttslut" && slave.fetishStrength > 10) {
			r += `Since ${he}'s an anal slut, ${he}'s `;
			if (delta > 0) {
				if (weight > 1) {
					r += `<span class="devotion inc">very happy</span> you took the time to personally pierce the entire area around ${his} whorish asspussy. `;
				} else {
					r += `<span class="devotion inc">happy</span> to get a piercing next to ${his} favorite hole from you personally. `;
				}
				r += `${He} can't wait to find out how the metal will feel against ${his} butthole when ${he} moves around. `;
				slave.devotion += 2 * delta;
			} else if (slave.devotion <= 95) {
				r += `saddened you decided to take out ${his} backdoor piercings. ${He} will miss the constant anal titillation, and ${he}'s worried this means you're becoming less interested in ${his} butthole. `;
				slave.devotion += 2 * delta;
			} else {
				r += `disapointed to lose ${his} backdoor piercings, but accepts your will for ${his} body. `;
			}
		}
		if (slave.sexualFlaw === "hates anal" && weight > 0) {
			r += `${He} has trouble thinking of ${his} asshole as a sexy, fuckable hole, so ${his} new piercings will be therapeutic. The constant tickling back there should force ${him} past ${his} hang-ups. `;
		}
	}

	if (slave.genes === "XY" && slave.attrXY <= 35 && ["ear", "eyebrow", "lips", "navel", "nose"].contains(location)) {
		r += `${His} girly new `;
		switch (location) {
			case "ear":
				r += `pierced ears should help ${him} a little with ${his} issues about ${his} self-image as a fuckable slave girl. `;
				break;
			case "lips":
				r += `facial piercing, just below ${his} pretty mouth, should help ${him} get used to the idea of it being a warm, wet hole for cocks. `;
				break;
			case "nose":
				r += `nose piercing should make ${his} reflection in the mirror seem just a bit more fuckably feminine to ${him}. `;
				break;
			case "navel":
				r += `navel piercing should help ${him} see ${his} naked body in a submissively sexual way, especially as ${he} turns to hide it and present ${his} asspussy. `;
				break;
			case "eyebrow":
				r += `facial piercing should help destroy ${his} vestiges of sexual identity outside of ${his} life as a hole for cocks. `;
				break;
			default:
				r += `set of slutty facial piercings should help ${him} get used to the idea of being a girly slave meant to please dicks. `; /* impossible, but I'm leaving the text anyway */
		}
		if (slave.devotion < -20) {
			r += `Unfortunately, that positive effect will have to wait until ${he}'s a little less resistant to the idea of being a sex slave. `;
		}
	}

	slave[`${location}Piercing`] = weight;
	return r;
};

/**
 * Adds a tattoo to a slave.
 * @param {App.Entity.SlaveState} slave
 * @param {string} location
 * @param {string|number} design (0 removes)
 * @returns {string} slave reaction
 */
App.Medicine.Modification.setTattoo = function(slave, location, design) {
	cashX(forceNeg(V.modCost), "slaveMod", slave);

	// reaction
	const {He, he, His, his, him} = getPronouns(slave);
	const {s, ss, x, c, say, title: Master} = getEnunciation(slave);
	let r = ` `;
	if (location === "anus" && design !== 0) {
		if (canSee(slave) && canTalk(slave)) {
			const anus = (function(s) {
				if (s.anus > 3) {
					return "anal gape";
				} else if (s.anus === 3) {
					return "relaxed asspussy";
				} else if (s.anus === 2) {
					return "soft butthole";
				} else {
					return "tight butthole";
				}
			})(slave);

			if (design === "bleached") {
				r += `${He} knew ${he} was getting ${his} ass bleached, of course. The stinging sensation was hard to miss. Even so, ${he} cranes ${his} neck around as soon as possible to look at ${his} ${anus} in the mirror. `;
			} else {
				r += `${He} knew ${he} was getting ${his} asshole tattooed, of course. ${He} cranes ${his} neck around as soon as possible to look at ${his} ${anus} in the mirror. `;
				if (slave.butt > 5) {
					r += `${His} huge buttocks are still held apart by the spreaders used to give the tattooing actuators access to ${his} most intimate area, giving ${him} a good view. `;
				}
			}
			if (slave.fetishKnown && slave.fetish === "buttslut") {
				switch (design) {
					case "bleached":
						r += `${He} giggles at ${his} crinkled fuckhole's clean, fuckable appearance, and starts squeezing ${his} sphincter muscles experimentally. "Thank you, ${Master}," ${he} gasps, flushing.`;
						break;
					case "flowers":
					case "tribal patterns":
						r += `${He} gasps at the beautiful design and then suddenly bursts into tears. "T-thank you, ${Master}," ${he} blubbers. "I'm ${s}-${s}uch an anal whore."`;
						break;
					case "advertisements":
						r += `${He} stares, ${his} mouth working as ${he} spells out `;
						if (slave.vagina > -1) {
							r += `'Fucktoy's Favorite Fuckhole.' ${He} smiles naughtily. "${Master}, it sure i${s}, `;
						} else {
							r += `'Spit First.' ${He} smiles naughtily. "${Master}, that work${s} for me. Lube'${s} good too. Or, um, dry'${s} fine too," `;
						}
						r += `${he} giggles.`;
						break;
					case "rude words":
						r += `${He} stares, ${his} mouth working as ${he} spells out `;
						if (slave.vagina > -1) {
							r += `'Fucktoy's Favorite Fuckhole.' ${He} smiles naughtily. "${Master}, it sure i${s}, `;
						} else {
							r += `'Sissy Slut's Sodomy Slit.' ${He} smiles naughtily. "${Master}, I love it when you fuck my lewd butt ${s}lit," `;
						}
						r += `${he} giggles.`;
						break;
					case "sacrilege":
						r += `${He} stares, ${his} mouth working as ${he} reads the ring of Aramaic blasphemy around ${his} hole. Getting it, ${he} smiles naughtily. "${Master}, I'm a horrible little butthole ${s}uccubu${s}. Plea${s}e, ${Master}, feed me. I need cum in my a${ss} to ${s}urvive."`;
						break;
					case "sacrament":
						r += `${He} stares, ${his} mouth working as ${he} reads the ring of Aramaic around ${his} hole. Getting it, ${he} smiles beatifically. "${Master}, I alway${s} knew butt${s}e${x} was a ${s}acrament."`;
						break;
					case "degradation":
						r += `${He} stares, ${his} mouth working as ${he} spells out '${WrittenMaster(slave)} Made Me An Anal Slut.' ${He} smiles naughtily. "Oh ${Master}, you did," ${he} ${say}s gratefully. "And I love you for it."`;
						break;
					case "counting":
						r += `${He} stares, realizing that the markings denote how many times ${he}'s been sodomized. "${Master}, thank you, and, and, I hope I'll have to come back in here a lot," ${he} ${say}s. "Um, becau${s}e I have to have that updated. Becau${s}e I'm being fucked in the butt a lot," ${he} adds, wanting to be completely clear.`;
						break;
					case "bovine patterns":
						r += `${He} stares at ${his} new cow's anus. Finally, ${he} focuses on the upsides of being permanently transformed like this. "${Master}, I hope thi${s} mean${s} I'll have to be fed up the butt a lot," ${he} ${say}s, referencing the milking systems' anal feeders.`;
						break;
					case "possessive":
						r += `${He} stares, realizing that the ring of numbers around ${his} anus give the date ${he} was enslaved by you. "Be${s}t pla${c}e for that date, ${Master}," ${he} ${say}s. "Being a butt ${s}lut i${s} my life now."`;
						break;
					case "paternalist":
						r += `${He} stares, ${his} mouth working as ${he} spells out '${WrittenMaster(slave)} Helped Me Love Anal.' "Oh ${Master}, you did," ${he} coos. "You did! Thank you ${s}o much for helping me become the anal ${s}lut I wa${s} alway${s} meant to be!"`;
						break;
					default:
						r += `${He} smiles, pleased at the attention ${his} ${anus} is getting.`;
				}
			} else if (slave.devotion > 50) {
				switch (design) {
					case "bleached":
						r += `${He} gasps at how closely the crinkled skin around ${his} anal opening matches the rest of ${his} ${slave.skin} body. "Oh, it'${s} ${s}o beautiful! Thank you, ${Master}, thank you," ${he} exclaims lovingly.`;
						break;
					case "flowers":
					case "tribal patterns":
						r += `${He} gasps at the beautiful design. "It'${s} ${s}o pretty, ${Master}," ${he} ${say}s. "Thank you. I love you ${s}o much!"`;
						break;
					case "advertisements":
					case "degradation":
					case "paternalist":
					case "possessive":
					case "rude words":
					case "sacrament":
					case "sacrilege":
						r += `${He} stares, ${his} mouth working as ${he} spells out the words tattooed in a ring around ${his} asshole. "Thank you, ${Master}," ${he} ${say}s devotedly. "I love you and I'll ${s}erve you with my butthole a${s} be${s}t I can."`;
						break;
					default:
						r += `${He} smiles, pleased at the mark of your attention to ${his} body. "Thank you, ${Master}," ${he} ${say}s devotedly. "I love you and I'll ${s}erve you with my butthole a${s} be${s}t I can."`;
				}
			} else if (slave.sexualFlaw === "hates anal") {
				r += `${He} stares, `;
				switch (design) {
					case "bleached":
						r += `horrified, and then shudders convulsively, but can't look away. `;
						break;
					case "flowers":
					case "tribal patterns":
						r += `torn between fascination at the intricate design and horror at having ${his} anus permanently tattooed. `;
						break;
					case "advertisements":
					case "degradation":
					case "paternalist":
					case "possessive":
					case "rude words":
					case "sacrament":
					case "sacrilege":
						r += `${his} mouth working as ${he} spells out the words tattooed in a ring around ${his} asshole. ${He} starts to cry as their meaning sinks in. `;
						break;
					default:
						r += `lower lip quivering. `;
				}
				r += `${He} hates being assraped, and knows that this means there's lots of it in store for ${him}. "${Master}, p-plea${s}e," ${he} moans halfheartedly. "I d-don't like being b-butt f-fucked."`;
			} else if (slave.devotion > 20) {
				r += `${He} stares, `;
				switch (design) {
					case "bleached":
						r += `fascinated. ${He} doesn't seem sure what to make of this. `;
						break;
					case "flowers":
					case "tribal patterns":
						r += `torn between admiration at the intricate design and realization that ${his} anus has been permanently tattooed. `;
						break;
					case "advertisements":
					case "degradation":
					case "paternalist":
					case "possessive":
					case "rude words":
					case "sacrament":
					case "sacrilege":
						r += `${his} mouth working as ${he} spells out the words tattooed in a ring around ${his} asshole. ${He} shudders a little as they sink in and ${he} internalizes what they say about ${him} and ${his} asshole. `;
						break;
					default:
						r += `realizing that ${his} anus has been permanently tattooed. It's yet another proof that ${he}'s permanently a sex slave, and ${his} anus is for fucking. `;
				}
				r += `"Thank you, ${Master}," ${he} ${say}s, taking refuge in propriety. "Um, d-doe${s} thi${s} mean I'm going to be more of a, um, b-butt${s}lut?" ${he} asks hesitantly, not sounding very enthusiastic.`;
			} else if (slave.trust < -20) {
				r += `${He} stares, `;
				switch (design) {
					case "bleached":
						r += `trying to process the appearance of what must seem like an unnaturally clean-looking hole. `;
						break;
					case "flowers":
					case "tribal patterns":
						r += `struggling to process the fact that one of ${his} most intimate areas has been tattooed. Beautifully tattooed, but still." `;
						break;
					case "advertisements":
					case "degradation":
					case "paternalist":
					case "possessive":
					case "rude words":
					case "sacrament":
					case "sacrilege":
						r += `${his} mouth working as ${he} spells out the words tattooed in a ring around ${his} asshole. ${He} shudders as ${he} does ${his} best to conceal ${his} feelings about such a permanent display of ${his} status as an anal slave. `;
						break;
					default:
						r += `looking sad as the meaning of a permanent display of ${his} status as an anal slave sinks in. `;
				}
				r += `After a short pause ${he} remembers ${his} duties and stammers, "T-thank you, ${Master}." After a while longer ${he} turns ${his} head away, clearly not wanting to dwell on how ${his} anus is obviously being beautified to attract sexual attention.`;
			} else {
				switch (design) {
					case "bleached":
						r += `${His} face darkens as ${he} sees ${his} crinkled fuckhole's clean, fuckable appearance. "Don't you dare fucking a${ss}rape me!" ${he} bursts out.`;
						break;
					case "flowers":
					case "tribal patterns":
						r += `${He}'s momentarily surprised by the pretty design, but then ${his} face darkens. "That'${s} permanent!" ${he} bursts out. "My a${ss}hole i${s} going to look like that forever!"`;
						break;
					case "advertisements":
						r += `${He} stares, ${his} mouth working as ${he} spells out `;
						if (slave.vagina > -1) {
							r += `'Second Pussy.' ${His} face darkens. "No it'${s} not," ${he} bursts out. "Anal is dirty."`;
						} else {
							r += `'Spit First.' ${His} face darkens. "Gro${ss}," ${he} bursts out. "Gro${ss} and wrong!"`;
						}
						break;
					case "rude words":
						r += `${He} stares, ${his} mouth working as ${he} spells out `;
						if (slave.vagina > -1) {
							r += `'Fucktoy's Favorite Fuckhole.' ${His} face darkens. "No it'${s} not," ${he} bursts out. "It'${s} my a${ss}!"`;
						} else {
							r += `'Sissy Slut's Sodomy Slit.' ${His} face darkens. "No it'${s} not," ${he} bursts out. "I'm not a ${s}i${ss}y! Or a ${s}lut! And it'${s} not a ${s}lit!" Those are debatable, but they can all change, of course.`;
						}
						break;
					case "sacrament":
					case "sacrilege":
						r += `${He} stares, ${his} mouth working as ${he} reads the ring of Aramaic around ${his} hole. ${He} doesn't understand it, but ${he}'s not happy about having stuff permanently inscribed around ${his} ass.`;
						break;
					case "degradation":
						r += `${He} stares, ${his} mouth working as ${he} spells out '${WrittenMaster(slave)} Made Me An Anal Slut.' ${His} face darkens. "No I'm not," ${he} bursts out. That remains to be seen.`;
						break;
					case "counting":
						r += `${He} stares, realizing that the markings denote how many times ${he}'s been sodomized. ${His} face darkens. "I ${s}ee there'${s} room for more," ${he} bursts out. "Fucking gro${ss}."`;
						break;
					case "bovine patterns":
						r += `${He} stares at ${his} new cow's anus. "Fucking gro${ss}," ${he} bursts out. "I'm not an animal." That remains to be seen.`;
						break;
					case "possessive":
						r += `${He} stares, realizing that the ring of numbers around ${his} anus give the date ${he} was enslaved by you. ${His} face darkens. "Wor${s}t day of my life," ${he} bursts out.`;
						break;
					case "paternalist":
						r += `${He} stares, ${his} mouth working as ${he} spells out '${WrittenMaster(slave)} Helped Me Love Anal.' "I don't," ${he} bursts out. "It'${s} fucking gro${ss}."`;
						break;
					default:
						r += `${He} stares, horrified that you have defaced ${his} body in this way.`;
				}
			}
		}
	}
	// TODO: maybe some reactions for other kinds of tattoos would be nice too?

	slave[`${location}Tat`] = design;
	return r;
};

/**
 * @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;
	}
};