code:
/* Major props to the anons who worked together to forge the Super Pregnancy Project. Let your legacy go unforgotten.*/
window.getPregBellySize = function(s) {
	let targetLen;
	let gestastionWeek = s.preg;
	let fetuses = s.pregType;
	let phi = 1.618;

	if (gestastionWeek <= 32) {
		targetLen = ((0.00006396 * Math.pow(gestastionWeek, 4)) - (0.005501 * Math.pow(gestastionWeek, 3)) + (0.161 * Math.pow(gestastionWeek, 2)) - (0.76 * gestastionWeek) + 0.208);
	} else if (gestastionWeek <= 106) {
		targetLen = ((-0.0000004675 * Math.pow(gestastionWeek, 4)) + (0.0001905 * Math.pow(gestastionWeek, 3)) - (0.029 * Math.pow(gestastionWeek, 2)) + (2.132 * gestastionWeek) - 16.575);
	} else {
		targetLen = ((-0.00003266 * Math.pow(gestastionWeek, 2)) + (0.076 * gestastionWeek) + 43.843);
	}

	let bellySize = ((4 / 3) * (Math.PI) * (phi / 2) * (Math.pow((targetLen / 2), 3)) * fetuses);
	return bellySize;
};

/**
 * @param {App.Entity.SlaveState} slave
 * @returns {string}
 */
window.bellyAdjective = function(slave) {
	if (slave.belly >= 1500) {
		if (slave.belly >= 1000000) {
			if (slave.preg > slave.pregData.normalBirth / 4) {
				return 'unfathomably distended, brimming with life';
			} else {
				return `unfathomable`;
			}
		} else if (slave.belly >= 750000) {
			if (slave.preg > slave.pregData.normalBirth / 4) {
				return 'monolithic bulging';
			} else {
				return `monolithic`;
			}
		} else if (slave.belly >= 600000) {
			if (slave.preg > slave.pregData.normalBirth / 4) {
				return 'titanic bulging';
			} else {
				return `titanic`;
			}
		} else if (slave.belly >= 450000) {
			if (slave.preg > slave.pregData.normalBirth / 4) {
				return 'gigantic bulgy';
			} else {
				return `gigantic`;
			}
		} else if (slave.belly >= 300000) {
			return 'massive';
		} else if (slave.belly >= 100000) {
			return 'giant';
		} else if (slave.belly >= 15000) {
			return 'huge';
		} else if (slave.belly >= 10000) {
			return 'big';
		} else {
			return `swollen`;
		}
	}
	return "";
};

