code:
App.Medicine.Surgery.Reactions.FangsImplant = class extends App.Medicine.Surgery.SimpleReaction {
	reaction(slave, diff) {
		const reaction = super.reaction(slave, diff);
		const {He, he, his, him, himself} = getPronouns(slave);
		const r = [];

		if (slave.fetish === Fetish.MINDBROKEN) {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} jaw, yet fails to understand that ${his} teeth have changed shape. Since the surgery was moderately invasive, ${his} health has been somewhat affected.`);
		} else if (slave.devotion > 50) {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} jaw. It takes a good while for ${him} to figure out what has happened, but eventually ${he} gets enough sensation in ${his} mouth to realize that ${he} now has a pair of fangs. ${He} seems doubtful, but then works up ${his} courage and bares them`);
			if (canSee(slave)) {
				r.push(`at a mirror.`);
			} else {
				r.push(`so you may see.`);
			}
			r.push(`${He} menaces`);
			if (canSee(slave)) {
				r.push(`${himself}`);
			}
			r.push(`for a while before laughing quietly. Since the surgery was moderately invasive, <span class="health dec">${his} health has been somewhat affected.</span>`);
		} else if (slave.devotion > 20) {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} jaw. It takes a good while for ${him} to figure out what has happened, but eventually ${he} gets enough sensation in ${his} mouth to realize that ${he} now has teeth fit for a carnivore. ${He} gasps with shock, but eventually ${his} shoulders slump and ${he} tries to carry on. ${He} isn't much affected mentally. Since the surgery was moderately invasive, ${his} health has been somewhat affected.`);
		} else {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} jaw. It takes a good while for ${him} to figure out what has happened, but eventually ${he} gets enough sensation in ${his} mouth to realize that ${he} now has a pair of fangs. ${He} gasps with shock, accidentally gets ${his} lip in the way in ${his} anguish, bites ${himself}, and starts gagging and spitting blood. The surgical invasion has filled ${him} with horror and anger. Since the surgery was moderately invasive, ${his} health has been somewhat affected. ${He} is terribly afraid of your total power over ${his} body.`);
			reaction.trust -= 10;
			reaction.devotion -= 10;
		}

		reaction.longReaction.push(r);
		return reaction;
	}
};

App.Medicine.Surgery.Procedures.FangsImplant = class extends App.Medicine.Surgery.Procedure {
	get name() {
		if (this.originalSlave.teeth === "fang") {
			return "Add another fang";
		} else {
			return "Replace them with fangs";
		}
	}

	get healthCost() {
		return 20;
	}

	apply(cheat) {
		this._slave.teeth = "fangs";
		return this._assemble(new App.Medicine.Surgery.Reactions.FangsImplant());
	}
};


code:
App.Medicine.Surgery.Reactions.SharpTeeth = class extends App.Medicine.Surgery.SimpleReaction {
	reaction(slave, diff) {
		const reaction = super.reaction(slave, diff);
		const {He, he, his, him, himself} = getPronouns(slave);
		const r = [];

		if (slave.fetish === Fetish.MINDBROKEN) {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} jaw, yet fails to understand that ${his} teeth have changed shape. Since the surgery was invasive, ${his} health has been greatly affected.`);
		} else if (slave.devotion > 50) {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} jaw. It takes a good while for ${him} to figure out what has happened, but eventually ${he} gets enough sensation in ${his} mouth to realize that ${he} now has teeth fit for a carnivore. ${He} seems doubtful, but then works up ${his} courage and bares them`);
			if (canSee(slave)) {
				r.push(`at a mirror.`);
			} else {
				r.push(`so you may see.`);
			}
			r.push(`${He} menaces`);
			if (canSee(slave)) {
				r.push(`${himself}`);
			}
			r.push(`for a while before laughing quietly. ${He} has become more submissive due to your radical reshaping of ${his} body. Since the surgery was invasive, ${his} health has been greatly affected.`);
			reaction.devotion += 4;
		} else if (slave.devotion > 20) {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} jaw. It takes a good while for ${him} to figure out what has happened, but eventually ${he} gets enough sensation in ${his} mouth to realize that ${he} now has teeth fit for a carnivore. ${He} gasps with shock, but eventually ${his} shoulders slump and ${he} tries to carry on. ${He} isn't much affected mentally. Since the surgery was invasive, ${his} health has been greatly affected.`);
		} else {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} jaw. It takes a good while for ${him} to figure out what has happened, but eventually ${he} gets enough sensation in ${his} mouth to realize that ${he} now has teeth fit for a carnivore. ${He} gasps with shock, accidentally gets ${his} tongue in the way in ${his} anguish, bites ${himself}, and starts gagging and spitting blood. The surgical invasion has filled ${him} with horror and anger. Since the surgery was invasive, ${his} health has been greatly affected. ${He} is terribly afraid of your total power over ${his} body.`);
			reaction.trust -= 10;
			reaction.devotion -= 10;
		}

		reaction.longReaction.push(r);
		return reaction;
	}
};

App.Medicine.Surgery.Procedures.SharpTeeth = class extends App.Medicine.Surgery.Procedure {
	get name() {
		return "Replace them with sharp teeth";
	}

	get healthCost() {
		return 20;
	}

	apply(cheat) {
		this._slave.teeth = "pointy";
		return this._assemble(new App.Medicine.Surgery.Reactions.SharpTeeth());
	}
};

repo_name: Arkerthan/fc

license: mit


code:
App.Medicine.Surgery.Reactions.Teeth = class extends App.Medicine.Surgery.SimpleReaction {
	reaction(slave, diff) {
		const reaction = super.reaction(slave, diff);
		const {He, he, his, him, himself} = getPronouns(slave);
		const r = [];

		if (slave.fetish === Fetish.MINDBROKEN) {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} jaw, yet fails to understand ${his} teeth are now fake. Since the surgery was invasive, ${his} health has been greatly affected.`);
		} else if (slave.devotion > 50) {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} jaw. It takes a good while for ${him} to figure out what has happened, but eventually ${he} gets enough sensation in ${his} mouth to realize that ${he} is wearing prosthetic teeth that ${he} can remove. ${He} gasps with shock, but figures out the idea quickly enough by experimentally`);
			if (hasAnyArms(slave)) {
				r.push(`sucking on two fingers.`);
			} else {
				r.push(`simulating oral sex with ${his} own tongue.`);
			}
			r.push(`${He} has become more submissive due to your radical reshaping of ${his} body. Since the surgery was invasive, ${his} health has been greatly affected.`);
			reaction.devotion += 4;
		} else if (slave.devotion > 20) {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} jaw. It takes a good while for ${him} to figure out what has happened, but eventually ${he} gets enough sensation in ${his} mouth to realize that ${he} is wearing prosthetic teeth that ${he} can remove. ${He} gasps with shock, but eventually ${his} shoulders slump and ${he} tries to carry on. ${He} isn't much affected mentally. Since the surgery was invasive, ${his} health has been greatly affected.`);
		} else {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} jaw. It takes a good while for ${him} to figure out what has happened, but eventually ${he} gets enough sensation in ${his} mouth to realize that ${he} is wearing prosthetic teeth that ${he} can remove. ${He} gasps with shock, gags, spits out the prosthetics,`);
			if (canSee(slave)) {
				r.push(`looks at ${himself} in the mirror,`);
			} else {
				r.push(`runs ${his} tongue across ${his} empty gums,`);
			}
			r.push(`and sobs. The surgical invasion has filled ${him} with horror and anger. Since the surgery was invasive, ${his} health has been greatly affected. ${He} is terribly afraid of your total power over ${his} body.`);
			reaction.trust -= 10;
			reaction.devotion -= 10;
		}

		reaction.longReaction.push(r);
		return reaction;
	}
};

App.Medicine.Surgery.Procedures.RemovableTeeth = class extends App.Medicine.Surgery.Procedure {
	get name() {
		return "Replace them with removable prosthetics";
	}

	get healthCost() {
		return 20;
	}

	apply(cheat) {
		this._slave.teeth = "removable";
		return this._assemble(new App.Medicine.Surgery.Reactions.Teeth());
	}
};


code:
App.Medicine.Surgery.Reactions.Electrolarynx = class extends App.Medicine.Surgery.SimpleReaction {
	reaction(slave, diff) {
		const reaction = super.reaction(slave, diff);
		const {He, he, his, him} = getPronouns(slave);
		const r = [];

		if (slave.fetish === Fetish.MINDBROKEN) {
			r.push(`Before surgery, ${he} was warned repeatedly not to try talking for a while, but ${he} fails to recall that. ${He} doesn't notice any difference.`);
		} else if (slave.voice === 0) {
			if (slave.devotion > 50) {
				r.push(`The autosurgery instructed ${him} in no uncertain terms not to speak during recovery, so ${he} knows that ${his} voice has been restored. When you come in to check on ${him} late in the process, though, ${he} does ${his} best to communicate adoration with ${his}`);
				if (canSee(slave)) {
					r.push(`eyes`);
				} else {
					r.push(`face`);
				}
				r.push(`alone. Finally, the accelerated healing process reaches the time when ${he}'s to test ${his} new vocal organs. ${He}`);
				if (canSee(slave)) {
					r.push(`looks at`);
				} else {
					r.push(`turns to`);
				}
				r.push(`you shyly, and`);
				if (hasAnyArms(slave)) {
					r.push(`meekly beckons to you from where ${he}'s still`);
				} else {
					r.push(`meekly mouths a request that you come over to where ${he}'s`);
				}
				r.push(`restrained by the autosurgery. When you're near, ${he} clears ${his} sore throat gingerly and then whispers, "I love you, ${getWrittenTitle(slave)}." ${He} is far more trusting of your plans for ${him} in the future.`);

				reaction.trust += 5;
				reaction.devotion += 5;
			} else if (slave.devotion >= -20) {
				r.push(`The autosurgery instructed ${him} in no uncertain terms not to speak during recovery, so ${he} knows that ${his} voice has been restored. When you come in to check on ${him} late in the process, though, ${he} does ${his} best to communicate gratitude with ${his}`);
				if (canSee(slave)) {
					r.push(`eyes`);
				} else {
					r.push(`face`);
				}
				r.push(`alone. Finally, the accelerated healing process reaches the time when ${he}'s to test ${his} new vocal organs. ${He}`);
				if (canSee(slave)) {
					r.push(`looks at`);
				} else {
					r.push(`turns to`);
				}
				r.push(`you shyly, and hesitantly`);
				if (hasAnyArms(slave)) {
					r.push(`beckons to you from where ${he}'s still`);
				} else {
					r.push(`mouths a request that you come over to where ${he}'s`);
				}
				r.push(`restrained by the autosurgery. When you're near, ${he} clears ${his} sore throat gingerly and then whispers, "Thank you, ${getWrittenTitle(slave)}." ${He} has a much higher regard for you due to this display of care for ${him}.`);
				reaction.devotion += 10;
			} else {
				r.push(`The autosurgery instructed ${him} in no uncertain terms not to speak during recovery, so ${he} knows that ${his} voice has been restored. When you come in to check on ${him} late in the process, though, ${he}`);
				if (canSee(slave)) {
					r.push(`stares at`);
				} else {
					r.push(`faces`);
				}
				r.push(`you for a long moment before turning away from you, towards the wall, and beginning to cry quietly. It seems ${he}'s surprised that you would do this for ${him}, and does not understand how to deal with the situation. In any case, ${he} hates you far less than ${he} once did.`);
				reaction.devotion += 10;
			}
		} else {
			if (slave.devotion > 50) {
				r.push(`Before surgery, ${he} was warned repeatedly not to try talking for a while, and ${he} obeys. When ${he} finally does, ${his} voice is raspy and weak, but it gains strength gradually. While ${he} first believes nothing has changed,`);
				if (canHear(slave)) {
					r.push(`${he} soon notices a slight synthetic tone to ${his} voice and realizes ${he} has been given a mechanical larynx.`);
				} else {
					r.push(`the autosurgery informs ${him} in no uncertain terms that ${he} has implanted with a mechanical larynx.`);
				}
				r.push(`${He} is <span class="devotion inc">appreciative</span> of ${his} new implant, and knowing how much you invested in ${him} makes ${him} <span class="trust inc">trust you more</span> as well.`);
				reaction.devotion += 10;
				reaction.trust += 10;
			} else if (slave.devotion > 20) {
				r.push(`Before surgery, ${he} was warned repeatedly not to try talking for a while, and ${he} obeys. When ${he} finally does, ${his} voice is raspy and weak, but it gains strength gradually. While ${he} first believes nothing has changed,`);
				if (canHear(slave)) {
					r.push(`${he} soon notices a slight synthetic tone to ${his} voice and realizes ${he} has been given a mechanical larynx.`);
				} else {
					r.push(`the autosurgery informs ${him} in no uncertain terms that ${he} has implanted with a mechanical larynx.`);
				}
				r.push(`${He} has mixed feelings about this development, but ${he}'s aware how valuable such implants are, and ${he} already accepted that you have complete control over ${his} body.`);
				reaction.devotion += 5;
				reaction.trust += 10;
			} else {
				r.push(`Before surgery, ${he} was warned repeatedly not to try talking for a while, and ${he} obeys. When ${he} finally does, ${his} voice is raspy and weak, but it gains strength gradually. While ${he} first believes nothing has changed,`);
				if (canHear(slave)) {
					r.push(`${he} soon notices a slight synthetic tone to ${his} voice and realizes ${he} has been given a mechanical larynx.`);
				} else {
					r.push(`the autosurgery informs ${him} in no uncertain terms that ${he} has implanted with a mechanical larynx.`);
				}
				r.push(`${He} is <span class="trust dec">disturbed</span> that you replaced ${his} larynx with an artificial one and afraid of increased control over ${him} that such device grants.`);
				reaction.devotion -= 5;
			}
		}
		r.push(`As with all surgery <span class="health dec">${his} health has been slightly affected.</span>`);

		reaction.longReaction.push(r);
		return reaction;
	}
};

App.Medicine.Surgery.Procedures.Electrolarynx = class extends App.Medicine.Surgery.Procedure {
	get name() {
		const {him} = getPronouns(this.originalSlave);
		return `Give ${him} an electrolarynx`;
	}

	get healthCost() {
		return 20;
	}

	apply(cheat) {
		this._slave.electrolarynx = 1;
		this._slave.voice = 2;
		return this._assemble(new App.Medicine.Surgery.Reactions.Electrolarynx());
	}
};


code:
App.Medicine.Surgery.Reactions.Mute = class extends App.Medicine.Surgery.SimpleReaction {
	reaction(slave, diff) {
		const reaction = super.reaction(slave, diff);
		const {He, he, his, him, himself} = getPronouns(slave);
		const r = [];

		if (slave.fetish === Fetish.MINDBROKEN) {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} larynx. It takes a good while for ${him} to figure out what has happened, but eventually ${he} tries to speak and makes no sound. ${He} simply keeps trying for some time before accepting this is how things are. As with all surgery ${his} health has been slightly affected.`);
		} else if (slave.devotion > 50) {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} larynx. It takes a good while for ${him} to figure out what has happened, but eventually ${he} tries to speak and makes no sound. ${He} panics, but manages to control ${himself} at length and begins to think.`);
			if (canSee(slave)) {
				r.push(`Seeing`);
			} else {
				r.push(`Finding`);
			}
			r.push(`you, ${he} tries to ask you for instruction and realizes ${he} cannot.`);
			if (hasBothLegs(slave)) {
				r.push(`After more thought, ${he} simply gets on ${his} knees facing away from you and spreads ${his} buttocks to offer ${himself} to you.`);
			} else {
				r.push(`After more thought, ${he} simply rolls over facing away from you and winks ${his} asshole to offer ${himself} to you.`);
			}
			r.push(`${He} has become more submissive due to your radical reshaping of ${his} being. As with all surgery ${his} health has been slightly affected.`);
			reaction.devotion += 4;
		} else if (slave.devotion > 20) {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} larynx. It takes a good while for ${him} to figure out what has happened, but eventually ${he} tries to speak and makes no sound. ${He} panics, trying to scream or protest but only managing to cry. Eventually ${he} pulls ${himself} together. ${He} will struggle with ${his} medically created disability. As with all surgery ${his} health has been slightly affected. ${He} is terribly afraid of your total power over ${his} body.`);
			reaction.trust -= 10;
			reaction.devotion -= 10;
		} else {
			r.push(`${He} clearly feels quite normal except for a vague ache around ${his} larynx. It takes a good while for ${him} to figure out what has happened, but eventually ${he} tries to speak and makes no sound. ${He} panics, trying to scream or protest but only managing to cry. As long as ${he} remains unbent to your will, ${he} will have a constant, inescapable reminder of who and what you are. The surgical invasion has filled ${him} with horror and anger. As with all surgery ${his} health has been slightly affected. ${He} is cripplingly afraid of your total power over ${his} body.`);
			reaction.trust -= 20;
			reaction.devotion -= 50;
		}

		reaction.longReaction.push(r);
		return reaction;
	}

	outro(slave, diff, previousReaction) {
		const reaction = super.outro(slave, diff, previousReaction);
		const {He} = getPronouns(slave);

		if (slave.behavioralFlaw === BehavioralFlaw.BITCHY) {
			const r = `<span class="green">${He} can hardly make sharp remarks without a voice.</span>`;
			if (reaction.longReaction.length > 0) {
				reaction.longReaction.last()
					.push(r);
			} else {
				reaction.longReaction.push([r]);
			}
			slave.behavioralFlaw = BehavioralFlaw.NONE;
		}

		return reaction;
	}
};

App.Medicine.Surgery.Procedures.Mute = class extends App.Medicine.Surgery.Procedure {
	get name() {
		return "Remove vocal cords";
	}

	get healthCost() {
		return 10;
	}

	apply(cheat) {
		surgeryAmp(this._slave, "voicebox");
		return this._assemble(new App.Medicine.Surgery.Reactions.Mute());
	}
};


code:
App.Medicine.Surgery.Reactions.NewVoice = class extends App.Medicine.Surgery.SimpleReaction {
	reaction(slave, diff) {
		const reaction = super.reaction(slave, diff);
		const {He, he, his, him} = getPronouns(slave);
		const r = [];

		if (slave.fetish === Fetish.MINDBROKEN) {
			r.push(`The autosurgery instructed ${him} in no uncertain terms not to speak during recovery, but ${he} fails to recall that. ${He} doesn't notice anything different, though.`);
		} else {
			r.push(`Before surgery, ${he} was warned repeatedly not to try talking for a while, and ${he} obeys. When ${he} finally does, ${his} voice is raspy and weak, but as it gradually gains strength ${he} notices that it sounds more natural and human than ${his} old electrolarynx.`);
		}
		r.push(`As with all surgery <span class="health dec">${his} health has been slightly affected.</span>`);

		reaction.longReaction.push(r);
		return reaction;
	}
};