/* calculates and returns expected ovum count during conception*/
window.setPregType = function(actor) {
	/* IMHO rework is possible. Can be more interesting to play, if this code will take in account more body conditions - age, fat, food, hormone levels, etc. */

	let ovum = jsRandom(actor.pregData.normalOvaMin, actor.pregData.normalOvaMax); // for default human profile it's always 1.
	let fertilityStack = 0; // adds an increasing bonus roll for stacked fertility drugs

	/* Suggestion for better animal pregnancy support - usage of another variable then ovum for fertility drugs bonus, and then adding actor.pregData.drugsEffect multiplier to it before adding to ovum. Example:

		let bonus = 0;

		... (code below where ovum changed to bonus)

		bonus *= actor.pregData.drugsEffect;
		ovum += bonus;

	*/

	if (actor.broodmother < 1) { // Broodmothers should be not processed here. Necessary now.
		if (typeof actor.readyOva === "number" && actor.readyOva !== 0) {
			ovum = actor.readyOva; // just single override; for delayed impregnation cases
		} else if (actor.ID === -1) {
			if (actor.geneticQuirks.fertility === 2 && actor.geneticQuirks.hyperFertility === 2) { // Do not mix with sperm
				if (actor.fertDrugs === 1) {
					ovum += jsEither([2, 3, 3, 3, 3, 4, 4, 5]);
				} else {
					ovum += jsEither([1, 1, 2, 2, 3, 3, 4]);
				}
				if (actor.forcedFertDrugs > 0) {
					ovum += jsEither([3, 3, 4, 4, 5]);
				}
			} else if (actor.geneticQuirks.hyperFertility === 2) { // Predisposed to multiples
				if (actor.fertDrugs === 1) {
					ovum += jsEither([1, 2, 2, 2, 2, 3, 3, 4]);
				} else {
					ovum += jsEither([0, 1, 1, 1, 1, 1, 2, 3]);
				}
				if (actor.forcedFertDrugs > 0) {
					ovum += jsEither([2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4]);
				}
			} else if (actor.geneticQuirks.fertility === 2) { // Predisposed to twins
				if (actor.fertDrugs === 1) {
					ovum += jsEither([1, 1, 2, 2, 2, 2, 3, 3]);
				} else {
					ovum += jsEither([0, 0, 0, 1, 1, 1, 1, 1, 1, 2]);
				}
				if (actor.forcedFertDrugs > 0) {
					ovum += jsEither([1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4]);
				}
			} else {
				if (actor.fertDrugs === 1) {
					ovum += jsEither([0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3]);
				} else {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
				}
				if (actor.forcedFertDrugs > 0) {
					ovum += jsEither([0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4]);
				}
			}
			ovum = Math.clamp(ovum, 0, 8);
		} else {
			if (actor.eggType === "horse" || actor.eggType === "cow") {
				if (actor.geneticQuirks.fertility === 2 && actor.geneticQuirks.hyperFertility === 2) { // Do not mix with sperm
					ovum += jsEither([0, 0, 0, 0, 0, 0, 1]);
					fertilityStack += 0.8;
				} else if (actor.geneticQuirks.hyperFertility === 2) { // Predisposed to multiples
					fertilityStack += 0.4;
				} else if (actor.geneticQuirks.fertility === 2) { // Predisposed to twins
					fertilityStack += 0.2;
				}
				if (actor.ovaImplant === "fertility") {
					fertilityStack += 0.3;
				}
				if (actor.hormones === 2) {
					fertilityStack += 0.2;
				}
				if (actor.hormoneBalance >= 200) {
					fertilityStack += 0.3;
				}
				if (actor.diet === "fertility") {
					fertilityStack += 0.3;
				}
				if (State.variables.masterSuitePregnancyFertilitySupplements === 1 && ((actor.assignment === "serve in the master suite" || actor.assignment === "be your Concubine"))) {
					fertilityStack += 0.5;
				}
				if (State.variables.reproductionFormula === 1 && (State.variables.week - actor.weekAcquired > 0)) {
					fertilityStack += 0.2;
				}
				if (actor.drugs === "super fertility drugs") {
					fertilityStack += 1.6;
				} else if (actor.drugs === "fertility drugs") {
					fertilityStack += 0.6;
				}
				fertilityStack = Math.floor(fertilityStack);
				if (State.variables.seeHyperPreg === 1) {
					if (actor.drugs === "super fertility drugs") {
						ovum += jsRandom(0, fertilityStack * 2);
					} else {
						ovum += jsRandom(0, fertilityStack);
					}
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
					}
				} else {
					ovum += jsRandom(0, fertilityStack);
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
						if (ovum > 4) {
							ovum = 4;
						}
					} else if (ovum > 3) {
						ovum = 3;
					}
				}
			} else if (actor.eggType === "dog") {
				if (actor.geneticQuirks.fertility === 2 && actor.geneticQuirks.hyperFertility === 2) { // Do not mix with sperm
					ovum += jsEither([1, 2, 2, 3]);
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
				} else if (actor.geneticQuirks.hyperFertility === 2) { // Predisposed to multiples
					ovum += jsEither([0, 1, 1, 2]);
					fertilityStack++;
					fertilityStack++;
				} else if (actor.geneticQuirks.fertility === 2) { // Predisposed to twins
					ovum += jsEither([0, 0, 0, 0, 1]);
					fertilityStack++;
				}
				if (actor.ovaImplant === "fertility") {
					ovum += jsEither([0, 0, 0, 0, 1]);
					fertilityStack++;
				}
				if (actor.hormones === 2) {
					ovum += jsEither([0, 0, 0, 1, 1, 1, 1, 2]);
					fertilityStack++;
				}
				if (actor.hormoneBalance >= 200) {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2]);
					fertilityStack++;
				}
				if (actor.diet === "fertility") {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 0, 0, 1]);
					fertilityStack++;
				}
				if (State.variables.masterSuitePregnancyFertilitySupplements === 1 && ((actor.assignment === "serve in the master suite" || actor.assignment === "be your Concubine"))) {
					ovum += jsEither([0, 0, 0, 1, 1, 2, 2, 2, 3, 3]);
					fertilityStack++;
					fertilityStack++;
				}
				if (State.variables.reproductionFormula === 1 && (State.variables.week - actor.weekAcquired > 0)) {
					fertilityStack++;
				}
				if (actor.drugs === "super fertility drugs") {
					ovum += jsEither([1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5]);
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
				} else if (actor.drugs === "fertility drugs") {
					ovum += jsEither([0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3]);
					fertilityStack++;
				}
				if (State.variables.seeHyperPreg === 1) {
					if (actor.drugs === "super fertility drugs") {
						ovum += jsRandom(0, fertilityStack * 2);
					} else {
						ovum += jsRandom(0, fertilityStack);
					}
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
					}
				} else {
					ovum += jsRandom(0, fertilityStack);
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
						if (ovum > 8) {
							ovum = jsEither([6, 8]);
						}
					} else if (ovum > 8) {
						ovum = jsRandom(6, 8);
					}
				}
			} else if (actor.eggType === "pig") {
				if (actor.geneticQuirks.fertility === 2 && actor.geneticQuirks.hyperFertility === 2) { // Do not mix with sperm
					ovum += jsRandom(4, 8);
					fertilityStack += 16;
				} else if (actor.geneticQuirks.hyperFertility === 2) { // Predisposed to multiples
					ovum += jsRandom(2, 6);
					fertilityStack += 10;
				} else if (actor.geneticQuirks.fertility === 2) { // Predisposed to twins
					ovum += jsRandom(2, 4);
					fertilityStack += 6;
				}
				if (actor.ovaImplant === "fertility") {
					ovum += jsRandom(4, 12);
					fertilityStack += 6;
				}
				if (actor.hormones === 2) {
					ovum += jsRandom(0, 4);
					fertilityStack += 3;
				}
				if (actor.hormoneBalance >= 200) {
					ovum += jsRandom(0, 4);
					fertilityStack += 3;
				}
				if (actor.diet === "fertility") {
					ovum += jsRandom(4, 10);
					fertilityStack += 6;
				}
				if (State.variables.masterSuitePregnancyFertilitySupplements === 1 && ((actor.assignment === "serve in the master suite" || actor.assignment === "be your Concubine"))) {
					ovum += jsRandom(8, 16);
					fertilityStack += 10;
				}
				if (State.variables.reproductionFormula === 1 && (State.variables.week - actor.weekAcquired > 0)) {
					fertilityStack += 2;
				}
				if (actor.drugs === "super fertility drugs") {
					ovum += jsRandom(10, 40);
					fertilityStack += 32;
				} else if (actor.drugs === "fertility drugs") {
					ovum += jsRandom(10, 20);
					fertilityStack += 16;
				}
				if (State.variables.seeHyperPreg === 1) {
					if (actor.drugs === "super fertility drugs") {
						ovum += jsRandom(fertilityStack / 2, fertilityStack * 2);
					} else {
						ovum += jsRandom(fertilityStack / 4, fertilityStack);
					}
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
					}
				} else {
					ovum += jsRandom(0, fertilityStack);
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
						if (ovum > 76) {
							ovum = jsEither([70, 72, 74, 76]);
						}
					} else if (ovum > 75) {
						ovum = jsRandom(60, 75);
					}
				}
			} else {
				if (actor.geneticQuirks.fertility === 2 && actor.geneticQuirks.hyperFertility === 2) { // Do not mix with sperm
					ovum += jsEither([1, 2, 2, 3]);
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
				} else if (actor.geneticQuirks.hyperFertility === 2) { // Predisposed to multiples
					ovum += jsEither([0, 1, 1, 2]);
					fertilityStack++;
					fertilityStack++;
				} else if (actor.geneticQuirks.fertility === 2) { // Predisposed to twins
					ovum += jsEither([0, 0, 0, 0, 1]);
					fertilityStack++;
				} else {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); // base chance for twins
				}
				if (actor.ovaImplant === "fertility") {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
					fertilityStack++;
				}
				if (actor.hormones === 2) {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2]);
					fertilityStack++;
				}
				if (actor.hormoneBalance >= 200) {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2]);
					fertilityStack++;
				}
				if (actor.diet === "fertility") {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
					fertilityStack++;
				}
				if (State.variables.masterSuitePregnancyFertilitySupplements === 1 && ((actor.assignment === "serve in the master suite" || actor.assignment === "be your Concubine"))) {
					ovum += jsEither([0, 0, 0, 1, 1, 2, 2, 2, 3, 3]);
					fertilityStack++;
					fertilityStack++;
				}
				if (State.variables.reproductionFormula === 1 && (State.variables.week - actor.weekAcquired > 0)) {
					fertilityStack++;
				}
				if (actor.drugs === "super fertility drugs") {
					ovum += jsEither([1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5]);
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
					fertilityStack++;
				} else if (actor.drugs === "fertility drugs") {
					ovum += jsEither([0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3]);
					fertilityStack++;
				}
				if (State.variables.seeHyperPreg === 1) {
					if (actor.drugs === "super fertility drugs") {
						ovum += jsRandom(0, fertilityStack * 2);
					} else {
						ovum += jsRandom(0, fertilityStack);
					}
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
					}
				} else {
					ovum += jsRandom(0, fertilityStack);
					if (actor.ovaImplant === "sympathy") {
						ovum *= 2;
						if (ovum > 12) {
							ovum = jsEither([10, 12]);
						}
					} else if (ovum > 12) {
						ovum = jsRandom(6, 12);
					}
				}
			}
		}
	}

	if (actor.geneticQuirks.superfetation === 2 && actor.womb.length > 0) {
		let ftvol = FetusGetPrediction(actor, actor.pregData.normalBirth);
		let cmvol = ftvol * actor.womb.length;
		let maxvol = actor.pregAdaptation*2000;
		if (State.variables.seeHyperPreg === 0) {
			maxvol /= 10; // without hyperpreg enabled it's limited to be roughly ten times smaller.
		}
		let freevol = maxvol - cmvol;
		let coeff = ((maxvol/actor.womb.length) / (freevol/ftvol)) / 2; // more divide to 2 is to balance for ensured 1 ova even if over limit.

		if (coeff < 0) { coeff = 0; }

		if (State.variables.seeHyperPreg !== 0) {
			coeff += jsRandom(0, fertilityStack/2); // this second chance for implantation. Should be affected only by chemical and genetic for easier implantation. Not directly related to ova count, only to body/womb condition. Raise successful implantation chance with already overfull womb. AFTER previous check.
		}

		if (ovum > coeff) {
			ovum = coeff;
		}

		/* for future, if monthly cycle will be a thing to implement, this will be useful.
		if (ovum < 0)
			ovum = jsRandom(coeff/fertilityStack, 1);
		*/
		if (ovum < 1) { ovum = 1; }

		// console.log("Name: ", actor.slaveName, "  ftvol:", ftvol, "  cmvol:", cmvol, "  maxvol:", maxvol, "  freevol:", freevol, "  coeff:", coeff, "  fertilityStack: ", fertilityStack, "  ovum: ", ovum);
	}

	return Math.ceil(ovum);
};

/*
 Penetrative ability, ability to become pregnant, and canBreed() must be checked outside of this. Designed to assume .eggType === "human".
 target is the slave to get pregnant. Also accepts the PC.
 chance is the % chance to conceive.
 hole control's the hole involved (0 - vagina, 1 - ass, 2 - both). .mpreg did this.
 fatherID is the ID of her sire or 0 if undefined.
 displayOverride is an override if defined - fatherID must be defined in this case.
*/
window.knockMeUp = function(target, chance, hole, fatherID, displayOverride) {
	let He;
	let r = ``;
	if (target.ID !== -1) {
		({He} = getPronouns(target));
	}
	if (V.seePreg !== 0) {
		// eslint-disable-next-line no-nested-ternary
		if (jsRandom(0, 99) < (chance + (V.reproductionFormula * ((target.pregSource <= 0) ? ((target.ID === -1) ? 0 : 10) : 20)))) {
			if (target.mpreg === hole) {
				if (target.pregWeek <= 0) {
					target.preg = 1;
					target.pregSource = (!fatherID ? 0 : fatherID);
					if (target.ID !== -1) {
						target.pregWeek = 1;
					}
				}

				target.pregType = setPregType(target);
				WombImpregnate(target, target.pregType, target.pregSource, 1);

				if (V.menstruation === 1) {
					//
				} else if (!displayOverride) {
					target.pregKnown = 1;
					if (target.ID === -1) {
						/* r += "You have gotten pregnant."; */
					} else {
						r += `<span class="lime">${He} has become pregnant.</span>`;
					}
					if (target.geneticQuirks.superfetation === 2 && target.womb.length > 0) {
						if (V.seeHyperPreg === 1) {
							target.fertPeak = 1;
						} else {
							target.fertPeak = 4;
						}
					}
				} else {
					target.pregKnown = 1;
					if (target.geneticQuirks.superfetation === 2 && target.womb.length > 0) {
						if (V.seeHyperPreg === 1) {
							target.fertPeak = 1;
						} else {
							target.fertPeak = 4;
						}
					}
				}
			} else if (hole === 2) {
				if (target.pregWeek <= 0) {
					target.preg = 1;
					target.pregSource = (!fatherID ? 0 : fatherID);
					if (target.ID !== -1) {
						target.pregWeek = 1;
					}
				}

				target.pregType = setPregType(target);
				WombImpregnate(target, target.pregType, target.pregSource, 1);

				if (V.menstruation === 1) {
					//
				} else if (!displayOverride) {
					target.pregKnown = 1;
					if (target.ID === -1) {
						/* r += "You have gotten pregnant."; */
					} else {
						r += `<span class="lime">${He} has become pregnant.</span>`;
					}
					if (target.geneticQuirks.superfetation === 2 && target.womb.length > 0) {
						if (V.seeHyperPreg === 1) {
							target.fertPeak = 1;
						} else {
							target.fertPeak = 4;
						}
					}
				} else {
					target.pregKnown = 1;
					if (target.geneticQuirks.superfetation === 2 && target.womb.length > 0) {
						if (V.seeHyperPreg === 1) {
							target.fertPeak = 1;
						} else {
							target.fertPeak = 4;
						}
					}
				}
			}
		}
	}
	return r;
};

window.getIncubatorReserved = function( /* slaves */ ) {
	return FetusGlobalReserveCount("incubator");
};

window.getNurseryReserved = function( /* slaves */ ) {
	return FetusGlobalReserveCount("nursery");
};

window.findFather = function(fatherID) {
	let father;

	father = V.slaves[V.slaveIndices[fatherID]];
	if (father === undefined) {
		if (V.incubator > 0) {
			father = V.tanks.find(s => s.ID === fatherID);
		}
	}
	if (father === undefined) {
		if (V.nursery > 0) {
			father = V.cribs.find(s => s.ID === fatherID);
		}
	}

	return father;
};

window.adjustFatherProperty = function(actor, property, newValue) {
	let father = findFather(actor.ID);
	if (father) {
		father[property] = newValue;
	}
};

/* OLD
window.adjustFatherProperty = function(actor, property, newValue) {
	let fatherIndex;

	fatherIndex = V.slaves.findIndex(function(s) { return s.ID === actor.ID; });
	if (fatherIndex > 0) {
		V.slaves[fatherIndex][property] = newValue;
	} else if (V.incubator > 0) {
		fatherIndex = V.tanks.findIndex(function(s) { return s.ID === actor.ID; });
		if (fatherIndex > 0) {
			V.tanks[fatherIndex][property] = newValue;
		}
	}
	if (fatherIndex === -1) {
		if (V.nursery > 0) {
			fatherIndex = V.cribs.findIndex(function(s) { return s.ID === actor.ID; });
			if (fatherIndex !== -1) {
				V.cribs[fatherIndex][property] = newValue;
			}
		}
	}
}
*/

/* not to be used until that last part is defined. It may become slave.boobWomb.volume or some shit */
/**
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
window.getBaseBoobs = function(slave) {
	return slave.boobs - slave.boobsImplant - slave.boobsMilk - slave.boobsWombVolume;
};

/**
 * Terminate a pregnancy without birth (i.e. miscarriage/abortion), while automatically applying the correct postpartum length
 * @param {App.Entity.SlaveState} slave
 */
window.TerminatePregnancy = function(slave) {
	if (slave.bellyPreg > 1500) {
		// late term - highly fertile slaves spring back quicker
		if (slave.geneticQuirks.fertility+slave.geneticQuirks.hyperFertility >= 4) {
			slave.pregWeek = -2;
		} else if (slave.geneticQuirks.hyperFertility > 1) {
			slave.pregWeek = -3;
		} else {
			slave.pregWeek = -4;
		}
	} else if (slave.pregWeek >= 4) {
		// still early
		slave.pregWeek = -2;
	} else if (slave.pregWeek > 0) {
		// very early
		slave.pregWeek = -1;
	}
	WombFlush(slave);
	SetBellySize(slave);
};

repo_name: IchthysdeKilt/fc

license: mit


code:
window.sortDomObjects = function(objects, attrName, reverse = 0) {
	reverse = (reverse) ? -1 : 1;

	function sortingByAttr(a, b) {
		let aVal = a.getAttribute(attrName);
		let bVal = b.getAttribute(attrName);
		let aInt = parseInt(aVal);
		if (!isNaN(aInt)) {
			return ((parseInt(bVal) - aInt) * reverse);
		} else if (bVal > aVal) {
			return -1 * reverse;
		}
		return ((aVal === bVal) ? 0 : 1) * reverse;
	}
	return objects.toArray().sort(sortingByAttr);
};

window.sortButtonsByDevotion = function() {
	let $sortedButtons = $('#qlWrapper button').remove();
	$sortedButtons = sortDomObjects($sortedButtons, 'data-devotion');
	$($sortedButtons).appendTo($('#qlWrapper'));
	quickListBuildLinks();
};

window.sortButtonsByTrust = function() {
	let $sortedButtons = $('#qlWrapper button').remove();
	$sortedButtons = sortDomObjects($sortedButtons, 'data-trust');
	$($sortedButtons).appendTo($('#qlWrapper'));
	quickListBuildLinks();
};

window.quickListBuildLinks = function() {
	$("[data-scroll-to]").click(App.UI.quickBtnScrollToHandler);
};

App.UI.quickBtnScrollToHandler = function() {
	let $this = $(this),
		$toElement = $this.attr('data-scroll-to');
	// note the * 1 enforces $offset to be an integer, without
	// it we scroll to True, which goes nowhere fast.
	let $offset = $this.attr('data-scroll-offset') * 1 || 0;
	let $speed = $this.attr('data-scroll-speed') * 1 || 500;
	// Use javascript scrollTop animation for in page navigation.
	$('html, body').animate({
		scrollTop: $($toElement).offset().top + $offset
	}, $speed);
};

window.sortIncubatorPossiblesByName = function() {
	let $sortedIncubatorPossibles = $('#qlIncubator div.possible').detach();
	$sortedIncubatorPossibles = sortDomObjects($sortedIncubatorPossibles, 'data-name');
	$($sortedIncubatorPossibles).appendTo($('#qlIncubator'));
};

window.sortIncubatorPossiblesByPregnancyWeek = function() {
	let $sortedIncubatorPossibles = $('#qlIncubator div.possible').detach();
	$sortedIncubatorPossibles = sortDomObjects($sortedIncubatorPossibles, 'data-preg-week');
	$($sortedIncubatorPossibles).appendTo($('#qlIncubator'));
};

window.sortIncubatorPossiblesByPregnancyCount = function() {
	let $sortedIncubatorPossibles = $('#qlIncubator div.possible').detach();
	$sortedIncubatorPossibles = sortDomObjects($sortedIncubatorPossibles, 'data-preg-count');
	$($sortedIncubatorPossibles).appendTo($('#qlIncubator'));
};

window.sortIncubatorPossiblesByReservedSpots = function() {
	let $sortedIncubatorPossibles = $('#qlIncubator div.possible').detach();
	$sortedIncubatorPossibles = sortDomObjects($sortedIncubatorPossibles, 'data-reserved-spots');
	$($sortedIncubatorPossibles).appendTo($('#qlIncubator'));
};

window.sortIncubatorPossiblesByPreviousSort = function() {
	let sort = State.variables.sortIncubatorList;
	if (sort !== 'unsorted') {
		if (sort === 'Name') {
			sortIncubatorPossiblesByName();
		} else if (sort === 'Reserved Incubator Spots') {
			sortIncubatorPossiblesByReservedSpots();
		} else if (sort === 'Pregnancy Week') {
			sortIncubatorPossiblesByPregnancyWeek();
		} else if (sort === 'Number of Children') {
			sortIncubatorPossiblesByPregnancyCount();
		}
	}
};

window.sortNurseryPossiblesByName = function() {
	let $sortedNurseryPossibles = $('#qlNursery div.possible').detach();
	$sortedNurseryPossibles = sortDomObjects($sortedNurseryPossibles, 'data-name');
	$($sortedNurseryPossibles).appendTo($('#qlNursery'));
};

window.sortNurseryPossiblesByPregnancyWeek = function() {
	let $sortedNurseryPossibles = $('#qlNursery div.possible').detach();
	$sortedNurseryPossibles = sortDomObjects($sortedNurseryPossibles, 'data-preg-week');
	$($sortedNurseryPossibles).appendTo($('#qlNursery'));
};

window.sortNurseryPossiblesByPregnancyCount = function() {
	let $sortedNurseryPossibles = $('#qlNursery div.possible').detach();
	$sortedNurseryPossibles = sortDomObjects($sortedNurseryPossibles, 'data-preg-count');
	$($sortedNurseryPossibles).appendTo($('#qlNursery'));
};

window.sortNurseryPossiblesByReservedSpots = function() {
	let $sortedNurseryPossibles = $('#qlNursery div.possible').detach();
	$sortedNurseryPossibles = sortDomObjects($sortedNurseryPossibles, 'data-reserved-spots');
	$($sortedNurseryPossibles).appendTo($('#qlNursery'));
};

window.sortNurseryPossiblesByPreviousSort = function() {
	let sort = State.variables.sortNurseryList;
	if (sort !== 'unsorted') {
		if (sort === 'Name') {
			sortNurseryPossiblesByName();
		} else if (sort === 'Reserved Nursery Spots') {
			sortNurseryPossiblesByReservedSpots();
		} else if (sort === 'Pregnancy Week') {
			sortNurseryPossiblesByPregnancyWeek();
		} else if (sort === 'Number of Children') {
			sortNurseryPossiblesByPregnancyCount();
		}
	}
};