App.Medicine.Surgery.Procedures.OFNewVoice = class extends App.Medicine.Surgery.Procedure {
	get name() {
		return "Replace";
	}

	get description() {
		return "remove electrolarynx and implant organic vocal cords";
	}

	get healthCost() {
		return 20;
	}

	apply(cheat) {
		this._slave.electrolarynx = 0;
		if (this._slave.ovaries === 1 && this._slave.hormoneBalance >= 200) {
			this._slave.voice = 3;
		} else if (this._slave.balls > 0 || this._slave.hormoneBalance < -20) {
			this._slave.voice = 1;
		} else {
			this._slave.voice = 2;
		}

		return this._assemble(new App.Medicine.Surgery.Reactions.NewVoice());
	}
};


code:
App.Medicine.Surgery.Reactions.RestoreVoice = class extends App.Medicine.Surgery.SimpleReaction {
	reaction(slave, diff) {
		const reaction = super.reaction(slave, diff);
		const {He, he, his, him} = getPronouns(slave);
		const r = [];

		if (slave.fetish === Fetish.MINDBROKEN) {
			r.push(`The autosurgery instructed ${him} in no uncertain terms not to speak during recovery, but ${he} fails to recall that. ${He} doesn't any effort to speak either, though.`);
		} else if (slave.devotion > 50) {
			r.push(`The autosurgery instructed ${him} in no uncertain terms not to speak during recovery, so ${he} knows that ${his} voice has been restored. When you come in to check on ${him} late in the process, though, ${he} does ${his} best to communicate adoration with ${his}`);
			if (canSee(slave)) {
				r.push(`eyes`);
			} else {
				r.push(`face`);
			}
			r.push(`alone. Finally, the accelerated healing process reaches the time when ${he}'s to test ${his} new vocal organs. ${He}`);
			if (canSee(slave)) {
				r.push(`looks at`);
			} else {
				r.push(`turns to`);
			}
			r.push(`you shyly, and`);
			if (hasAnyArms(slave)) {
				r.push(`meekly beckons to you from where ${he}'s still`);
			} else {
				r.push(`meekly mouths a request that you come over to where ${he}'s`);
			}
			r.push(`restrained by the autosurgery. When you're near, ${he} clears ${his} sore throat gingerly and then whispers, "I love you, ${getWrittenTitle(slave)}." ${He} is far more trusting of your plans for ${him} in the future.`);

			reaction.trust += 5;
			reaction.devotion += 5;
		} else if (slave.devotion >= -20) {
			r.push(`The autosurgery instructed ${him} in no uncertain terms not to speak during recovery, so ${he} knows that ${his} voice has been restored. When you come in to check on ${him} late in the process, though, ${he} does ${his} best to communicate gratitude with ${his}`);
			if (canSee(slave)) {
				r.push(`eyes`);
			} else {
				r.push(`face`);
			}
			r.push(`alone. Finally, the accelerated healing process reaches the time when ${he}'s to test ${his} new vocal organs. ${He}`);
			if (canSee(slave)) {
				r.push(`looks at`);
			} else {
				r.push(`turns to`);
			}
			r.push(`you shyly, and hesitantly`);
			if (hasAnyArms(slave)) {
				r.push(`beckons to you from where ${he}'s still`);
			} else {
				r.push(`mouths a request that you come over to where ${he}'s`);
			}
			r.push(`restrained by the autosurgery. When you're near, ${he} clears ${his} sore throat gingerly and then whispers, "Thank you, ${getWrittenTitle(slave)}." ${He} has a much higher regard for you due to this display of care for ${him}.`);
			reaction.devotion += 10;
		} else {
			r.push(`The autosurgery instructed ${him} in no uncertain terms not to speak during recovery, so ${he} knows that ${his} voice has been restored. When you come in to check on ${him} late in the process, though, ${he}`);
			if (canSee(slave)) {
				r.push(`stares at`);
			} else {
				r.push(`faces`);
			}
			r.push(`you for a long moment before turning away from you, towards the wall, and beginning to cry quietly. It seems ${he}'s surprised that you would do this for ${him}, and does not understand how to deal with the situation. In any case, ${he} hates you far less than ${he} once did.`);
			reaction.devotion += 10;
		}
		r.push(`As with all surgery <span class="health dec">${his} health has been slightly affected.</span>`);

		reaction.longReaction.push(r);
		return reaction;
	}
};

App.Medicine.Surgery.Procedures.OFRestoreVoice = class extends App.Medicine.Surgery.Procedure {
	get name() {
		return "Implant";
	}

	get healthCost() {
		return 10;
	}

	apply(cheat) {
		if (this._slave.ovaries === 1 && this._slave.hormoneBalance >= 200) {
			this._slave.voice = 3;
		} else if (this._slave.balls > 0 || this._slave.hormoneBalance < -20) {
			this._slave.voice = 1;
		} else {
			this._slave.voice = 2;
		}
		return this._assemble(new App.Medicine.Surgery.Reactions.RestoreVoice());
	}
};


code:
App.Medicine.Surgery.Reactions.VoiceLower = class extends App.Medicine.Surgery.SimpleReaction {
	reaction(slave, diff) {
		const reaction = super.reaction(slave, diff);
		const {He, he, his, him, himself, hers} = getPronouns(slave);
		const r = [];

		if (slave.fetish === Fetish.MINDBROKEN) {
			r.push(`The autosurgery instructed ${him} in no uncertain terms not to speak during recovery, but ${he} fails to recall that. ${He} doesn't notice anything different. As with all surgery ${his} health has been slightly affected.`);
		} else if (slave.devotion > 50) {
			r.push(`Before surgery, ${he} was warned repeatedly not to try talking for a while, and ${he} obeys. When ${he} finally does, ${his} voice is raspy and weak, but it gains strength gradually. It comes out`);
			if (diff.voice === 1) {
				r.push(`far`);
			}
			r.push(`lower and more manly than it was before, and ${he} laughs at ${himself} as ${he} gets used to it. ${He} has become more dominant, since this allows ${him} to think of ${himself} as`);
			if (diff.voice === 1) {
				r.push(`an action movie hero.`);
			} else if (diff.voice === 2) {
				r.push(`a mature career woman.`);
			}
			r.push(`As with all surgery <span class="health dec">${his} health has been slightly affected.</span>`);
			reaction.devotion += 4;
		} else if (slave.devotion > 20) {
			r.push(`Before surgery, ${he} was warned repeatedly not to try talking for a while, and ${he} obeys. When ${he} finally does, ${his} voice is raspy and weak, but it gains strength gradually. It comes out`);
			if (diff.voice === 1) {
				r.push(`far`);
			}
			r.push(`lower and more manly than it was before, and ${he} laughs grimly at ${himself} as ${he} gets used to it. ${He} carries on regardless, accepting it as one more thing to learn to accept. As with all surgery ${his} health has been slightly affected.`);
		} else {
			r.push(`Before surgery, ${he} was warned repeatedly not to try talking for a while, and ${he} obeys. When ${he} finally does, ${his} voice is raspy and weak, but it gains strength gradually. It comes out`);
			if (diff.voice === 1) {
				r.push(`far`);
			}
			r.push(`lower and more manly than it was before. For now, <span class="devotion dec">${he} feels this`);
			if (diff.voice === 1) {
				r.push(`gravelly`);
			} else if (diff.voice === 2) {
				r.push(`subdued`);
			}
			r.push(`voice is not ${hers}, a cruel mockery. As with all surgery ${his} health has been slightly affected. ${He} is now terribly afraid of your total power over ${his} body.`);
			reaction.trust -= 10;
			reaction.devotion -= 5;
		}

		reaction.longReaction.push(r);
		return reaction;
	}
};

App.Medicine.Surgery.Procedures.VoiceLower = class extends App.Medicine.Surgery.Procedure {
	get name() {
		return "Perform surgery to lower voice";
	}

	get healthCost() {
		return 10;
	}

	apply(cheat) {
		this._slave.voice -= 1;
		this._slave.voiceImplant -= 1;
		return this._assemble(new App.Medicine.Surgery.Reactions.VoiceLower());
	}
};


code:
App.Medicine.Surgery.Reactions.VoiceRaise = class extends App.Medicine.Surgery.SimpleReaction {
	reaction(slave, diff) {
		const reaction = super.reaction(slave, diff);
		const {He, he, his, him, himself, hers} = getPronouns(slave);
		const r = [];

		if (slave.fetish === Fetish.MINDBROKEN) {
			r.push(`The autosurgery instructed ${him} in no uncertain terms not to speak during recovery, but ${he} fails to recall that. ${He} doesn't notice anything different. As with all surgery ${his} health has been slightly affected.`);
		} else if (slave.devotion > 50) {
			r.push(`Before surgery, ${he} was warned repeatedly not to try talking for a while, and ${he} obeys. When ${he} finally does, ${his} voice is raspy and weak, but it gains strength gradually. It comes out`);
			if (diff.voice === 3) {
				r.push(`far`);
			}
			r.push(`higher and more girly than it was before, and ${he} laughs at ${himself} as ${he} gets used to it. ${He} has become more submissive, since this helps ${him} to think of ${himself} as a`);
			if (diff.voice === 3) {
				r.push(`bimbo slut.`);
			} else {
				r.push(`real woman.`);
			}
			r.push(`As with all surgery <span class="health dec">${his} health has been slightly affected.</span>`);
			reaction.devotion += 4;
		} else if (slave.devotion > 20) {
			r.push(`Before surgery, ${he} was warned repeatedly not to try talking for a while, and ${he} obeys. When ${he} finally does, ${his} voice is raspy and weak, but it gains strength gradually. It comes out`);
			if (diff.voice === 3) {
				r.push(`far`);
			}
			r.push(`higher and more girly than it was before, and ${he} laughs grimly at ${himself} as ${he} gets used to it. ${He} carries on regardless, accepting it as one more thing to learn to accept. As with all surgery ${his} health has been slightly affected.`);
		} else {
			r.push(`Before surgery, ${he} was warned repeatedly not to try talking for a while, and ${he} obeys. When ${he} finally does, ${his} voice is raspy and weak, but it gains strength gradually. It comes out`);
			if (diff.voice === 3) {
				r.push(`far`);
			}
			r.push(`higher and more girly than it was before. For now, <span class="devotion dec">${he} feels this`);
			if (diff.voice === 3) {
				r.push(`bimbo`);
			} else {
				r.push(`feminine`);
			}
			r.push(`voice is not ${hers}, a cruel mockery. As with all surgery ${his} health has been slightly affected. ${He} is now terribly afraid of your total power over ${his} body.`);
			reaction.trust -= 10;
			reaction.devotion -= 5;
		}

		reaction.longReaction.push(r);
		return reaction;
	}
};

App.Medicine.Surgery.Procedures.VoiceRaise = class extends App.Medicine.Surgery.Procedure {
	get name() {
		return "Perform surgery to raise voice";
	}

	get healthCost() {
		return 10;
	}

	apply(cheat) {
		this._slave.voice += 1;
		this._slave.voiceImplant += 1;
		return this._assemble(new App.Medicine.Surgery.Reactions.VoiceRaise());
	}
};


code:
App.Medicine.pocketVolume = function() {
	/**
	 * @param {FC.HumanState} slave
	 * @returns {number}
	 */
	function boobs(slave) {
		/*
		The breast is approximated by a spherical cap with the volume of slave.boobs.
		The radius `a` of the cap is proportional to slave height and shoulders width
		We assume that the skin has elasticity coefficient that depends on slave physical age.
		Max pocket volume is therefore the volume of the cap with the same base but the
		surface stretched by the skin elasticity coefficient
		V = 1/6*πh(3a²+h²)
		A = π(a²+h²)
		*/
		const a = 0.05 * slave.height + slave.shoulders * 1.1;
		/* now the eq. for V we rewrite as
		h³ + 3a³h -6/π*V = 0
		and replace:
		p = 3*a³, q = -6/π*V:
		h³ + ph + q = 0
		*/
		const p = 3 * a * a;
		const q = -6. / Math.PI * slave.boobs;
		const Q = Math.pow(p / 3, 3) + Math.pow(q / 2, 2);
		console.assert(Q > 0);
		const h = Math.cbrt(-q / 2 + Math.sqrt(Q)) + Math.cbrt(-q / 2 - Math.sqrt(Q));

		// now the current skin area:
		const A = Math.PI * (a * a + h * h);

		const skinStretchFactor = linearInterpolation(Math.clamp(slave.physicalAge, 13, 45), 13, 1.5, 45, 1.15);

		const hMax = Math.sqrt(A * skinStretchFactor / Math.PI - a * a);
		const VMax = Math.PI / 6 * hMax * (3 * a * a + hMax * hMax);

		return VMax;
	}

	/**
	 * @param {FC.HumanState} slave
	 * @returns {number}
	 */
	function butt(slave) {
		return slave.butt + (slave.hips > 0 ? 2 : 1);
	}

	/**
	 * @param {FC.HumanState} slave
	 * @returns {number}
	 */
	function lips(slave) {
		return slave.lips + 10;
	}

	return {
		boobs,
		butt,
		lips
	};
}();

/**
 * @template {FC.SizingImplantTarget} T
 * @param {FC.HumanState} slave
 * @param {T} target
 * @returns {FC.Medicine.ImplantInfo<T>}
 */
App.Medicine.implantInfo = function(slave, target) {
	/**
	 * @template {"boobs"|"butt"} Target
	 * @param {Target} target
	 * @returns FC.Medicine.ImplantInfo<Target>
	 */
	function boobsButt(target) {
		return {
			type: slave[`${target}ImplantType`],
			volume: slave[`${target}Implant`]
		};
	}

	switch (target) {
		case "boobs":
		case "butt":
			return /** @type {FC.Medicine.ImplantInfo<T>} */(boobsButt(target));
		case "lips":
			return {
				type: slave.lipsImplant > 0 ? "normal" : "none",
				volume: slave.lipsImplant
			};
	}
};

/**
 * @template {FC.SizingImplantTarget} T
 * @param {T} target
 * @returns {Array<FC.BodyPartImplantTypeMap[T]>}
 */
App.Medicine.implantTypesForTarget = function(target) {
	return Object.keys(App.Data.Medicine.sizingImplants[target]);
};

/**
 * Returns the size of the flesh part of the sizable body part
 *
 * "Flesh" means without implants and without milk in case of boobs
 * @param {FC.HumanState} slave // or genepool objects!
 * @param {FC.SizableBodyPart} part
 * @returns {number}
 */
App.Medicine.fleshSize = function(slave, part) {
	switch (part) {
		case "balls": return slave.balls;
		case "boobs": {
			const milkVolume = isFinite(slave.boobsMilk) ? slave.boobsMilk : 0; // genepool objects don't have boobsMilk
			return slave.boobs - slave.boobsImplant - milkVolume;
		}
		case "butt": return slave.butt - slave.buttImplant;
		case "dick": return slave.dick;
		case "lips": return slave.lips - slave.lipsImplant;
	}
};

/**
 * Returns the size of the flesh part of the sizable body part
 * @param {FC.HumanState} slave
 * @param {FC.SizableBodyPart} part
 * @returns {number}
 */
App.Medicine.assetSize = function(slave, part) {
	return slave[part];
};

/**
 * @param {FC.SizableBodyPart} part
 * @returns {number}
 */
App.Medicine.maxAssetSize = function(part) {
	switch (part) {
		case "balls": return 125;
		case "boobs": return 50000;
		case "butt": return 20;
		case "dick": return 31;
		case "lips": return V.seeExtreme ? 96 : 86;
	}
};


code:
App.Medicine.OrganFarm.Organ = class {
	/**
	 * @param {object} params
	 * @param {string} params.name - display name
	 * @param {string|function(App.Entity.SlaveState):string} [params.tooltip] - full sentence, uncapitalized and unpunctuated
	 * @param {number} params.cost - how much it costs to grow the organ
	 * @param {number} params.time - how long it takes to grow the organ (without upgrades)
	 * @param {function(App.Entity.SlaveState):boolean} [params.canGrow]
	 * @param {string[]} [params.dependencies] - organs that are implanted first if possible, use type of other organs as values
	 * @param {App.Medicine.OrganFarm.OrganImplantAction[]} [params.actions]
	 * @param {boolean} [params.displayMultipleActions=false] allow multiple implant links to be displayed
	 */
	constructor({
		name, tooltip = "", cost, time, canGrow = () => true, dependencies = [],
		displayMultipleActions = false, actions = []
	}) {
		this.name = name;
		this.tooltip = tooltip;
		this.cost = cost;
		this.time = time;
		/** @type {function(App.Entity.SlaveState):boolean} */
		this.canGrow = canGrow;
		/** @type {string[]} */
		this.dependencies = dependencies;
		this.displayMultipleActions = displayMultipleActions;
		/** @type {App.Medicine.OrganFarm.OrganImplantAction[]} */
		this.implantActions = actions;
	}
};

App.Medicine.OrganFarm.OrganImplantAction = class {
	/**
	 * @param {object} params
	 * @param {string} params.name - display name, shown when showing error (otherwise shows procedure name)
	 * @param {typeof App.Medicine.Surgery.Procedure} params.surgeryProcedure - surgery reaction to show
	 * @param {Array} [params.surgeryProcedureParams] - parameters for surgeryProcedure, the slave argument will be added to the front automatically
	 * @param {boolean} [params.autoImplant]
	 * @param {function(App.Entity.SlaveState):boolean} params.canImplant
	 * @param {function(App.Entity.SlaveState):string} params.implantError - message to show if this action cannot be used
	 */
	constructor({
		name,
		surgeryProcedure,
		surgeryProcedureParams = [],
		autoImplant = true,
		canImplant,
		implantError
	}) {
		this.name = name;
		this.surgeryProcedure = surgeryProcedure;
		this.surgeryProcedureParams = surgeryProcedureParams;
		this.autoImplant = autoImplant;
		/**
		 * True if this action can implant the organ
		 * @type {function(App.Entity.SlaveState):boolean}
		 */
		this.canImplant = canImplant;
		/**
		 * Error message if the organ cannot be implanted.
		 * @type {function(App.Entity.SlaveState):string}
		 */
		this.implantError = implantError;
	}
};


code:
App.Medicine.OrganFarm.TesticlesImplantAction = class extends App.Medicine.OrganFarm.OrganImplantAction {
	/**
	 * @param {object} params
	 * @param {string} params.name
	 * @param {boolean} params.animal
	 * @param {typeof App.Medicine.Surgery.Procedure} params.surgeryProcedure
	 * @param {Array} [params.surgeryProcedureParams]
	 * @param {boolean} [params.autoImplant]
	 * @param {function(App.Entity.SlaveState):boolean} params.canImplant
	 * @param {function(App.Entity.SlaveState):string} params.implantError
	 */
	constructor({
		name,
		animal,
		surgeryProcedure,
		surgeryProcedureParams,
		autoImplant = true,
		canImplant,
		implantError,
	}) {
		super({
			name: name,
			surgeryProcedure: surgeryProcedure,
			surgeryProcedureParams: surgeryProcedureParams,
			autoImplant: autoImplant,
			canImplant: s => (!this.animal || V.animalTesticles > 0) && canImplant(s),
			implantError: s => (this.animal && V.animalTesticles === 0) ? "" : implantError(s),
		});
		this.animal = animal;
	}
};