code:
/**
 * generate two independent Gaussian numbers using Box-Muller transform.
 * mean and deviation specify the desired mean and standard deviation.
 * @returns {number[]}
 */
window.gaussianPair = function(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 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.
window.normalRandInt = function(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
 * @returns {number}
 */
window.jsRandom = function(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.
 * @param {number[]} arr
 * @param {number} count
 * @returns {number[]}
 */
window.jsRandomMany = function(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.
 * @param {number|string|number[]|string[]} choices
 * @param {...(number|string|number[]|string[])} otherChoices
 * @returns {number|string|number[]|string[]|undefined}
 */
window.jsEither = function(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:
/* This is modified radiobutton macro, for automatic checked state setup*/
/* Usage (be sure to use quotes around parameters):

	<<rbutton "$variable" "value">>
Or:
	<<rbutton "$variable" "value" "HTML_element_ID" "Text to replace with, inside html element with ID from previous parameter. <br> HTML tags allowed.">>

Group of radiobutton will be created based on variable name. Checked state will be set up if variable contain value matched with second parameter. Full form of macro call can be used to display extended description of selected value.
*/

Macro.add('rbutton', {
	handler() {
		if (this.args.length < 2) {
			const errors = [];
			if (this.args.length === 0) { errors.push('variable name'); }
			if (this.args.length < 2) { errors.push('checked value'); }
			return this.error(`no ${errors.join(' or ')} specified`);
		}

		// Ensure that the variable name argument is a string.
		if (typeof this.args[0] !== 'string') {
			return this.error('variable name argument is not a string');
		}

		const varName = this.args[0].trim();

		// Try to ensure that we receive the variable's name (incl. sigil), not its value.
		if (varName[0] !== '$' && varName[0] !== '_') {
			return this.error(`variable name "${this.args[0]}" is missing its sigil ($ or _)`);
		}

		const initValue = Wikifier.getValue(this.args[0]);
		const varId = Util.slugify(varName);
		const checkValue = this.args[1];
		const el = document.createElement('input');

		let replaceID = "";
		let replaceText = "";
		if (typeof this.args[2] === 'string') {
			replaceID = this.args[2];
		}
		if (typeof this.args[3] === 'string') {
			replaceText = this.args[3];
		}

		/*
		Setup and initialize the group counter.
		*/
		if (!TempState.hasOwnProperty(this.name)) {
			TempState[this.name] = {};
		}

		if (!TempState[this.name].hasOwnProperty(varId)) {
			TempState[this.name][varId] = 0;
		}

		/*
		Setup and append the input element to the output buffer.
		*/
		jQuery(el)
			.attr({
				id: `${this.name}-${varId}-${TempState[this.name][varId]++}`,
				name: `${this.name}-${varId}`,
				type: 'radio',
				tabindex: 0 // for accessibility
			})
			.addClass(`macro-${this.name}`)
			.on('change', function() {
				if (this.checked) {
					Wikifier.setValue(varName, checkValue);

					if (replaceID.length > 0 && replaceText.length > 0) {
						let replaceEl = document.getElementById(replaceID);
						// alert (replaceEl);
						if (replaceEl !== null) {
							replaceEl.innerHTML = replaceText;
						}
					}
				}
			})
			.ready(function() {
				// alert ("DOM finished");
				if (el.checked && replaceID.length > 0 && replaceText.length > 0) {
					let replaceEl = document.getElementById(replaceID);
					// alert (replaceEl);
					if (replaceEl !== null) {
						replaceEl.innerHTML = replaceText;
					}
				}
			})
			.appendTo(this.output);

		/*
		Set the story variable to the checked value and the input element to checked, if requested.
		*/
		if (initValue === checkValue) {
			el.checked = true;
			Wikifier.setValue(varName, checkValue);
		}
	}
});


code:
/**
 * @param {{ rivalry: number; }} id
 * @returns {string}
 */
window.rivalryTerm = function(id) {
	if (id.rivalry === 1) {
		return "growing rival";
	} else if (id.rivalry === 2) {
		return "rival";
	} else {
		return "bitter rival";
	}
};

/**
 * @param {{ relationship: number; }} id
 * @returns {string}
 */
window.relationshipTerm = function(id) {
	if (id.relationship === 1) {
		return "friend";
	} else if (id.relationship === 2) {
		return "best friend";
	} else if (id.relationship === 3) {
		return "friend with benefits";
	} else if (id.relationship === 4) {
		return "lover";
	} else {
		return `slave ${getPronouns(id).wife}`;
	}
};

/**
 * @param {{ relationship: number; }} id
 * @returns {string}
 */
window.relationshipTermShort = function(id) {
	if (id.relationship === 1) {
		return "friend";
	} else if (id.relationship === 2) {
		return "BFF";
	} else if (id.relationship === 3) {
		return "FWB";
	} else if (id.relationship === 4) {
		return "lover";
	} else {
		return `${getPronouns(id).wife}`;
	}
};


code:
/**
 * Returns true if two slaves are allowed to have sex according to the rules.
 * @param {App.Entity.SlaveState} slaveA
 * @param {App.Entity.SlaveState} slaveB
 * @returns {boolean}
 */
App.Utils.sexAllowed = function sexAllowed(slaveA, slaveB) {
	/* check most specific to least specific - master, partner, family, slaves */
	if (slaveA === V.PC) {
		return slaveB.rules.release.master === 1;
	} else if (slaveB === V.PC) {
		return slaveA.rules.release.master === 1;
	} else if (haveRelationshipP(slaveA, slaveB)) {
		return (slaveA.rules.release.partner === 1) && (slaveB.rules.release.partner === 1);
	} else if (V.familyTesting === 0 && haveRelationP(slaveA, slaveB)) {
		return V.seeIncest && (slaveA.rules.release.family === 1) && (slaveB.rules.release.family === 1);
	} else if (V.familyTesting === 1 && areRelated(slaveA, slaveB)) {
		return V.seeIncest && (slaveA.rules.release.family === 1) && (slaveB.rules.release.family === 1);
	} else {
		return (slaveA.rules.release.slaves === 1) && (slaveB.rules.release.slaves === 1);
	}
};

/**
 * Returns true if a slave has a romantic partner other than the PC who is both willing and allowed to have sex with her.
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
App.Utils.hasPartnerSex = function hasPartnerSex(slave) {
	const hasWillingSlavePartner = (slave.rules.relationship === "permissive") && (slave.relationship >= 3) && (slave.relationshipTarget > 0);
	return hasWillingSlavePartner && this.sexAllowed(slave, getSlave(slave.relationshipTarget));
};

/**
 * Returns true if a slave has a close family member other than the PC who is both willing and allowed to have sex with her.
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
App.Utils.hasFamilySex = function hasFamilySex(slave) {
	if (V.seeIncest === 0 || slave.rules.release.family === 0) {
		return false;
	}
	if (V.familyTesting === 0 && slave.relationTarget > 0) {
		return this.sexAllowed(slave, getSlave(slave.relationTarget));
	} else { // familyTesting === 1
		return jsDef(randomRelatedSlave(slave, (s) => { return this.sexAllowed(slave, s); }));
	}
};

/**
 * Returns true if the slave has any kind of nonassignment sex with someone other than the PC.
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
App.Utils.hasNonassignmentSex = function hasNonassignmentSex(slave) {
	return (slave.rules.release.slaves === 1) || this.hasFamilySex(slave) || this.hasPartnerSex(slave);
};

/**
 * Returns true if there is any restriction at all on how a slave may choose to get off.
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean}
 */
App.Utils.releaseRestricted = function releaseRestricted(slave) {
	return (slave.rules.release.slaves === 0) || (slave.rules.release.family === 0) || (slave.rules.release.masturbation === 0) || (slave.rules.release.partner === 0);
};

/**
 * Returns a short summary of the slave's release rules
 * @param {App.Entity.SlaveState} slave
 * @returns {string}
 */
App.Utils.releaseSummaryShort = function releaseSummaryShort(slave) {
	const rel = slave.rules.release;
	let ret = "";
	if (rel.masturbation === 1) {
		ret += "M";
	}
	if (rel.partner === 1) {
		ret += "P";
	}
	if (rel.family === 1 && V.seeIncest === 1) {
		ret += "F";
	}
	if (rel.slaves === 1) {
		ret += "O";
	}
	if (rel.master === 1) {
		ret += "Y";
	}
	if (ret === "") {
		ret = "None";
	}
	return ret;
};

/**
 * Returns a longer summary of the slave's release rules
 * @param {App.Entity.SlaveState} slave
 * @returns {string}
 */
App.Utils.releaseSummaryLong = function releaseSummaryLong(slave) {
	const rel = slave.rules.release;
	const includeFamily = (rel.family === 1) && (V.seeIncest === 1);
	if (rel.masturbation === 0 && rel.partner === 0 && !includeFamily && rel.slaves === 0 && rel.master === 0) {
		return "chastity";
	} else if (rel.masturbation === 1 && rel.partner === 0 && !includeFamily && rel.slaves === 0 && rel.master === 0) {
		return "masturbation only";
	} else if (rel.masturbation === 0 && rel.partner === 1 && !includeFamily && rel.slaves === 0 && rel.master === 0) {
		return "partner only";
	} else if (rel.masturbation === 0 && rel.partner === 0 && includeFamily && rel.slaves === 0 && rel.master === 0) {
		return "family only";
	} else if (rel.masturbation === 0 && rel.partner === 0 && !includeFamily && rel.slaves === 0 && rel.master === 1) {
		return "you only";
	} else if (rel.slaves === 1) {
		let ret = "permissive";
		let exceptions = [];
		if (rel.partner === 0) {
			exceptions.push("partner");
		}
		if (!includeFamily) {
			exceptions.push("family");
		}
		if (rel.master === 0) {
			exceptions.push("you");
		}
		if (exceptions.length > 0) {
			ret += " except " + exceptions.reduce(function(res, ch, i, arr) { return res + (i === arr.length - 1 ? ' and ' : ', ') + ch; });
		}
		if (rel.masturbation === 0) {
			ret += ", no masturbation";
		}
		return ret;
	} else {
		let permissions = [];
		if (rel.masturbation === 1) {
			permissions.push("masturbation");
		}
		if (rel.partner === 1) {
			permissions.push("partner");
		}
		if (includeFamily) {
			permissions.push("family");
		}
		if (rel.master === 1) {
			permissions.push("you");
		}
		if (permissions.length < 1) { return "unknown"; } // probably means BC didn't get run, but let's not die because of it
		return permissions.reduce(function(res, ch, i, arr) { return res + (i === arr.length - 1 ? ' and ' : ', ') + ch; });
	}
};

/**
 * Returns a description of the slave's release rules
 * @param {App.Entity.SlaveState} slave
 * @returns {string}
 */
App.Desc.releaseDesc = function releaseDesc(slave) {
	const rel = slave.rules.release;
	const includeFamily = (rel.family === 1) && (V.seeIncest === 1);
	let r = "and ";
	let appendFrequency = false;
	if (rel.masturbation === 0 && rel.partner === 0 && !includeFamily && rel.slaves === 0 && rel.master === 0) {
		r += "$he is to remain completely chaste.";
	} else if (rel.masturbation === 1 && rel.partner === 0 && !includeFamily && rel.slaves === 0 && rel.master === 0) {
		r += "$he is only allowed to masturbate";
		if (slave.energy > 95) {
			r += ", which $he is constantly doing.";
		} else {
			r += ".";
		}
	} else if (rel.masturbation === 0 && rel.partner === 1 && !includeFamily && rel.slaves === 0 && rel.master === 0) {
		r += "$he is not allowed to masturbate or proposition ";
		if (slave.rules.relationship === "permissive" && slave.relationship >= 3) {
			r += `slaves other than $his ${relationshipTerm(slave)}.`;
		} else {
			r += "other slaves; $he must find sexual release in $his duties.";
		}
	} else if (rel.masturbation === 0 && rel.partner === 0 && includeFamily && rel.slaves === 0 && rel.master === 0) {
		r += "$he is not allowed to masturbate. $He is only allowed to achieve sexual release with close family members,";
		appendFrequency = true;
	} else if (rel.masturbation === 0 && rel.partner === 0 && !includeFamily && rel.slaves === 0 && rel.master === 1) {
		r += "$he is not allowed to masturbate. $He must find you if $he wants sexual release,";
		appendFrequency = true;
	} else if (rel.slaves === 1) {
		if (rel.masturbation === 0) {
			r += "$he is not allowed to masturbate, but may ";
		} else {
			r += "$he is allowed to masturbate. $He may also ";
		}
		if (V.universalRulesConsent === 1) {
			r += "proposition other slaves to find sexual release,";
		} else {
			r += "demand sex from other slaves,";
		}

		const exceptPartner = (rel.partner === 0) && (slave.rules.relationship === "permissive") && (slave.relationship >= 3);
		if (exceptPartner && !includeFamily) {
			r += ` except for $his ${relationshipTerm(slave)} and close family members,`;
		} else if (exceptPartner) {
			r += ` except for $his ${relationshipTerm(slave)},`;
		} else if (!includeFamily) {
			r += ` except for $his close family members,`;
		}

		appendFrequency = true;
	} else {
		if (rel.masturbation === 0) {
			r += "$he is not allowed to masturbate, but may ";
		} else {
			r += "$he is allowed to masturbate. $He may also ";
		}

		const showPartner = (rel.partner === 1) && (slave.rules.relationship === "permissive") && (slave.relationship >= 3);
		if (includeFamily && showPartner) {
			r += `have sex with $his ${relationshipTerm(slave)} and close family members,`;
		} else if (showPartner) {
			r += `fuck $his ${relationshipTerm(slave)} as much as he wants,`;
		} else if (includeFamily && rel.master === 1) {
			r += `proposition sex from $his close family members and you,`;
		} else if (includeFamily) {
			r += `proposition sex from $his close family members,`;
		} else { // should mean rel.master === 1
			r += `find you for sexual relief,`;
		}

		appendFrequency = true;
	}
	if (appendFrequency) {
		if ((slave.devotion > 50) || (slave.energy > 95)) {
			r += " which $he is constantly doing.";
		} else if (slave.devotion > 20) {
			r += " which $he is often willing to do.";
		} else {
			r += " which $he is rarely willing to do.";
		}
	}
	return r;
};

App.Utils.testAllReleaseText = function testAllReleaseText() {
	let slave = new App.Entity.SlaveState();
	slave.rules.relationship = "permissive";
	slave.relationship = 4;
	slave.relationshipTarget = -1;
	let r = "";
	for (let i = 0; i < Math.pow(2, 5); ++i) {
		const bits = i.toString(2).padStart(5, "0");
		let rule = new App.Entity.ReleaseRulesState();
		rule.masturbation = Number(bits[0]);
		rule.partner = Number(bits[1]);
		rule.family = Number(bits[2]);
		rule.slaves = Number(bits[3]);
		rule.master = Number(bits[4]);
		slave.rules.release = rule;

		r += JSON.stringify(rule) + "\n";
		r += App.Utils.releaseSummaryShort(slave) + "\n";
		r += App.Utils.releaseSummaryLong(slave) + "\n";
		r += App.Desc.releaseDesc(slave) + "\n";
	}
	return r;
};


code:
window.removeActiveSlave = function removeActiveSlave() {
	"use strict";

	const AS_ID = V.activeSlave.ID;
	let LENGTH = V.slaves.length;
	const INDEX = V.slaveIndices[AS_ID];
	let missing = false;

	WombChangeID(V.PC, AS_ID, V.missingParentID);
	if (V.PC.pregSource === V.missingParentID) {
		missing = true;
	}

	if (V.PC.mother === AS_ID) {
		V.PC.mother = V.missingParentID;
		missing = true;
	}
	if (V.PC.father === AS_ID) {
		V.PC.father = V.missingParentID;
		missing = true;
	}
	if (V.PC.sisters > 0) {
		if (areSisters(V.PC, V.activeSlave) > 0) {
			V.PC.sisters--;
		}
	}
	if (V.PC.daughters > 0) {
		if (V.activeSlave.father === -1 || V.activeSlave.mother === -1) {
			V.PC.daughters--;
		}
	}

	if (INDEX >= 0 && INDEX < LENGTH) {
		if (V.incubator > 0) {
			V.tanks.forEach(child => {
				if (AS_ID === child.mother) {
					child.mother = V.missingParentID;
					missing = true;
				}
				if (AS_ID === child.father) {
					child.father = V.missingParentID;
					missing = true;
				}
			});
		}
		if (V.nursery > 0) {
			V.cribs.forEach(child => {
				if (AS_ID === child.mother) {
					child.mother = V.missingParentID;
					missing = true;
				}
				if (AS_ID === child.father) {
					child.father = V.missingParentID;
					missing = true;
				}
			});
		}
		V.slaves.forEach(slave => {
			WombChangeID(slave, AS_ID, V.missingParentID); /* This check is complex, should be done in JS now, all needed will be done here. */
			WombChangeGeneID(slave, AS_ID, V.missingParentID);
			if (slave.pregSource === V.missingParentID) {
				missing = true;
			}
			if (V.activeSlave.daughters > 0) {
				if (slave.mother === AS_ID) {
					slave.mother = V.missingParentID;
				}
				if (slave.father === AS_ID) {
					slave.father = V.missingParentID;
				}
				missing = true;
			}
			if (V.activeSlave.mother > 0 || V.activeSlave.father > 0) {
				if (V.activeSlave.mother === slave.ID || V.activeSlave.father === slave.ID) {
					slave.daughters--;
				}
			}
			if (V.activeSlave.sisters > 0) {
				if (areSisters(V.activeSlave, slave) > 0) {
					slave.sisters--;
				}
			}
			if (slave.ID === V.activeSlave.relationTarget) {
				slave.relation = 0;
				slave.relationTarget = 0;
			}
			if (slave.milkSource !== 0) {
				if (slave.milkSource === AS_ID) {
					slave.milkSource = 0;
					slave.inflation = 0;
					slave.inflationType = "none";
					slave.inflationMethod = 0;
				}
			}
			if (slave.cumSource !== 0) {
				if (slave.cumSource === AS_ID) {
					slave.cumSource = 0;
					slave.inflation = 0;
					slave.inflationType = "none";
					slave.inflationMethod = 0;
				}
			}
			if (slave.ID === V.activeSlave.relationshipTarget) {
				slave.relationship = 0;
				slave.relationshipTarget = 0;
			}
			if (slave.ID === V.activeSlave.rivalryTarget) {
				slave.rivalry = 0;
				slave.rivalryTarget = 0;
			}
			/* moved to saDevotion as a discovery event
				if (slave.origBodyOwnerID === AS_ID) {
				slave.origBodyOwnerID = 0;
				}
			*/
		});

		/* remove from Pit fighters list, if needed */
		V.fighterIDs.delete(AS_ID);

		/* remove from Coursing Association, if needed */
		if (V.Lurcher !== 0 && V.Lurcher.ID === AS_ID) {
			V.Lurcher = 0;
		}

		if (Array.isArray(V.personalAttention)) {
			V.personalAttention.deleteWith(s => s.ID === AS_ID);
			if (V.personalAttention.length === 0) {
				if (V.PC.career === "escort") {
					V.personalAttention = "whoring";
				} else if (V.PC.career === "servant") {
					V.personalAttention = "upkeep";
				} else {
					V.personalAttention = "business";
				}
			}
		}

		/* Remove from facility array or leadership role, if needed */
		removeJob(V.activeSlave, V.activeSlave.assignment);

		if (V.traitor !== 0) {
			missing = true; /* no exceptions, fetus system relies on this */
			if (AS_ID === V.traitor.pregSource) {
				V.traitor.pregSource = 0;
			}
			if (V.traitor.mother === AS_ID) {
				V.traitor.mother = V.missingParentID;
			}
			if (V.traitor.father === AS_ID) {
				V.traitor.father = V.missingParentID;
			}
			if (V.traitor.origBodyOwnerID === AS_ID) {
				V.traitor.origBodyOwnerID = 0;
			}
		}
		if (V.boomerangSlave !== 0) {
			missing = true;
			if (AS_ID === V.boomerangSlave.pregSource) {
				V.boomerangSlave.pregSource = 0;
			}
			if (V.boomerangSlave.mother === AS_ID) {
				V.boomerangSlave.mother = V.missingParentID;
			}
			if (V.boomerangSlave.father === AS_ID) {
				V.boomerangSlave.father = V.missingParentID;
			}
			if (V.boomerangSlave.origBodyOwnerID === AS_ID) {
				V.traitor.origBodyOwnerID = 0;
			}
		}

		V.organs.deleteWith(s => s.ID === AS_ID);
		V.completedOrgans.deleteWith(s => s.ID === AS_ID);

		for (let _o = 0; _o < V.adjustProsthetics.length; _o++) {
			if (V.adjustProsthetics[_o].ID === AS_ID) {
				V.adjustProsthetics.deleteAt(_o);
				V.adjustProstheticsCompleted--;
				_o--;
			}
		}

		const _geneIndex = V.genePool.findIndex(s => s.ID === AS_ID);
		if (_geneIndex !== -1) {
			let keep = false;
			if (V.traitor !== 0) {
				if (isImpregnatedBy(V.traitor, V.activeSlave) || V.traitor.ID === AS_ID) {
					/* did we impregnate the traitor, or are we the traitor? */
					keep = true;
				}
			}
			if (V.boomerangSlave !== 0) {
				if (isImpregnatedBy(V.boomerangSlave, V.activeSlave) || V.boomerangSlave.ID === AS_ID) {
					/* did we impregnate the boomerang, or are we the boomerang? */
					keep = true;
				}
			}
			if (isImpregnatedBy(V.PC, V.activeSlave)) {
				/* did we impregnate the PC */
				keep = true;
			}
			if (!keep) {
				/* avoid going through this loop if possible */
				keep = V.slaves.some(slave => {
					/* have we impregnated a slave that is not ourselves? */
					return (slave.ID !== AS_ID && isImpregnatedBy(slave, V.activeSlave));
				});
			}
			if (!keep) {
				V.genePool.deleteAt(_geneIndex);
			}
		}
		if (missing) {
			V.missingTable[V.missingParentID] = {
				slaveName: V.activeSlave.slaveName,
				slaveSurname: V.activeSlave.slaveSurname,
				fullName: SlaveFullName(V.activeSlave),
				dick: V.activeSlave.dick,
				vagina: V.activeSlave.vagina,
				ID: V.missingParentID
			};
			if (V.traitor.ID === V.activeSlave.ID) {
				/* To link developing fetuses to their parent */
				V.traitor.missingParentTag = V.missingParentID;
			} else if (V.boomerangSlave.ID === V.activeSlave.ID) {
				V.boomerangSlave.missingParentTag = V.missingParentID;
			}
			V.missingParentID--;
		}

		removeSlave(INDEX);
		LENGTH--;
		V.activeSlave = 0;
		V.JobIDArray = resetJobIDArray(); /* need to call this once more to update count of resting slaves*/
	}
};
/**
 * @param {App.Entity.SlaveState} removedSlave
 */
window.removeNonNGPSlave = function removeNonNGPSlave(removedSlave) {
	"use strict";
	const ID = removedSlave.ID;
	let LENGTH = V.slaves.length;
	const INDEX = V.slaveIndices[ID];
	let missing = false;

	// Only bother if PC is being kept
	if (V.freshPC !== 1) {
		WombChangeID(V.PC, ID, V.missingParentID);
		if (V.PC.pregSource === V.missingParentID) {
			missing = true;
		}
		if (V.PC.mother === ID) {
			V.PC.mother = V.missingParentID;
			missing = true;
		}
		if (V.PC.father === ID) {
			V.PC.father = V.missingParentID;
			missing = true;
		}
		if (V.PC.sisters > 0) {
			if (areSisters(V.PC, removedSlave) > 0) {
				V.PC.sisters--;
			}
		}
		if (V.PC.daughters > 0) {
			if (removedSlave.father === -1 || removedSlave.mother === -1) {
				V.PC.daughters--;
			}
		}
	}

	if (INDEX >= 0 && INDEX < LENGTH) {
		V.slaves.forEach(slave => {
			if (slave.assignment === "be imported") {
				WombChangeID(slave, ID, V.missingParentID); /* This check is complex, should be done in JS now, all needed will be done here. */
				WombChangeGeneID(slave, ID, V.missingParentID);
				if (slave.pregSource === V.missingParentID) {
					missing = true;
				}
				if (removedSlave.daughters > 0) {
					if (slave.mother === ID) {
						slave.mother = V.missingParentID;
					}
					if (slave.father === ID) {
						slave.father = V.missingParentID;
					}
					missing = true;
				}
				if (slave.ID === removedSlave.relationTarget) {
					slave.relation = 0;
					slave.relationTarget = 0;
				}
				if (slave.milkSource !== 0) {
					if (slave.milkSource === ID) {
						slave.milkSource = 0;
						slave.inflation = 0;
						slave.inflationType = "none";
						slave.inflationMethod = 0;
					}
				}
				if (slave.cumSource !== 0) {
					if (slave.cumSource === ID) {
						slave.cumSource = 0;
						slave.inflation = 0;
						slave.inflationType = "none";
						slave.inflationMethod = 0;
					}
				}
				if (slave.ID === removedSlave.relationshipTarget) {
					slave.relationship = 0;
					slave.relationshipTarget = 0;
				}
				if (slave.ID === removedSlave.rivalryTarget) {
					slave.rivalry = 0;
					slave.rivalryTarget = 0;
				}
			}
		});

		const _geneIndex = V.genePool.findIndex(s => s.ID === ID);
		if (_geneIndex !== -1) {
			let keep = false;
			if (isImpregnatedBy(V.PC, removedSlave)) {
				/* did we impregnate the PC */
				keep = true;
			}
			if (!keep) {
				/* avoid going through this loop if possible */
				keep = V.slaves.some(slave => {
					/* have we impregnated a slave that is not ourselves? */
					return (slave.ID !== ID && isImpregnatedBy(slave, removedSlave));
				});
			}
			if (!keep) {
				V.genePool.deleteAt(_geneIndex);
			}
		}
		if (missing) {
			V.missingTable[V.missingParentID] = {
				slaveName: removedSlave.slaveName,
				slaveSurname: removedSlave.slaveSurname,
				fullName: SlaveFullName(removedSlave),
				dick: removedSlave.dick,
				vagina: removedSlave.vagina,
				ID: V.missingParentID
			};
			V.missingParentID--;
		}

		removeSlave(INDEX);
	}
};


code:
/**
 * @param {App.Entity.SlaveState} slave
 * @param {Object[]} rules
 * @returns {boolean}
 */
window.hasSurgeryRule = function(slave, rules) {
	return rules.some(
		rule => ruleApplied(slave, rule) && rule.set.autoSurgery > 0);
};

/**
 * @param {App.Entity.SlaveState} slave
 * @param {Object[]} rules
 * @param {string} what
 * @returns {boolean}
 */
window.hasRuleFor = function(slave, rules, what) {
	return rules.some(
		rule => ruleApplied(slave, rule) && rule[what] !== null);
};

/**
 * @param {App.Entity.SlaveState} slave
 * @param {Object[]} rules
 * @returns {boolean}
 */
window.hasHColorRule = function(slave, rules) {
	return hasRuleFor(slave, rules, "hColor");
};

/**
 * @param {App.Entity.SlaveState} slave
 * @param {App.RA.Rule[]} rules
 * @returns {boolean}
 * */
window.hasHStyleRule = function(slave, rules) {
	return hasRuleFor(slave, rules, "hStyle");
};

/**
 * @param {App.Entity.SlaveState} slave
 * @param {App.RA.Rule[]} rules
 * @returns {boolean}
 * */
window.hasEyeColorRule = function(slave, rules) {
	return hasRuleFor(slave, rules, "eyeColor");
};

/**
 * return if a rule is applied on a slave
 * @param {App.Entity.SlaveState} slave
 * @param {App.RA.Rule[]} rules
 * @returns {boolean}
 */
window.lastPregRule = function(slave, rules) {
	return rules.some(rule =>
		ruleApplied(slave, rule) && rule.set.preg === -1);
};

/**
 * @param {App.RA.RuleSetters[]} rules
 * @returns {App.RA.RuleSetters}
 */
window.mergeRules = function(rules) {
	if (rules.length === 0) {
		return emptyDefaultRule().set;
	}

	const combinedRule = emptyDefaultRule().set;

	rules.forEach(rule => {
		App.RA.ruleDeepAssign(combinedRule, rule);
	});
	return combinedRule;
};

/**
 * return if a rule is applied on a slave
 * @param {App.Entity.SlaveState} slave
 * @param {App.RA.Rule} rule
 * @returns {boolean}
 */
window.ruleApplied = function(slave, rule) {
	return slave.currentRules.includes(rule.ID);
};

/**
 * remove slave from the facility described by the rule
 * @param {App.Entity.SlaveState} slave
 * @param {Object} rule
 * @returns {string}
 */
window.RAFacilityRemove = function RAFacilityRemove(slave, rule) {
	let r = "";
	if (!rule.facilityRemove) { return r; }
	switch (rule.setAssignment) {
		case "be confined in the arcade":
			if (slave.assignment === rule.setAssignment) {
				r += `<br>${slave.slaveName} has been removed from ${V.arcadeName} and has been assigned to ${rule.removalAssignment}.`;
				assignJob(slave, rule.removalAssignment);
			}
			break;

		case "work in the brothel":
			if (slave.assignment === rule.setAssignment) {
				r += `<br>${slave.slaveName} has been removed from ${V.brothelName} and has been assigned to ${rule.removalAssignment}.`;
				assignJob(slave, rule.removalAssignment);
			}
			break;

		case "serve in the club":
			if (slave.assignment === rule.setAssignment) {
				r += `<br>${slave.slaveName} has been removed from ${V.clubName} and has been assigned to ${rule.removalAssignment}.`;
				assignJob(slave, rule.removalAssignment);
			}
			break;

		case "work in the dairy":
			if (slave.assignment === rule.setAssignment) {
				r += `<br>${slave.slaveName} has been removed from ${V.dairyName} and has been assigned to ${rule.removalAssignment}.`;
				assignJob(slave, rule.removalAssignment);
			}
			break;

		case "work as farmhand":
			if (slave.assignment === rule.setAssignment) {
				r += `<br>${slave.slaveName} has been removed from ${V.farmyardName} and has been assigned to ${rule.removalAssignment}.`;
				assignJob(slave, rule.removalAssignment);
			}
			break;

		case "rest in the spa":
			if (slave.assignment === rule.setAssignment) {
				r += `<br>${slave.slaveName} has been removed from ${V.spaName} and has been assigned to ${rule.removalAssignment}.`;
				assignJob(slave, rule.removalAssignment);
			}
			break;

		case "work as a nanny":
			if (slave.assignment === rule.setAssignment) {
				r += `<br>${slave.slaveName} has been removed from ${V.nurseryName} and has been assigned to ${rule.removalAssignment}.`;
				assignJob(slave, rule.removalAssignment);
			}
			break;

		case "get treatment in the clinic":
			if (slave.assignment === rule.setAssignment) {
				r += `<br>{slave.slaveName} has been removed from ${V.clinicName} and has been assigned to ${rule.removalAssignment}.`;
				assignJob(slave, rule.removalAssignment);
			}
			break;

		case "serve in the master suite":
			if (slave.assignment === rule.setAssignment) {
				r += `<br>{slave.slaveName} has been removed from ${V.masterSuiteName} and has been assigned to ${rule.removalAssignment}.`;
				assignJob(slave, rule.removalAssignment);
			}
			break;

		case "live with your Head Girl":
			if (slave.assignment === rule.setAssignment) {
				r += `<br>${slave.slaveName} has been removed from ${V.HGSuiteName} and has been assigned to ${rule.removalAssignment}.`;
				assignJob(slave, rule.removalAssignment);
			}
			break;

		case "work as a servant":
			if (slave.assignment === rule.setAssignment) {
				r += `<br>${slave.slaveName} has been removed from ${V.servantsQuartersName} and has been assigned to ${rule.removalAssignment}.`;
				assignJob(slave, rule.removalAssignment);
			}
			break;

		case "learn in the schoolroom":
			if (slave.assignment === rule.setAssignment) {
				r += `<br>${slave.slaveName} has been removed from ${V.schoolroomName} and has been assigned to ${rule.removalAssignment}.`;
				assignJob(slave, rule.removalAssignment);
			}
			break;

		case "be confined in the cellblock":
			if (slave.assignment === rule.setAssignment) {
				r += `<br>${slave.slaveName} has been removed from ${V.cellblockName} and has been assigned to ${rule.removalAssignment}.`;
				assignJob(slave, rule.removalAssignment);
			}
			break;
	}
};

/**
 * return whether the rule applies to the slave
 * @param {App.RA.RuleConditions} cond
 * @param {App.Entity.SlaveState} slave
 * @returns {boolean} flag */
window.ruleAppliesP = function ruleAppliesP(cond, slave) {
	let flag = true;
	// attribute / function check
	switch (cond.function) {
		case false: // never applies
			flag = false;
			break;
		case "between": // between two values of a slave's attribute
			flag = between(
				slave[cond.data.attribute],
				cond.data.value[0],
				cond.data.value[1]);
			break;
		case "belongs": // the attribute belongs in the list of values
			flag = cond.data.value.includes(slave[cond.data.attribute]);
			break;
		case "custom": // user provided JS function
			//TODO: This should use a cached Function instead of 'eval'ing
			flag = eval(cond.data)(slave);
			break;
	}
	if (!flag) {
		return false;
	}
	// assignment / facility / special slaves / specific slaves check

	return (cond.assignment.length === 0 || cond.assignment.includes(slave.assignment)) &&
		(cond.selectedSlaves.length === 0 || cond.selectedSlaves.includes(slave.ID)) &&
		!(cond.excludedSlaves.includes(slave.ID));
};

App.RA.newRule = function() {
	return {
		rule: emptyRule,
		conditions: emptyConditions,
		setters: emptySetters,
		growth: emptyGrowth,
		surgery: emptySurgery
	};

	/** @returns {App.RA.Rule} */
	function emptyRule() {
		const id = generateNewID();
		return {
			ID: id,
			name: `Rule ${id}`,
			condition: emptyConditions(),
			// TODO: rename properties in snake_case to camelCase?
			set: emptySetters()
		};
	}

	/** @returns {App.RA.RuleConditions} */
	function emptyConditions() {
		return {
			function: false,
			data: {},
			assignment: [],
			selectedSlaves: [],
			excludedSlaves: [],
		};
	}
	/** @returns {App.RA.RuleSetters} */
	function emptySetters() {
		return {
			releaseRules: emptyRelease(),
			toyHole: null,
			clitSetting: null,
			clitSettingXY: null,
			clitSettingXX: null,
			clitSettingEnergy: null,
			speechRules: null,
			clothes: null,
			collar: null,
			shoes: null,
			armAccessory: null,
			legAccessory: null,
			chastityVagina: null,
			chastityAnus: null,
			chastityPenis: null,
			virginAccessory: null,
			aVirginAccessory: null,
			vaginalAccessory: null,
			aVirginDickAccessory: null,
			dickAccessory: null,
			bellyAccessory: null,
			aVirginButtplug: null,
			buttplug: null,
			buttplugAttachment: null,
			vaginalAttachment: null,
			eyeColor: null,
			makeup: null,
			nails: null,
			hColor: null,
			hornColor: null,
			hLength: null,
			haircuts: null,
			hStyle: null,
			eyebrowHColor: null,
			eyebrowHStyle: null,
			eyebrowFullness: null,
			markings: null,
			pubicHColor: null,
			pubicHStyle: null,
			nipplesPiercing: null,
			areolaePiercing: null,
			clitPiercing: null,
			vaginaLube: null,
			vaginaPiercing: null,
			dickPiercing: null,
			anusPiercing: null,
			lipsPiercing: null,
			tonguePiercing: null,
			earPiercing: null,
			nosePiercing: null,
			eyebrowPiercing: null,
			navelPiercing: null,
			corsetPiercing: null,
			boobsTat: null,
			buttTat: null,
			vaginaTat: null,
			dickTat: null,
			lipsTat: null,
			anusTat: null,
			shouldersTat: null,
			armsTat: null,
			legsTat: null,
			backTat: null,
			stampTat: null,
			birthsTat: null,
			abortionTat: null,
			brandDesign: null,
			brandTarget: null,
			scarTarget: null,
			scarDesign: null,
			curatives: null,
			livingRules: null,
			relationshipRules: null,
			standardPunishment: null,
			standardReward: null,
			weight: null,
			diet: null,
			dietCum: null,
			dietMilk: null,
			onDiet: null,
			muscles: null,
			XY: null,
			XX: null,
			gelding: null,
			preg: null,
			abortion: null,
			growth: emptyGrowth(),
			hyper_drugs: 0,
			aphrodisiacs: null,
			autoSurgery: 0,
			autoBrand: 0,
			pornFeed: null,
			pornFameSpending: null,
			dietGrowthSupport: 0,
			eyewear: null,
			earwear: null,
			setAssignment: null,
			pitRules: null,
			facilityRemove: false,
			removalAssignment: "rest",
			surgery: emptySurgery(),
			underArmHColor: null,
			underArmHStyle: null,
			drug: null,
			eyes: null,
			pregSpeed: null,
			bellyImplantVol: -1,
			teeth: null,
			label: null,
			removeLabel: null,
			skinColor: null,
			inflationType: null,
		};
	}

	/** @returns {App.RA.RuleReleaseSetters} */
	function emptyRelease() {
		return {
			masturbation: null,
			partner: null,
			family: null,
			slaves: null,
			master: null
		};
	}

	/** @returns {App.RA.RuleGrowthSetters} */
	function emptyGrowth() {
		return {
			boobs: null,
			butt: null,
			lips: null,
			dick: null,
			balls: null,
			intensity: 0
		};
	}

	/** @returns {App.RA.RuleSurgerySettings} */
	function emptySurgery() {
		return {
			eyes: null,
			hears: null,
			smells: null,
			tastes: null,
			lactation: null,
			prostate: null,
			cosmetic: null,
			accent: null,
			shoulders: null,
			shouldersImplant: null,
			boobs: null,
			hips: null,
			hipsImplant: null,
			butt: null,
			faceShape: null,
			lips: null,
			holes: null,
			tummy: null,
			hair: null,
			bodyhair: null,
			vasectomy: null,
			earShape: null,
			horn: null,
			bellyImplant: null
		};
	}
}();

/**
 * @returns {App.RA.Rule}
 */
window.emptyDefaultRule = App.RA.newRule.rule;

/**
 * Saves the slave, silently fires the RA, saves the slave's after-RA state, and then reverts the slave.
 * Call and then check potential change against $slaveAfterRA to see if the RA would revert it.
 * @param {App.Entity.SlaveState} slave
 */
window.RulesDeconfliction = function RulesDeconfliction(slave) {
	const before = clone(slave);
	DefaultRules(slave);
	State.variables.slaveAfterRA = clone(slave);
	slave = before;
};

/**
 * Creates a table to summarize RA
 * @returns {string}
 */
window.RASummaryCell = function() {
	/**
	 * @param {object[]} objects
	 * @param {string[]} member
	 */
	function collectMemberFromObjects(objects, member) {
		let r = [];
		for (const o of objects) {
			let to = o;
			for (const m of member) {
				to = to[m];
			}
			r.push(to);
		}
		return r;
	}

	/**
	 * @callback ObjectWalker
	 * @param {object} obj
	 * @param {string[]} memberPath
	 */

	/**
	 * @param {object} obj
	 * @param {ObjectWalker} walker
	 * @param {string[]} path
	 */
	function walkObject(obj, walker, path) {
		for (const prop in obj) {
			const v = obj[prop];
			const vp = path.concat([prop]);
			if (v !== null && typeof v === 'object') {
				walkObject(v, walker, vp);
			} else {
				walker(obj, vp);
			}
		}
	}

	/**
	 * @param {string[]} path
	 * @param {Array} cells
	 * @param {string[]} table
	 */
	function addRow(path, cells, table) {
		if (!cells.some(v => v !== null)) { // skip empty rows
			return;
		}

		function ruleSetValueToString(v) {
			if (typeof v === 'object')
			{
				if(v.hasOwnProperty('cond') && v.hasOwnProperty('val')) {
					return `<nowiki>${v.cond}</nowiki> ${v.val}`;
				} else if(v.hasOwnProperty('min') && v.hasOwnProperty('max')) {
					return `${v.min} to ${v.max}`;
				} else {
					return JSON.stringify(v);
				}
			}
			return `${v}`;
		}

		let r = `<td>${path.join('.')}</td>`;
		for (const cell of cells) {
			r += cell !== null ? `<td>${ruleSetValueToString(cell)}</td>` : '	';
		}
		table.push(r);
	}
	/** @type {App.RA.Rule[]} */
	const rules = V.defaultRules;
	let r = "";

	if (rules.length === 0) {
		return '';
	}

	/* start row title */
	r += `<tr><td></td>`;

	/* make rest of row title */
	for (const rule of rules) {
		r += `<td>${rule.name}</td>`;
	}
	r += `</tr>`;

	const setters = rules.map(r => r.set);
	/* A row for every condition the RA can set. */
	/* start loop for row*/

	let tableRows = [];
	walkObject(emptyDefaultRule().set, (obj, path) => {
		addRow(path, collectMemberFromObjects(setters, path), tableRows);
	}, []);

	for (const row of tableRows) {
		r += `<tr>${row}</tr>`;
	}
	return r;
};

/**
 * Creates RA target object used in rules for body properties
 * @param {string} condition comparison condition. One of '==', '>=', '<=', '>', '<'
 * @param {number} val target value
 * @returns {App.RA.NumericTarget}
 */
App.RA.makeTarget = function(condition, val) {
	return {
		cond: condition,
		val: val
	};
};

/**
 * Creates RA range object used in rules
 * @param {number} minValue
 * @param {number} maxValue
 * @returns {App.RA.NumericRange}
 */
App.RA.makeRange = function(minValue, maxValue) {
	return {
		min: minValue, max: maxValue
	};
};

/**
 * Shall the current value be increased according to the target and condition
 * @param {number} current
 * @param {App.RA.NumericTarget} target
 * @param {number} [step=1] change step
 * @returns {boolean}
 */
App.RA.shallGrow = function(current, target, step = 1) {
	return target && (((current < target.val - step) && (target.cond === '==')) ||
		((current < target.val) && (target.cond === '>=' || target.cond === '>')) ||
		(current === target.val && target.cond === '>'));
};

/**
 * Shall the current value be decreased according to the target and condition
 * @param {number} current
 * @param {App.RA.NumericTarget} target
 * @param {number} [step=1]
 * @returns {boolean}
 */
App.RA.shallShrink = function(current, target, step = 1) {
	return target && (((current > target.val + step) && (target.cond === '==')) ||
		((current > target.val) && (target.cond === '<=' || target.cond === '<')) ||
		(current === target.val && target.cond === '<'));
};

App.RA.ruleDeepAssign = function deepAssign(target, source) {
	function isObject(o) {
		return (o !== undefined && o !== null && typeof o === 'object' && !Array.isArray(o));
	}

	for (const key in source) {
		if (!source.hasOwnProperty(key)) {
			continue;
		}
		if (isObject(source[key])) {
			if (!target.hasOwnProperty(key) || target[key] === null) {
				target[key] = {};
			}
			deepAssign(target[key], source[key]);
		} else if (key === "label" || key === "removeLabel") {
			if (source[key] != null) {
				if (target[key] != null) {
					target[key] += "|" + source[key];
				} else {
					target[key] = source[key];
				}
			}
		} else {
			// A rule overrides any preceding ones if,
			// * there are no preceding ones,
			// * or it sets autoBrand,
			// * or it does not set autoBrand and is not null
			const overrides = (
				target[key] === undefined || target[key] === null ||
				(key === "autoBrand" && source[key]) ||
				(key !== "autoBrand" && source[key] !== null));
			if (overrides) {
				target[key] = source[key];
			}
		}
	}
	return target;
};