App.Medicine.OrganFarm.Testicles = class extends App.Medicine.OrganFarm.Organ {
	/**
	 * @param {object} params
	 * @param {string} params.name
	 * @param {FC.AnimalType} params.ballType
	 */
	constructor({name, ballType}) {
		super({
			name: name,
			tooltip: "will add a prostate if one is not already present; requires a penis for successful implantation",
			cost: 5000, time: 10,
			canGrow: () => (this.ballType === "human" || V.animalTesticles > 0), dependencies: ["penis"],
			actions: [
				new App.Medicine.OrganFarm.TesticlesImplantAction({
					name: "Implant", animal: ballType !== "human",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFAddBalls,
					surgeryProcedureParams: [ballType],
					canImplant: slave => (slave.balls <= 0 && slave.dick > 0),
					implantError: slave => {
						if (slave.dick === 0) {
							return "This slave lacks the penis necessary to accept testicles.";
						} else {
							return "This slave already has testicles.";
						}
					},
				}),
				new App.Medicine.OrganFarm.TesticlesImplantAction({
					name: "Implant",
					animal: ballType !== "human", autoImplant: false,
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFAddTesticles,
					surgeryProcedureParams: [ballType],
					canImplant: slave => (slave.dick === 0 && slave.balls <= 0),
					implantError: slave => ((slave.balls > 0) ? "This slave already has testicles." : ""),
				}),
				new App.Medicine.OrganFarm.TesticlesImplantAction({
					name: "Replace",
					animal: ballType !== "human", autoImplant: false,
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFReplaceTesticles,
					surgeryProcedureParams: [ballType],
					canImplant: slave => (slave.balls > 0 && slave.ballType !== this.ballType),
					implantError: slave => (slave.balls > 0 ? `This slave already has ${this.ballType} testicles.` : ""),

				})
			]
		});
		this.ballType = ballType;
	}
};

App.Medicine.OrganFarm.OvariesImplantAction = class extends App.Medicine.OrganFarm.OrganImplantAction {
	/**
	 * @param {object} params
	 * @param {string} params.name
	 * @param {boolean} params.animal
	 * @param {typeof App.Medicine.Surgery.Procedure} params.surgeryProcedure
	 * @param {Array} [params.surgeryProcedureParams]
	 * @param {boolean} [params.autoImplant]
	 * @param {function(App.Entity.SlaveState):boolean} params.canImplant
	 * @param {function(App.Entity.SlaveState):string} params.implantError
	 */
	constructor({
		name,
		animal,
		surgeryProcedure,
		surgeryProcedureParams,
		autoImplant = true,
		canImplant,
		implantError
	}) {
		super({
			name: name, autoImplant: autoImplant,
			surgeryProcedure: surgeryProcedure,
			surgeryProcedureParams: surgeryProcedureParams,
			canImplant: s => (!this.animal || V.animalTesticles > 0) && canImplant(s),
			implantError: s => (this.animal && V.animalTesticles === 0) ? "" : implantError(s),
		});
		this.animal = animal;
	}
};

App.Medicine.OrganFarm.Ovaries = class extends App.Medicine.OrganFarm.Organ {
	/**
	 * @param {object} params
	 * @param {string} params.name
	 * @param {FC.AnimalType} params.eggType
	 * @param {string} params.pregData
	 */
	constructor({name, eggType, pregData}) {
		super({
			name: name, tooltip: "requires a vagina for successful implantation",
			cost: 10000, time: 10,
			canGrow: () => (this.eggType === "human" || V.animalOvaries > 0),
			actions: [
				new App.Medicine.OrganFarm.OvariesImplantAction({
					name: "Implant", animal: eggType !== "human",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFAddOvaries,
					surgeryProcedureParams: [eggType, pregData],
					canImplant: s => (s.vagina >= 0 && s.ovaries <= 0 && s.mpreg === 0 && s.bellyImplant === -1),
					implantError: s => {
						if (s.vagina < 0) {
							return "This slave lacks the vagina necessary to accept ovaries.";
						}
						if (s.ovaries > 0 || s.mpreg > 0) {
							return "This slave already has ovaries.";
						}
						return "This slave's body cavity is filled with another organ.";
					},
				}),
				new App.Medicine.OrganFarm.OvariesImplantAction({
					name: "Replace", animal: eggType !== "human",
					autoImplant: false,
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFReplaceOvaries,
					surgeryProcedureParams: [eggType, pregData],
					canImplant: s => (s.vagina >= 0 && s.mpreg === 0 && s.bellyImplant === -1 && (s.eggType !== this.eggType || s.preg === -3)),
					implantError: s => (s.eggType === this.eggType) ? `This slave already has ${s.eggType} ovaries.` : "",
				})
			]
		});
		this.eggType = eggType;
		this.pregData = pregData;
	}
};

App.Medicine.OrganFarm.AnalWombImplantAction = class extends App.Medicine.OrganFarm.OrganImplantAction {
	/**
	 * @param {object} params
	 * @param {FC.AnimalType} params.eggType
	 * @param {string} params.pregData
	 */
	constructor({eggType, pregData}) {
		super({
			name: "Implant",
			surgeryProcedure: App.Medicine.Surgery.Procedures.OFAddMPreg,
			surgeryProcedureParams: [eggType, pregData],
			canImplant: s => ((this.eggType === "human" || V.animalTesticles > 0) && s.ovaries === 0 && s.vagina <= -1 && s.mpreg === 0 && s.bellyImplant < 0),
			implantError: s => {
				if (this.eggType !== "human" && V.animalTesticles === 0) {
					return "";
				}
				if (s.bellyImplant >= 0) {
					return "This slave has a fillable abdominal implant.";
				}
				return "This slave has existing reproductive Organs.";
			},
		});
		this.eggType = eggType;
		this.pregData = pregData;
	}
};

App.Medicine.OrganFarm.AnalWomb = class extends App.Medicine.OrganFarm.Organ {
	/**
	 * @param {object} params
	 * @param {string} params.name
	 * @param {FC.AnimalType} params.eggType
	 * @param {string} params.pregData
	 */
	constructor({name, eggType, pregData}) {
		super({
			name: name,
			tooltip: "the slave must not have female reproductive organs for successful implantation",
			cost: 20000, time: 10,
			canGrow: () => (V.arcologies[0].FSGenderRadicalistResearch === 1 && (eggType === "human" || V.animalMpreg > 0)),
			actions: [
				new App.Medicine.OrganFarm.AnalWombImplantAction({
					eggType: eggType,
					pregData: pregData
				})
			]
		});
	}
};


code:
/**
 * Organs will usually be displayed in this order.
 *
/** @type {Map<string, App.Medicine.OrganFarm.Organ>} */
App.Medicine.OrganFarm.Organs = new Map([
	["penis",
		new App.Medicine.OrganFarm.Organ({
			name: "Penis", cost: 5000, time: 5,
			canGrow: () => V.seeDicks !== 0 || V.makeDicks === 1,
			actions: [
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Implant",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFAddDick,
					canImplant: slave => slave.dick <= 0,
					implantError: () => "this slave already has a penis",
				})
			]
		})],
	["testicles",
		new App.Medicine.OrganFarm.Testicles({
			name: "Testicles", ballType: "human"
		})],
	["pigTesticles",
		new App.Medicine.OrganFarm.Testicles({
			name: "Pig testicles", ballType: "pig"
		})],
	["dogTesticles",
		new App.Medicine.OrganFarm.Testicles({
			name: "Dog testicles", ballType: "dog"
		})],
	["horseTesticles",
		new App.Medicine.OrganFarm.Testicles({
			name: "Horse testicles", ballType: "horse"
		})],
	["cowTesticles",
		new App.Medicine.OrganFarm.Testicles({
			name: "Cow testicles", ballType: "cow"
		})],
	["scrotum",
		new App.Medicine.OrganFarm.Organ({
			name: "Scrotum", tooltip: "requires balls for successful implantation", cost: 2500, time: 5,
			dependencies: ["testicles", "pigTesticles", "dogTesticles", "horseTesticles", "cowTesticles"],
			actions: [
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Graft on",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFAddScrotum,
					canImplant: slave => slave.scrotum <= 0 && slave.balls > 0,
					implantError: slave => slave.scrotum > 0 ? "This slave already has a scrotum." : "This slave lacks the balls necessary to accept a scrotum.",
				})
			]
		})],
	["foreskin",
		new App.Medicine.OrganFarm.Organ({
			name: "Foreskin", cost: 2500, time: 5,
			actions: [
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Graft on",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFAddForeskin,
					canImplant: slave => slave.foreskin <= 0,
					implantError: slave => `This slave already has a ${slave.dick > 0 ? "foreskin" : "clitoral hood"}.`,
				})
			]
		})],
	["prostate",
		new App.Medicine.OrganFarm.Organ({
			name: "Prostate", cost: 5000, time: 5,
			actions: [
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Implant",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFAddProstate,
					canImplant: s => s.prostate === 0,
					implantError: () => "This slave already has a prostate.",
				})
			]
		})],
	["ovaries",
		new App.Medicine.OrganFarm.Ovaries({
			name: "Ovaries", eggType: "human", pregData: "human"
		})],
	["freshOvaries",
		new App.Medicine.OrganFarm.Organ({
			name: "Younger Ovaries",
			tooltip: "requires a womb for successful implantation",
			cost: 10000, time: 10,
			canGrow: () => V.youngerOvaries === 1,
			dependencies: ["ovaries", "pigOvaries", "dogOvaries", "horseOvaries", "cowOvaries", "mpreg", "mpregPig", "mpregDog", "mpregHorse", "mpregCow"],
			actions: [
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Implant",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFFreshOvaries,
					canImplant: s => (s.mpreg !== 0 || s.ovaries !== 0) && s.bellyImplant === -1 && s.physicalAge < 70 && s.ovaryAge >= 45,
					implantError: s =>
						s.ovaryAge < 45
							? "This slave's ovaries are too young to benefit from rejuvenation."
							: s.physicalAge >= 70
								? "This slave's body is too old to handle pregnancy."
								: "This slave lacks a viable womb.",
				})
			]
		})],
	["immortalOvaries",
		new App.Medicine.OrganFarm.Organ({
			name: "Immortal Ovaries",
			tooltip: "requires a womb for successful implantation",
			cost: 50000, time: 20,
			canGrow: () => V.immortalOvaries === 1,
			dependencies: ["ovaries", "pigOvaries", "dogOvaries", "horseOvaries", "cowOvaries", "mpreg", "mpregPig", "mpregDog", "mpregHorse", "mpregCow"],
			actions: [
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Implant",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFImmortalOvaries,
					canImplant: s => (s.mpreg !== 0 || s.ovaries !== 0) && s.bellyImplant === -1 && s.physicalAge < 70 && s.ovaryAge >= 0,
					implantError: s =>
						s.ovaryAge < 0
							? "This slave already has immortal ovaries."
							: s.physicalAge >= 70
								? "This slave's body is too old to handle pregnancy."
								: "This slave lacks a viable womb.",
				})
			]
		})],
	["asexualReproOvaries",
		new App.Medicine.OrganFarm.Organ({
			name: "Asexual reproduction modification",
			tooltip: "requires existing ovaries for successful implantation",
			cost: 10000, time: 10,
			canGrow: () => V.asexualReproduction === 1,
			dependencies: ["ovaries", "pigOvaries", "dogOvaries", "horseOvaries", "cowOvaries", "mpreg", "mpregPig", "mpregDog", "mpregHorse", "mpregCow"],
			actions: [
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Implant",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFAsexualReproOvaries,
					canImplant: s => s.mpreg !== 0 || s.ovaries !== 0,
					implantError: () => "This slave lacks ovaries.",
				})
			]
		})],
	["pigOvaries",
		new App.Medicine.OrganFarm.Ovaries({
			name: "Pig ovaries", eggType: "pig", pregData: "pig"
		})],
	["dogOvaries",
		new App.Medicine.OrganFarm.Ovaries({
			name: "Dog ovaries", eggType: "dog", pregData: "canineM",
		})],
	["horseOvaries",
		new App.Medicine.OrganFarm.Ovaries({
			name: "Horse ovaries", eggType: "horse", pregData: "equine",
		})],
	["cowOvaries",
		new App.Medicine.OrganFarm.Ovaries({
			name: "Cow ovaries", eggType: "cow", pregData: "cow"
		})],
	["leftEye",
		new App.Medicine.OrganFarm.Organ({
			name: "Left Eye", cost: 5000, time: 10,
			tooltip: s => getLeftEyeVision(s) === 2 ? "would not improve this slave" : "",
			actions: [
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Implant",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFNewEyes,
					surgeryProcedureParams: ["left"],
					canImplant: s => getLeftEyeVision(s) === 0 && getBestVision(s) !== 0 && getLeftEyeType(s) !== 2,
					implantError: s => getLeftEyeVision(s) !== 0 ? "Slave has a working left eye." : "",
				}),
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Implant",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFUnblind,
					surgeryProcedureParams: ["left"],
					canImplant: s => getBestVision(s) === 0 && getLeftEyeType(s) !== 2,
					implantError: () => "",
				}),
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Replace", autoImplant: false,
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFNewEyes,
					surgeryProcedureParams: ["left"],
					canImplant: s => getLeftEyeType(s) === 2,
					implantError: () => "",
				})
			]
		})],
	["rightEye",
		new App.Medicine.OrganFarm.Organ({
			name: "Right Eye", cost: 5000, time: 10,
			tooltip: s => getRightEyeVision(s) === 2 ? "would not improve this slave" : "",
			actions: [
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Implant",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFNewEyes,
					surgeryProcedureParams: ["right"],
					canImplant: s => getRightEyeVision(s) === 0 && getBestVision(s) !== 0 && getRightEyeType(s) !== 2,
					implantError: s => getRightEyeVision(s) !== 0 ? "Slave has a working right eye." : "",
				}),
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Implant",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFUnblind,
					surgeryProcedureParams: ["right"],
					canImplant: s => getBestVision(s) === 0 && getRightEyeType(s) !== 2,
					implantError: () => "",
				}),
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Replace", autoImplant: false,
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFNewEyes,
					surgeryProcedureParams: ["right"],
					canImplant: s => getRightEyeType(s) === 2,
					implantError: () => "",
				})
			]
		})],
	["ears",
		new App.Medicine.OrganFarm.Organ({
			name: "Normal Ears", cost: 1000, time: 2,
			actions: [
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Attach",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFImplantEars,
					canImplant: s => s.earShape === "none",
					implantError: () => "This slave already has ears."
				}),
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Replace",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFImplantEars,
					autoImplant: false,
					canImplant: s => s.earShape !== "normal",
					implantError: () => "This slave already has normal ears."
				})
			]
		})],
	["topEars",
		new App.Medicine.OrganFarm.Organ({
			name: "Top Ears", cost: 1000, time: 2,
			canGrow: () => V.surgeryUpgrade >= 1,
			actions: [
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Attach",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFImplantTopEars,
					canImplant: s => s.earT === "none",
					implantError: () => "This slave already has ears at the top of the head."
				}),
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Replace",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFImplantTopEars,
					autoImplant: false,
					canImplant: s => s.earT !== "normal",
					implantError: () => "This slave already has normal ears at the top of the head."
				})
			]
		})],
	["cochleae",
		new App.Medicine.OrganFarm.Organ({
			name: "Cochleae", cost: 8000, time: 6,
			actions: [
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Implant",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFUndeafen,
					canImplant: s => s.hears <= -2 && s.earImplant === 0,
					implantError: () => "This slave already has working ears.",
				}),
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Replace", autoImplant: false,
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFNewEars,
					canImplant: s => s.earImplant === 1,
					implantError: () => "",
				})
			]
		})],
	["voicebox",
		new App.Medicine.OrganFarm.Organ({
			name: "Vocal Cords", cost: 5000, time: 5,
			actions: [
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Implant",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFRestoreVoice,
					canImplant: s => s.voice === 0 && s.electrolarynx === 0,
					implantError: () => "This slave is not mute.",
				}),
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Replace", autoImplant: false,
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFNewVoice,
					canImplant: s => s.electrolarynx === 1,
					implantError: () => "",
				})
			]
		})],
	["hair",
		new App.Medicine.OrganFarm.Organ({
			name: "Hair Follicles", cost: 500, time: 2, displayMultipleActions: true,
			actions: [
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Scalp",
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFRestoreHairHead,
					canImplant: s => s.bald !== 0,
					implantError: () => "This slave already has hair.",
				}),
				/* So apparently hair is tracked via the .earTColor variable with no differential between possible hair origin. Not worth the variable, currently.
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Ears", healthImpact: 5,
					surgeryType: "addHairEarsH", autoImplant: false,
					canImplant: s => (s.earTShape !== "normal"),
					implantError: "",
					implant: s => {
						s.earTColor = getGeneticHairColor(s);
					}
				}),
				*/
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Brow", autoImplant: false,
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFRestoreHairBrow,
					canImplant: s => s.eyebrowHStyle === "bald",
					implantError: () => "",
				}),
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Axillary", autoImplant: false,
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFRestoreHairPits,
					canImplant: s => s.underArmHStyle === "bald" || s.underArmHStyle === "hairless",
					implantError: () => "",
				}),
				new App.Medicine.OrganFarm.OrganImplantAction({
					name: "Pubic", autoImplant: false,
					surgeryProcedure: App.Medicine.Surgery.Procedures.OFRestoreHairPubes,
					canImplant: s => s.pubicHStyle === "bald" || s.pubicHStyle === "hairless",
					implantError: () => "",
				})
			]
		})],
	["mpreg",
		new App.Medicine.OrganFarm.AnalWomb({
			name: "Anal womb and ovaries", eggType: "human", pregData: "human",
		})],
	["mpregPig",
		new App.Medicine.OrganFarm.AnalWomb({
			name: "Anal pig womb and ovaries", eggType: "pig", pregData: "pig",
		})],
	["mpregDog",
		new App.Medicine.OrganFarm.AnalWomb({
			name: "Anal dog womb and ovaries", eggType: "dog", pregData: "canineM",
		})],
	["mpregHorse",
		new App.Medicine.OrganFarm.AnalWomb({
			name: "Anal horse womb and ovaries", eggType: "horse", pregData: "equine",
		})],
	["mpregCow",
		new App.Medicine.OrganFarm.AnalWomb({
			name: "Anal cow womb and ovaries", eggType: "cow", pregData: "cow",
		})],
]);


code:
/**
 * generate two independent Gaussian numbers using Box-Muller transform.
 * mean and deviation specify the desired mean and standard deviation.
 * @param {number} [mean=0]
 * @param {number} [deviation=1]
 * @returns {number[]}
 */
function gaussianPair(mean = 0, deviation = 1) {
	const r = Math.sqrt(-2.0 * Math.log(1 - Math.random()));
	const sigma = 2.0 * Math.PI * (1 - Math.random());
	return [r * Math.cos(sigma), r * Math.sin(sigma)].map(val => val * deviation + mean);
}

/**
 * Generate a skewed normal random variable
 * Reference: http://azzalini.stat.unipd.it/SN/faq-r.html
 * @param {number} skew
 * @returns {number}
 */
App.Utils.Math.skewedGaussian = function(skew) {
	let randoms = gaussianPair();
	if (skew === 0) {
		// Don't bother, return an unskewed normal distribution
		return randoms[0];
	}
	let delta = skew / Math.sqrt(1 + skew * skew);
	let result = delta * randoms[0] + Math.sqrt(1 - delta * delta) * randoms[1];
	return randoms[0] >= 0 ? result : -result;
};

/**
 * Generate a skewed normal random variable between max and min
 * @param {number} max
 * @param {number} min
 * @param {number} skew
 * @returns {number}
 */
App.Utils.Math.limitedSkewedGaussian = function(max, min, skew) {
	let result = App.Utils.Math.skewedGaussian(skew);
	while (result < min || result > max) {
		result = App.Utils.Math.skewedGaussian(skew);
	}
	return result;
};

/**
 * Generate a random integer with a normal distribution between min and max (both inclusive).
 * Default parameters result in truncating the standard normal distribution between -3 and +3.
 * Not specifying min/max results in rerolling val approximately 0.3% of the time.
 * @param {number} [mean=0]
 * @param {number} [deviation=1]
 * @param {number} [min=mean-3*deviation]
 * @param {number} [max=mean+3*deviation]
 * @returns {number}
 */
function normalRandInt(mean = 0, deviation = 1, min = mean - 3 * deviation, max = mean + 3 * deviation) {
	let val = gaussianPair(mean, deviation)[0];
	while (val < min || val > max) {
		val = gaussianPair(mean, deviation)[0];
	}
	return Math.round(val);
}

/**
 * Returns a random integer between min and max (both inclusive).
 * If count is defined, chooses that many random numbers between min and max and returns the average. This is an approximation of a normal distribution.
 * @param {number} min
 * @param {number} max
 * @param {number} [count=1]
 * @returns {number}
 */
function jsRandom(min, max, count = 1) {
	function rand() {
		return Math.random() * (max - min + 1) + min;
	}

	if (count === 1) {
		return Math.floor(rand());
	}

	let total = 0;
	for (let i = 0; i < count; i++) {
		total += rand();
	}
	return Math.floor(total / count);
}

/**
 * Chooses multiple random elements of an array.
 * @template {*} T
 * @param {Array<T>} arr
 * @param {number} count
 * @returns {Array<T>}
 */
function jsRandomMany(arr, count) {
	let result = [];
	let tmp = arr.slice();
	for (let i = 0; i < count; i++) {
		let index = Math.floor(Math.random() * tmp.length);
		result.push(tmp.splice(index, 1)[0]);
	}
	return result;
}

/**
 * Accepts both an array and a list, returns undefined if nothing is passed.
 * @template {*} T
 * @param {Array<T>} choices
 * @param {...T} [otherChoices]
 * @returns {T}
 */
function jsEither(choices, ...otherChoices) {
	if (otherChoices.length === 0 && Array.isArray(choices)) {
		return choices[Math.floor(Math.random() * choices.length)];
	}
	const allChoices = otherChoices;
	allChoices.push(...choices);
	return allChoices[Math.floor(Math.random() * allChoices.length)];
}


code:
/**
 * @template {PropertyKey} K
 * @template {*} T
 * @param {Partial<Record<K, T>>} dict
 * @param {K} value
 * @param {T} [defaultValue]
 * @returns {T|null}
 */
App.Ratings.exact = function(dict, value, defaultValue = null) {
	const res = dict[value];
	return res ? res : defaultValue;
};

/**
 * @template {*} T
 * @param {Record<number, T>} ratings
 * @param {number} value
 * @returns {T|null}
 */
App.Ratings.numeric = function(ratings, value) {
	for (const key in ratings) {
		if (parseInt(key) >= value) {
			return ratings[key];
		}
	}
	return null;
};

/**
 * @param {object} ratings
 * @param {number[]} values
 * @returns {*|null}
 */
App.Ratings.multiNumeric = function(ratings, values) {
	const firstRating = App.Ratings.numeric(ratings, values[0]);
	if (firstRating === null || typeof firstRating === "string" || firstRating.hasOwnProperty("desc")) {
		return firstRating;
	}
	return App.Ratings.multiNumeric(firstRating, values.slice(1));
};


code:
App.RA.Activation.Stack = class Stack {
	constructor() {
		/**
		 * @private
		 * @type {number[]}
		 */
		this._numbers = [];
		/**
		 * @private
		 * @type {string[]}
		 */
		this._strings = [];
	}

	/**
	 * @param {boolean} v
	 */
	pushBoolean(v) {
		this._numbers.push(v ? 1 : 0);
	}

	/**
	 * @returns {FC.Bool}
	 */
	popAsBoolean() {
		return this._numbers.pop() ? 1 : 0;
	}

	/**
	 * @param {number} v
	 */
	pushNumber(v) {
		this._numbers.push(v);
	}

	/**
	 * @returns {number}
	 */
	popNumber() {
		return this._numbers.pop();
	}

	/**
	 * @param {string} v
	 */
	pushString(v) {
		this._strings.push(v);
	}

	/**
	 * @returns {string}
	 */
	popString() {
		return this._strings.pop();
	}
};


code:
/**
 * All functions should only be called from z1-conditionEditorController.js
 */
App.RA.Activation.SimpleEditor = (function() {
	/**
	 * @typedef {object} RuleState
	 * @property {"always"|"never"|"boolean"|"number"|"string"|"custom"} activeRuleType
	 * @property {string} boolGetter
	 * @property {boolean} negateBool
	 * @property {string} stringGetter
	 * @property {string} stringComparator
	 * @property {string} stringValue
	 * @property {string} numberGetter
	 * @property {number} numberUpperValue
	 * @property {"lt"|"lte"|""} numberUpperComparator
	 * @property {number} numberLowerValue
	 * @property {"gt"|"gte"|""} numberLowerComparator
	 * @property {string[]} assignments
	 * @property {"include"|"exclude"|"ignore"} assignmentMode
	 * @property {string} customGetter
	 * @property {"b"|"s"|"n"} customMode
	 */

	/**
	 * @type {HTMLDivElement}
	 */
	let editorNode = null;
	/**
	 * @type {RuleState}
	 */
	let currentRule = null;

	/**
	 * @param {FC.RA.PostFixRule} rule
	 * @param {HTMLDivElement}parent
	 */
	function editor(rule, parent) {
		currentRule = deserializeRule(rule);
		editorNode = parent;
		editorNode.append(buildEditor());
	}

	function refreshEditor() {
		if (editorNode !== null) {
			$(editorNode).empty().append(buildEditor());
		}
	}

	/**
	 * If the rule is valid, returns the serialized rule, otherwise null.
	 *
	 * @returns {FC.RA.PostFixRule}
	 */
	function saveEditor() {
		if (currentRule == null) {
			return null;
		}
		return serializeRule(currentRule);
	}

	function resetEditor() {
		currentRule = null;
		editorNode = null;
	}

	/**
	 * @returns {HTMLElement}
	 */
	function buildEditor() {
		const outerDiv = document.createElement("div");
		// selector
		const selectorDiv = document.createElement("div");
		selectorDiv.classList.add("button-group");
		outerDiv.append(selectorDiv);
		App.UI.DOM.appendNewElement("button", selectorDiv, "Always", currentRule.activeRuleType === "always" ? ["selected", "disabled"] : []).onclick = () => {
			currentRule.activeRuleType = "always";
			refreshEditor();
		};
		App.UI.DOM.appendNewElement("button", selectorDiv, "Never", currentRule.activeRuleType === "never" ? ["selected", "disabled"] : []).onclick = () => {
			currentRule.activeRuleType = "never";
			refreshEditor();
		};
		App.UI.DOM.appendNewElement("button", selectorDiv, "Boolean", currentRule.activeRuleType === "boolean" ? ["selected", "disabled"] : []).onclick = () => {
			currentRule.activeRuleType = "boolean";
			refreshEditor();
		};
		App.UI.DOM.appendNewElement("button", selectorDiv, "Number", currentRule.activeRuleType === "number" ? ["selected", "disabled"] : []).onclick = () => {
			currentRule.activeRuleType = "number";
			refreshEditor();
		};
		App.UI.DOM.appendNewElement("button", selectorDiv, "String", currentRule.activeRuleType === "string" ? ["selected", "disabled"] : []).onclick = () => {
			currentRule.activeRuleType = "string";
			refreshEditor();
		};
		App.UI.DOM.appendNewElement("button", selectorDiv, "Custom", currentRule.activeRuleType === "custom" ? ["selected", "disabled"] : []).onclick = () => {
			currentRule.activeRuleType = "custom";
			refreshEditor();
		};

		// add bool
		if (currentRule.activeRuleType === "boolean") {
			const boolDiv = document.createElement("div");
			boolDiv.classList.add("button-group");
			outerDiv.append(boolDiv);
			/**
			 * @type {selectOption[]}
			 */
			const options = [];
			App.RA.Activation.getterManager.booleanGetters.forEach((getter, key) => {
				if (!getter.visible || getter.visible()) {
					options.push({
						key: key, name: getter.name, enabled: !getter.enabled || getter.enabled()
					});
				}
			});
			boolDiv.append(App.UI.DOM.makeSelect(options, currentRule.boolGetter, key => {
				currentRule.boolGetter = key;
				refreshEditor();
			}));
			boolDiv.append(" should be ");
			App.UI.DOM.appendNewElement("button", boolDiv, "True", currentRule.negateBool ? [] : ["selected", "disabled"]).onclick = () => {
				currentRule.negateBool = false;
				refreshEditor();
			};
			App.UI.DOM.appendNewElement("button", boolDiv, "False", currentRule.negateBool ? ["selected", "disabled"] : []).onclick = () => {
				currentRule.negateBool = true;
				refreshEditor();
			};
		} else if (currentRule.activeRuleType === "number") {
			const numberDiv = document.createElement("div");
			outerDiv.append(numberDiv);
			/**
			 * @type {selectOption[]}
			 */
			const options = [];
			App.RA.Activation.getterManager.numberGetters.forEach((getter, key) => {
				if (!getter.visible || getter.visible()) {
					options.push({
						key: key, name: getter.name, enabled: !getter.enabled || getter.enabled()
					});
				}
			});
			numberDiv.append(App.UI.DOM.makeSelect(options, currentRule.numberGetter, key => {
				currentRule.numberGetter = key;
				refreshEditor();
			}));
			numberDiv.append(" should be ");
			numberDiv.append(App.UI.DOM.makeSelect(
				[{key: "gt", name: "greater than"}, {key: "gte", name: "greater than or equal to"},
					{key: "", name: "ignored"}],
				currentRule.numberLowerComparator, key => {
					currentRule.numberLowerComparator = key;
					refreshEditor();
				}));
			if (currentRule.numberLowerComparator !== "") {
				numberDiv.append(" ", App.UI.DOM.makeTextBox(currentRule.numberLowerValue, val => {
					currentRule.numberLowerValue = val;
				}, true));
			}
			numberDiv.append(" and ");
			numberDiv.append(App.UI.DOM.makeSelect(
				[{key: "lt", name: "less than"}, {key: "lte", name: "less than or equal to"},
					{key: "", name: "ignored"}],
				currentRule.numberUpperComparator, key => {
					currentRule.numberUpperComparator = key;
					refreshEditor();
				}));
			if (currentRule.numberUpperComparator !== "") {
				numberDiv.append(" ", App.UI.DOM.makeTextBox(currentRule.numberUpperValue, val => {
					currentRule.numberUpperValue = val;
				}, true));
			}
		} else if (currentRule.activeRuleType === "string") {
			const stringDiv = document.createElement("div");
			outerDiv.append(stringDiv);
			/**
			 * @type {selectOption[]}
			 */
			const options = [];
			App.RA.Activation.getterManager.stringGetters.forEach((getter, key) => {
				if (!getter.visible || getter.visible()) {
					options.push({
						key: key, name: getter.name, enabled: !getter.enabled || getter.enabled()
					});
				}
			});
			stringDiv.append(App.UI.DOM.makeSelect(options, currentRule.stringGetter, key => {
				currentRule.stringGetter = key;
				refreshEditor();
			}));
			stringDiv.append(" should ");
			stringDiv.append(App.UI.DOM.makeSelect(
				[{key: "eqstr", name: "equal"}, {key: "substr", name: "contain"}, {key: "match", name: "match"}],
				currentRule.stringComparator, key => {
					currentRule.stringComparator = key;
					refreshEditor();
				}));
			stringDiv.append(" ", App.UI.DOM.makeTextBox(currentRule.stringValue, val => {
				currentRule.stringValue = val;
			}));
		} else if (currentRule.activeRuleType === "custom") {
			const options = new App.UI.OptionsGroup();
			options.addOption("Mode", "customMode", currentRule)
				.addValueList([["Boolean", "b"], ["Number", "n"], ["String", "s"]]).addGlobalCallback(refreshEditor);
			outerDiv.append(options.render());
			const textArea = document.createElement("textarea");
			textArea.classList.add("condition-custom");
			textArea.append(currentRule.customGetter);
			textArea.onchange = ev => {
				// @ts-ignore
				currentRule.customGetter = ev.target.value;
				refreshEditor();
			};
			App.UI.DOM.appendNewElement("div", outerDiv, textArea);
		}

		// Assignments
		outerDiv.append("Assignments: ");
		outerDiv.append(App.UI.DOM.makeSelect(
			[{key: "ignore", name: "Ignored"}, {key: "include", name: "Include"}, {key: "exclude", name: "Exclude"}],
			currentRule.assignmentMode, key => {
				currentRule.assignmentMode = key;
				refreshEditor();
			}
		));
		if (currentRule.assignmentMode !== "ignore") {
			for (const [key, getter] of App.RA.Activation.getterManager.assignmentGetters) {
				if (getter.enabled && !getter.enabled()) {
					continue;
				}
				const checkbox = document.createElement("input");
				checkbox.setAttribute("type", "checkbox");
				checkbox.checked = currentRule.assignments.includes(key);
				checkbox.onchange = () => {
					if (!currentRule.assignments.includes(key)) {
						currentRule.assignments.push(key);
					} else {
						const index = currentRule.assignments.indexOf(key);
						currentRule.assignments.splice(index, 1);
					}
				};
				outerDiv.append(` ${getter.name}: `, checkbox);
			}
		}

		return outerDiv;
	}

	/**
	 * @param {FC.RA.PostFixRule} rule
	 * @returns {RuleState}
	 */
	function deserializeRule(rule) {
		// About the TS errors in this function: we can assume a lot about the rule composition because we know it's in
		// the simple format. The rule itself is still a normal FC.RA.PostFixRule which would allow a lot more.
		// Therefore, TS is not happy even though we know everything's fine.
		/**
		 * @type {RuleState}
		 */
		const ruleState = {
			activeRuleType: "always",
			boolGetter: "isfertile",
			negateBool: false,
			stringGetter: "label",
			stringValue: "",
			stringComparator: "eqstr",
			numberGetter: "devotion",
			numberUpperValue: 100,
			numberUpperComparator: "",
			numberLowerValue: -100,
			numberLowerComparator: "",
			assignments: [],
			assignmentMode: "ignore",
			customGetter: "context => false",
			customMode: "b",
		};
		// we know there is only main one rule.
		let i = 0;
		const rulePart = rule[i];
		if (rulePart === true) {
			ruleState.activeRuleType = "always";
		} else if (rulePart === false) {
			ruleState.activeRuleType = "never";
		} else if (App.RA.Activation.getterManager.isBoolean(rulePart)) {
			ruleState.activeRuleType = "boolean";
			ruleState.boolGetter = rulePart;
			if (rule[i + 1] === "not") {
				ruleState.negateBool = true;
				i++;
			}
		} else if (App.RA.Activation.getterManager.isNumber(rulePart)) {
			ruleState.activeRuleType = "number";
			ruleState.numberGetter = rulePart;
			// check if there is a lower rule:
			if (rule[i + 2].startsWith("g")) {
				ruleState.numberLowerValue = rule[i + 1];
				ruleState.numberLowerComparator = rule[i + 2];
				// check if there is also an upper value:
				if (rule[i + 3] === ruleState.numberGetter) {
					ruleState.numberUpperValue = rule[i + 4];
					ruleState.numberUpperComparator = rule[i + 5];
					i += 3;
				}
			} else {
				ruleState.numberUpperValue = rule[i + 1];
				ruleState.numberUpperComparator = rule[i + 2];
			}
			i += 2;
		} else if (App.RA.Activation.getterManager.isString(rulePart)) {
			ruleState.activeRuleType = "string";
			ruleState.stringGetter = rulePart;
			ruleState.stringValue = rule[i + 1].slice(1);
			ruleState.stringComparator = rule[i + 2];
			i += 2;
		} else if (typeof rulePart === "string" && rulePart.startsWith("?")) {
			ruleState.activeRuleType = "custom";
			ruleState.customMode = rulePart.charAt(1);
			ruleState.customGetter = rulePart.slice(2);
		} else {
			throw new Error("Rule is not in simple mode format!");
		}
		i++;

		// check for assignment rules
		let any = false;
		while (App.RA.Activation.getterManager.isAssignment(rule[i])) {
			any = true;
			ruleState.assignments.push(rule[i]);
			i++;
		}
		if (any) {
			i += 2;
			if (rule[i] === "not") {
				ruleState.assignmentMode = "exclude";
				i++;
			} else {
				ruleState.assignmentMode = "include";
			}
		}

		return ruleState;
	}

	/**
	 * Expects a valid RulePart structure
	 *
	 * @param {RuleState} ruleState
	 * @returns {FC.RA.PostFixRule}
	 */
	function serializeRule(ruleState) {
		/**
		 * @type {FC.RA.PostFixRule}
		 */
		const rule = [];
		let counter = 0;

		switch (ruleState.activeRuleType) {
			case "always":
				rule.push(true);
				counter++;
				break;
			case "never":
				rule.push(false);
				counter++;
				break;
			case "boolean":
				rule.push(ruleState.boolGetter);
				if (ruleState.negateBool) {
					rule.push("not");
				}
				counter++;
				break;
			case "number": {
				let any = false;
				if (ruleState.numberLowerComparator !== "") {
					any = true;
					rule.push(ruleState.numberGetter);
					rule.push(ruleState.numberLowerValue);
					rule.push(ruleState.numberLowerComparator);
					counter++;
				}
				if (ruleState.numberUpperComparator !== "") {
					any = true;
					rule.push(ruleState.numberGetter);
					rule.push(ruleState.numberUpperValue);
					rule.push(ruleState.numberUpperComparator);
					counter++;
				}
				if (!any) {
					rule.push(true);
					counter++;
				}
			}
				break;
			case "string":
				rule.push(ruleState.stringGetter);
				rule.push("!" + ruleState.stringValue);
				rule.push(ruleState.stringComparator);
				counter++;
				break;
			case "custom":
				rule.push("?" + ruleState.customMode + ruleState.customGetter);
				counter++;
		}

		if (ruleState.assignmentMode !== "ignore" && ruleState.assignments.length > 0) {
			rule.push(...ruleState.assignments);
			rule.push(ruleState.assignments.length, "or");
			if (ruleState.assignmentMode === "exclude") {
				rule.push("not");
			}
			counter++;
		}

		rule.push(counter, "and");
		return rule;
	}

	return {
		build: editor,
		save: saveEditor,
		reset: resetEditor,
	};
})();