code:
/** @file This holds data structures that `App.Entity.PlayerState` and `App.Entity.SlaveState` have in common */

/**
 * Returns all HumanState objects;
 * Doesn't return GenePoolRecords from the gene pool;
 * Doesn't return MissingParentRecords from V.missingTable;
 * @param {boolean} [legacy=false]
 * @returns {FC.HumanState[]}
 */
globalThis.getHumanStates = (legacy=false) => {
	// TODO:@franklygeorge once ChildState is implemented add it to this
	let allHumanRecords = []
		.concat(getSlaveStates(legacy))
		.concat([V.PC]);

	return allHumanRecords;
};

/**
 * Keeps track of lots of stats (births, sexual interactions, etc)
 */
App.Entity.HumanActionCountersState = class HumanActionCountersState {
	constructor() {
		/** amount of milk given */
		this.milk = 0;
		/** amount of cum given */
		this.cum = 0;
		/** number of births the slave has ever given */
		this.birthsTotal = 0;
		/** number of abortions while in the arcology */
		this.abortions = 0;
		/** number of miscarriages while in the arcology */
		this.miscarriages = 0;
		/** number of labors they have undergone */
		this.laborCount = 0;
		/** How many slaves they have sired. */
		this.slavesFathered = 0;
		/** How many slaves they have knocked up. */
		this.slavesKnockedUp = 0;
		/** The number of times the actor has had their mouth sexually engaged (blowjob, etc).
		 * Use only for logic checks. To increment, use `seX()`.
		 */
		this.oral = 0;
		/** The number of times the actor has had their vagina sexually engaged (penetration, tribbing, etc).
		 * Use only for logic checks. To increment, use `seX()`.
		 */
		this.vaginal = 0;
		/** The number of times the actor has had their anus sexually engaged (penetration, etc).
		 * Use only for logic checks. To increment, use `seX()`.
		 */
		this.anal = 0;
		/** The number of times the actor has had their breasts sexually engaged (titjob, etc).
		 * Use only for logic checks. To increment, use `seX()`.
		 */
		this.mammary = 0;
		/** The number of times the actor has penetrated an orifice.
		 * Use only for logic checks. To increment, use `seX()`.
		 */
		this.penetrative = 0;
		/** number of pit fights won */
		this.pitWins = 0;
		/** number of pit fights lost */
		this.pitLosses = 0;
		/** number of hymen reconstructions */
		this.reHymen = 0;
	}

	/**
	 * Properties that don't exist on App.Entity.SlaveActionCountersState will be lost
	 * @param {App.Entity.SlaveActionCountersState|App.Entity.PlayerActionCountersState} counter
	 * @returns {App.Entity.SlaveActionCountersState}
	 */
	static convertToSlaveActionCountersState(counter) {
		const newCounter = new App.Entity.SlaveActionCountersState();
		for (const key in counter) {
			if (key in newCounter) {
				newCounter[key] = counter[key];
			}
		}
		return newCounter;
	}

	/**
	 * Properties that don't exist on App.Entity.PlayerActionCountersState will be lost
	 * @param {App.Entity.SlaveActionCountersState|App.Entity.PlayerActionCountersState} counter
	 * @returns {App.Entity.PlayerActionCountersState}
	 */
	static convertToPlayerActionCountersState(counter) {
		const newCounter = new App.Entity.PlayerActionCountersState();
		for (const key in counter) {
			if (key in newCounter) {
				newCounter[key] = counter[key];
			}
		}
		return newCounter;
	}
};

/**
 * Defines how skilled they are at a given subject
 */
App.Entity.HumanSkillsState = class HumanSkillsState {
	constructor() {
		// TODO: some of these may not be fully implemented for events handling the PC
		/**
		 * skill in vaginal sex
		 * * 0-10: unskilled
		 * * 11-30: basic
		 * * 31-60: skilled
		 * * 61-99: expert
		 * * 100+: master
		 */
		this.vaginal = 0;
		/**
		 * skill in penetrative sex
		 * * 0-10: unskilled
		 * * 11-30: basic
		 * * 31-60: skilled
		 * * 61-99: expert
		 * * 100+: master
		 */
		this.penetrative = 0;
		/**
		 * skill in oral sex
		 * * 0-10: unskilled
		 * * 11-30: basic
		 * * 31-60: skilled
		 * * 61-99: expert
		 * * 100+: master
		 */
		this.oral = 0;
		/**
		 * skill in anal sex
		 * * 0-10: unskilled
		 * * 11-30: basic
		 * * 31-60: skilled
		 * * 61-99: expert
		 * * 100+: master
		 */
		this.anal = 0;
	}
};

/**
 * How they are allowed to get their fix
 */
App.Entity.ReleaseRulesState = class ReleaseRulesState {
	constructor(isPC = false) {
		/** Can they masturbate?
		 * @type {FC.Bool} */
		this.masturbation = isPC ? 1 : 0;
		/** Can they fuck their romantic partner (relationship = FWB or higher)?
		 * @type {FC.Bool} */
		this.partner = 1;
		/** Can the development facility leader (Nurse, Attendant, etc) fuck them if they need it?
		 * @type {FC.Bool} */
		this.facilityLeader = 1;
		/** Can they fuck with their close family members (siblings/parents/children)?
		 * @type {FC.Bool} */
		this.family = isPC ? 1 : 0;
		/** Can they fuck the general slave population?
		 * @type {FC.Bool} */
		this.slaves = isPC ? 1 : 0;
		/** Will the master allow them to solicit sex from the master?
		 * @type {FC.Bool} */
		this.master = 1;
	}
};

/**
 * Defines what rules apply to them
 */
App.Entity.RuleState = class RuleState {
	constructor(isPC = false) {
		/**
		 * * "spare"
		 * * "normal"
		 * * "luxurious"
		 * @type {FC.Rules.Living}
		 */
		this.living = isPC ? "luxurious" : "spare";
		/**
		 * * "none"
		 * * "cruel"
		 * * "restrictive"
		 * * "permissive"
		 * * "mandatory"
		 * @type {FC.Rules.Rest}
		 */
		this.rest = isPC ? "permissive" : "restrictive";
		/**
		 * Are they allowed to use mobility aids
		 * * "restrictive"
		 * * "permissive"
		 * @type {FC.Rules.Mobility}
		 */
		this.mobility = isPC ? "permissive" : "restrictive";
		/**
		 * * "restrictive"
		 * * "permissive"
		 * * "accent elimination"
		 * * "language lessons"
		 * @type {FC.Rules.Speech}
		 */
		this.speech = isPC ? "permissive" : "restrictive";
		/** release rules */
		this.release = new App.Entity.ReleaseRulesState(isPC);
		/**
		 * * "restrictive"
		 * * "just friends"
		 * * "permissive"
		 * @type {FC.Rules.Relationship}
		 */
		this.relationship = isPC ? "permissive" : "restrictive";
		/**
		 * * "none"
		 * * "induce"
		 * * "maintain"
		 * @type {FC.Rules.Lactation}
		 */
		this.lactation = "none";
		/**
		 * * "confinement"
		 * * "whipping"
		 * * "chastity"
		 * * "situational"
		 * @type {FC.Rules.Punishment}
		 */
		this.punishment = "situational";
		/**
		 * * "relaxation"
		 * * "drugs"
		 * * "orgasm"
		 * * "situational"
		 * @type {FC.Rules.Reward}
		 */
		this.reward = isPC ? "relaxation" : "situational";
	}
};

/**
 * Encapsulates custom AI prompts
 */
App.Entity.SlaveCustomAIPrompts = class SlaveCustomAIPrompts {
	constructor() {
		/** replaces the slave's posture prompts with a custom string for user-specified poses */
		this.pose = "";
		/** replaces the slave's expression positive prompt with a custom string for user-specified expressions */
		this.expressionPositive = "";
		/** replaces the slave's expression negative prompt with a custom string for user-specified expressions */
		this.expressionNegative = "";
		/** automatically adds to the dynamic positive prompt string */
		this.positiveRA = "";
		/** automatically adds to the dynamic positive prompt string */
		this.negativeRA = "";
		/** manually adds to the dynamic positive prompt string */
		this.positive = "";
		/** manually adds to the dynamic negative prompt string */
		this.negative = "";
	}
};

/**
 * Encapsulates custom poses
 */
App.Entity.SlaveCustomAIPose = class SlaveCustomAIPose {
	constructor() {
		/** custom pose type selected. JSON or PNG for completely custom poses from file (see filename), Library to pick by name (see name).
		 * @type {"Library"|"JSON"|"PNG"}
		 */
		this.type = "Library";
		/** pick a custom pose from the library
		 * @type {keyof App.Data.Art.Poses}
		 */
		this.name = "Standing, Neutral";
		/** load a custom pose from file
		 * @type {string}
		 */
		this.filename = "";
	}
};

/**
 * Custom data defined by the user to be used with HumanState objects
 * @see App.Entity.HumanState
 */
App.Entity.CustomAddonsState = class CustomAddonsState {
	constructor() {
		/** adds a custom tattoo */
		this.tattoo = "";
		/** a label appended after their name */
		this.label = "";
		/** adds a custom description */
		this.desc = "";
		/** What the slave refers to you as. */
		this.title = "";
		/** Replaces SlaveTitle() if set. */
		this.name = "";
		/** What the slave refers to you as, with a lisp.*/
		this.titleLisp = "";
		/**
		 * holds their custom image file name (used if images are enabled)
		 *
		 * null: no custom image
		 * @type {FC.CustomImage}
		 */
		this.image = null;
		/**
		 * holds the custom hair vector base file name
		 *
		 * used if vector images are enabled
		 * @type {FC.Zeroable<string>}
		 */
		this.hairVector = 0;
		/** skips their image in the weekly ai auto regeneration
		 * @type {FC.Bool}
		 * 0: no; 1: yes */
		this.aiAutoRegenExclude = 0;
		/**
		 * holds the ai image ID
		 *
		 * used if ai images are enabled
		 * @type {Array<number>}
		 */
		this.aiImageIds = [];
		/**
		 * holds the index of the displayed AI image in aiImageIds
		 *
		 * used if ai images are enabled
		 * @type {number}
		 */
		this.aiDisplayImageIdx = -1;
		/**
		 * custom AI prompts; may be null or absent
		 * @type {App.Entity.SlaveCustomAIPrompts}
		 */
		this.aiPrompts = null;
		/**
		 * if true then custom AI prompts will overwrite dynamic AI prompts, otherwise they will be appended to the end
		 */
		this.aiPromptsOverwrite = false;
		/**
		 * custom AI pose for OpenPose; may be null or absent.
		 * @type {App.Entity.SlaveCustomAIPose}
		 */
		this.aiPose = null;
	}
};

App.Entity.ScarState = class {
	constructor() {
		/**
		 * generic scar
		 * * 0: no scar
		 * * 1+: increasing intensity of scar
		 */
		this.generic = 0;
		/**
		 * whip scar
		 * * 0: no scar
		 * * 1+: increasing intensity of scar
		 */
		this.whip = 0;
		/**
		 * burn scar
		 * * 0: no scar
		 * * 1+: increasing intensity of scar
		 */
		this.burn = 0;
		/**
		 * surgical scar
		 * * 0: no scar
		 * * 1+: increasing intensity of scar
		 */
		this.surgical = 0;
		/**
		 * cutting scar
		 * * 0: no scar
		 * * 1+: increasing intensity of scar
		 */
		this.cutting = 0;
		/**
		 * chain/manacles scar, focused on wrists ankles or neck
		 * * 0: no scar
		 * * 1+: increasing intensity of scar
		 */
		this.chain = 0;
		/**
		 * exotic scar for the face
		 * * 0: no scar
		 * * 1: scar
		 */
		this.exotic = 0;
		/**
		 * menacing scar for the face
		 * * 0: no scar
		 * * 1: scar
		 */
		this.menacing = 0;
		/**
		 * custom scars can vary in number and will have to be defined when created.
		 */
	}
};

App.Entity.piercingState = class {
	constructor() {
		/** @type {FC.PiercingType} */
		this.weight = 0;
		/**
		 * String describing piercing. Leave empty to use default description.
		 * @type {string}
		 */
		this.desc = "";
	}
};

App.Entity.genitalPiercingState = class extends App.Entity.piercingState {
	constructor() {
		super();
		/** @type {boolean} */
		this.smart = false;
	}
};

App.Entity.piercingStateRA = class {
	constructor() {
		/** @type {FC.PiercingType} */
		this.weight = null;
		/**
		 * String describing piercing. Leave empty to use default description.
		 * @type {string}
		 */
		this.desc = null;
	}
};

App.Entity.genitalPiercingStateRA = class extends App.Entity.piercingStateRA {
	constructor() {
		super();
		/** @type {boolean} */
		this.smart = null;
	}
};

App.Entity.completePiercingState = class {
	constructor() {
		this.ear = new App.Entity.piercingState();
		this.nose = new App.Entity.piercingState();
		this.eyebrow = new App.Entity.piercingState();
		this.lips = new App.Entity.piercingState();
		this.tongue = new App.Entity.piercingState();
		this.nipple = new App.Entity.piercingState();
		this.areola = new App.Entity.piercingState();
		this.navel = new App.Entity.piercingState();
		this.corset = new App.Entity.piercingState();
		this.genitals = new App.Entity.genitalPiercingState();
		this.vagina = new App.Entity.piercingState();
		this.dick = new App.Entity.piercingState();
		this.anus = new App.Entity.piercingState();
	}
};

App.Entity.completePiercingStateRA = class {
	constructor() {
		this.ear = new App.Entity.piercingStateRA();
		this.nose = new App.Entity.piercingStateRA();
		this.eyebrow = new App.Entity.piercingStateRA();
		this.lips = new App.Entity.piercingStateRA();
		this.tongue = new App.Entity.piercingStateRA();
		this.nipple = new App.Entity.piercingStateRA();
		this.areola = new App.Entity.piercingStateRA();
		this.navel = new App.Entity.piercingStateRA();
		this.corset = new App.Entity.piercingStateRA();
		this.genitals = new App.Entity.genitalPiercingStateRA();
		this.vagina = new App.Entity.piercingStateRA();
		this.dick = new App.Entity.piercingStateRA();
		this.anus = new App.Entity.piercingStateRA();
	}
};

/**
 * To ensure that all new limbs contain expected attributes
 */
App.Entity.LimbState = class LimbState {
	constructor() {
		/**
		 * type of limb
		 * * 1: normal
		 * * 2: simple prosthetic
		 * * 3: advanced - Sex
		 * * 4: advanced - Beauty
		 * * 5: advanced - Combat
		 * * 6: cybernetic
		 * @type {FC.LimbType}
		 */
		this.type = 1;
		/**
		 * Partial ScarState:
		 * The body part in question, such as back or left hand.
		 * The key of that part is the type of scar they can have and the value is how serious it is, from 0 up.
		 * @type {{[key: string]: Partial<App.Entity.ScarState>}} */
		this.scar = {};
		/**
		 * Key: body part
		 * Value: Brand description
		 * @type {{[key: string]: string}} */
		this.brand = {};
	}
};

/**
 * To ensure that all new arms contain expected attributes
 */
App.Entity.ArmState = class extends App.Entity.LimbState {
	constructor() {
		super();
	}
};

/**
 * To ensure that all new legs contain expected attributes
 */
App.Entity.LegState = class extends App.Entity.LimbState {
	constructor() {
		super();
	}
};

/**
 * To ensure that all new eyes contain expected attributes
 */
App.Entity.SingleEyeState = class SingleEyeState {
	constructor() {
		/**
		 * type of eye
		 * * 1: normal
		 * * 2: glass
		 * * 3: cybernetic
		 */
		this.type = 1;
		/**
		 * vision of eye
		 * * 0: blind
		 * * 1: impaired
		 * * 2: normal
		 */
		this.vision = 2;
		/**
		 * current eye color
		 */
		this.iris = "brown";
		/**
		 * pupil shape
		 */
		this.pupil = "circular";
		/**
		 * sclera color
		 */
		this.sclera = "white";
	}
};

App.Entity.EyeState = class EyeState {
	constructor() {
		this.left = new App.Entity.SingleEyeState();
		this.right = new App.Entity.SingleEyeState();
		this.origColor = "brown";
	}
};

/** Genetic "natural targets" for this individual when full grown, without influence from drugs, surgery, etc */
App.Entity.GeneticState = class GeneticState {
	/**
	 * @param {number|string} [seed=undefined]
	 */
	constructor(seed=undefined) {
		/** adult, natural height; expected height when full-grown/no drugs/no surgery
		 * @type {number} */
		this.height = 170;
		this.boobs = 500;
		this.artSeed = jsRandom(0, 10 ** 14, undefined, seed);
	}
};

/**
 * @typedef {object} App.Entity.enslavePCOptions
 * @property {boolean} [removeKeys=true] If true then we remove any properties that are not found in App.Entity.SlaveState
 * @property {boolean} [setSkills=true] If true then we set the new slaves skills based off the PC's data
 * @property {string} [badEnding="boring"] The type of ending that cause the enslavement. Modifies things. Pass "none" for no extra modification
 * @see App.Entity.SlaveState
 * @see App.Entity.HumanState.enslavePC
 */

/**
 * This defines properties that are shared between slaves and the PC.
 * The values here should be the default for slaves.
 * Any properties that need to be different for the PC should be overwritten in App.Entity.PlayerState.
 * @see App.Entity.PlayerState
 * @see App.Entity.SlaveState
 */
// TODO:@franklygeorge typing hints and documentation for all of these properties
App.Entity.HumanState = class HumanState extends App.Entity.GenePoolRecord {
	/**
	 * @param {number|string} [seed=undefined]
	 */
	constructor(seed=undefined) {
		// NOTE if the property you are adding has to do with the state of the human's body then it likely belongs in `GenePoolRecord` instead
		super(seed);
		/** Their prestige */
		this.prestige = 0;
		/** reason for their prestige
		 * @type {FC.Zeroable<string>} */
		this.prestigeDesc = 0;
		/** @type {string} Their origin */
		this.origin = "";
		/** @type {number} number of their children that are your in your stock */
		this.daughters = 0; // TODO:@franklygeorge: is this update/validated somewhere?; refactor to "children"
		/** @type {number} number of the their siblings that are your in your stock */
		this.sisters = 0; // TODO:@franklygeorge: is this update/validated somewhere?; refactor to "siblings"
		/** @type {number} how far along they are with being trained (skills, flaws, quirks, PC education, PC sparring) */
		this.training = 0;
		/**
		 * Number of ready to be impregnated ova (override normal cases),
		 *
		 * For delayed impregnations with multiples. Used onetime on next call of the SetPregType
		 * widget. After SetPregType use it to override .pregType, it set back to 0 automatically.
		 * @type {number | undefined}
		 */
		this.readyOva = undefined;
		/**
		 * They have a minor injury ("black eye", "bruise", "split lip")
		 * @type {FC.Zeroable<string>}
		 */
		this.minorInjury = 0;
		/** @type {FC.EyeWear} */
		this.eyewear = "none";
		/** @type {FC.EarWear} */
		this.earwear = "none";

		/**
		 * Used for the player, but it is here for function compatibility. (whatever that means)
		 * TODO:@franklygeorge find where this is being checked, make sure it is isPlayer() exclusive, then move this to PlayerState
		 * (uncommon in events)(V.PC.preg >= 28)
		 * how the player acts when heavily pregnant
		 * * 0 - no change
		 * * 1 - submissive and motherly
		 * * 2 - aggressive and dominant
		 */
		this.pregMood = 0;
		/**
		 * Menstrual cycle known variable. To be used for fert cycle discover and things like pregnancy without a first period
		 * @type {FC.Bool}
		 * * 0: no; 1: yes
		 */
		this.fertKnown = 0;
		/**
		 * Menstrual cycle control variable.
		 *
		 * * 0-: Danger week
		 * * 1: safe week
		 * * 2: menstruating - check with {@link isMenstruating}
		 * * 3: safe week
		 */
		this.fertPeak = 0;
		/**
		 * Used for late periods. Anything < 0 is late.
		 */
		this.fertLate = 0;
		/**
		 * variable used to set off the birth events
		 *
		 * * 1: birth this week
		 * * 0: not time yet
		 * @type {FC.Bool} */
		this.labor = 0;
		/**
		 * pregnancy time or state. See Pregnancy Control section for more.
		 * * -3: sterilized
		 * * -2: sterile
		 * * -1: contraceptives
		 * * 0: fertile
		 * * 1 - 10: pregnant, not showing
		 * * 11 - 20: showing
		 * * 21 - 30: pregnant
		 * * 30 - 35: very pregnant
		 */
		this.preg = -1;
		/**
		 * Who sired their pregnancy
		 *
		 * | ***ID***             | **Type**                          |
		 * |----------------------|-----------------------------------|
		 * | *SID.RAPIST*         | a rapist                          |
		 * | *SID.FUTA*           | a futanari sister                 |
		 * | *SID.ANIMAL*         | an animal                         |
		 * | *SID.LAB*            | designer baby                     |
		 * | *SID.ELITE*          | a member of the Societal Elite    |
		 * | *SID.CLIENT*         | one of your clients               |
		 * | *SID.ARCOWNER*       | another arcology owner            |
		 * | *SID.FORMERMASTER*   | your former Master                |
		 * | *SID.CITIZEN*        | citizen of your arcology          |
		 * | *SID.PC*             | the player                        |
		 * | *SID.GENERIC*        | unidentifiable                    |
		 * | *A positive number*  | a slave                           |
		 * @type {FC.SpecialID|FC.HumanID}
		 */
		this.pregSource = SID.GENERIC;
		/**
		 * Number of children.
		 *
		 * **Warning!** Should be not changed after initial impregnation setup.
		 * See Pregnancy Control section for more.
		 */
		this.pregType = 0;
		/**
		 * How adapted their body is to being pregnant. 1 pregAdaption supports 1000cc of pregnancy. A normal singleton pregnancy is about 15 pregAdaption.
		 */
		this.pregAdaptation = 50;
		/**
		 * This sets the default option for the pregnancy notice
		 * @type {"none"|"incubator"|"nursery"|"nothing"}
		 */
		this.pregNoticeDefault = "none";
		/**
		 * If true the end of week pregnancy notice will be bypassed
		 */
		this.pregNoticeBypass = false;
		/**
		 * has makeup
		 * * 0: none
		 * * 1: minimal
		 * * 2: expensive, luxurious
		 * * 3: color-coordinated with hair
		 * * 4: heavy
		 * * 5: neon
		 * * 6: color-coordinated neon
		 * * 7: metallic
		 * * 8: color-coordinated metallic
		 */
		this.makeup = 0;
		/**
		 * nail type
		 * * 0: neatly clipped
		 * * 1: long and elegant
		 * * 2: color-coordinated with hair
		 * * 3: sharp and claw-like
		 * * 4: bright and glittery
		 * * 5: very long and garish
		 * * 6: neon
		 * * 7: color-coordinated neon
		 * * 8: metallic
		 * * 9: color-coordinated metallic
		 */
		this.nails = 0;
		/**
		 * * "healthy"
		 * * "restricted"
		 * * "corrective"
		 * * "muscle building"
		 * * "fattening"
		 * * "slimming"
		 * * "XX"
		 * * "XY"
		 * * "XXY"
		 * * "cum production"
		 * * "cleansing"
		 * * "fertility"
		 * * "high caloric" (planned)
		 * @type {FC.Diet}
		 */
		this.diet = "healthy";
		/**
		 * * -2: heavy male hormones
		 * * -1: male hormones
		 * * 0: none
		 * * 1: female hormones
		 * * 2: heavy female hormones
		 */
		this.hormones = 0;
		/**
		 * * 0: none
		 * * 1: preventatives
		 * * 2: curatives
		 */
		this.curatives = 0;

		/**
		 * * 0: none
		 * * 1: standard
		 * * 2: powerful
		 */
		this.aphrodisiacs = 0;

		/**
		 * may accept strings, use at own risk
		 *
		 * * "a ball gown"
		 * * "a biyelgee costume"
		 * * "a bra"
		 * * "a bunny outfit"
		 * * "a burkini"
		 * * "a burqa"
		 * * "a button-up shirt"
		 * * "a button-up shirt and panties"
		 * * "a chattel habit"
		 * * "a cheerleader outfit"
		 * * "a comfortable bodysuit"
		 * * "a tight Imperial bodysuit"
		 * * "a cybersuit"
		 * * "a dirndl"
		 * * "a fallen nuns habit"
		 * * "a Fuckdoll suit"
		 * * "a gothic lolita dress"
		 * * "a halter top dress"
		 * * "a hanbok"
		 * * "a hijab and abaya"
		 * * "a hijab and blouse"
		 * * "a huipil"
		 * * "a kimono"
		 * * "a klan robe"
		 * * "a latex catsuit"
		 * * "a leotard"
		 * * "a long qipao"
		 * * "a maternity dress"
		 * * "a military uniform"
		 * * "a mini dress"
		 * * "a monokini"
		 * * "a mounty outfit"
		 * * "a nice maid outfit"
		 * * "a nice nurse outfit"
		 * * "a nice pony outfit"
		 * * "a niqab and abaya"
		 * * "a one-piece swimsuit"
		 * * "a penitent nuns habit"
		 * * "a police uniform"
		 * * "a red army uniform"
		 * * "a Santa dress"
		 * * "a scalemail bikini"
		 * * "a schoolgirl outfit"
		 * * "a schutzstaffel uniform"
		 * * "a skimpy loincloth"
		 * * "a slave gown"
		 * * "a slutty klan robe"
		 * * "a slutty maid outfit"
		 * * "a slutty nurse outfit"
		 * * "a slutty outfit"
		 * * "a slutty pony outfit"
		 * * "a slutty qipao"
		 * * "a slutty schutzstaffel uniform"
		 * * "a sports bra"
		 * * "a string bikini"
		 * * "a striped bra"
		 * * "a succubus outfit"
		 * * "a sweater"
		 * * "a sweater and cutoffs"
		 * * "a sweater and panties"
		 * * "a t-shirt"
		 * * "a t-shirt and jeans"
		 * * "a t-shirt and panties"
		 * * "a t-shirt and thong"
		 * * "a tank-top"
		 * * "a tank-top and panties"
		 * * "a thong"
		 * * "a toga"
		 * * "a tube top"
		 * * "a tube top and thong"
		 * * "a confederate army uniform"
		 * * "an apron"
		 * * "an oversized t-shirt"
		 * * "an oversized t-shirt and boyshorts"
		 * * "an evening dress"
		 * * "attractive lingerie"
		 * * "attractive lingerie for a pregnant woman"
		 * * "battlearmor"
		 * * "Imperial Plate"
		 * * "battledress"
		 * * "body oil"
		 * * "boyshorts"
		 * * "chains"
		 * * "clubslut netting"
		 * * "conservative clothing"
		 * * "cutoffs"
		 * * "cutoffs and a t-shirt"
		 * * "harem gauze"
		 * * "jeans"
		 * * "kitty lingerie"
		 * * "leather pants"
		 * * "leather pants and a tube top"
		 * * "leather pants and pasties"
		 * * "lederhosen"
		 * * "nice business attire"
		 * * "no clothing"
		 * * "overalls"
		 * * "panties"
		 * * "panties and pasties"
		 * * "restrictive latex"
		 * * "shibari ropes"
		 * * "slutty business attire"
		 * * "slutty jewelry"
		 * * "spats and a tank top"
		 * * "sport shorts"
		 * * "sport shorts and a sports bra"
		 * * "sport shorts and a t-shirt"
		 * * "stretch pants and a crop-top"
		 * * "striped panties"
		 * * "striped underwear"
		 * * "uncomfortable straps"
		 * * "Western clothing"
		 * @type {FC.Clothes} */
		this.clothes = "no clothing";
		/**
		 * may accept strings, use at own risk
		 * * "none"
		 * * "ancient Egyptian"
		 * * "cruel retirement counter"
		 * * "uncomfortable leather"
		 * * "tight steel"
		 * * "shock punishment"
		 * * "heavy gold"
		 * * "pretty jewelry"
		 * * "nice retirement counter"
		 * * "bell collar"
		 * * "leather with cowbell"
		 * * "bowtie"
		 * * "neck tie"
		 * * "neck corset"
		 * * "stylish leather"
		 * * "satin choker"
		 * * "preg biometrics"
		 * * "silk ribbon"
		 * @type {FC.Collar}
		 */
		this.collar = "none";
		/**
		 * may accept strings, use at own risk
		 * @type {FC.WithNone<FC.Shoes>}
		 */
		this.shoes = "none";
		/**
		 * may accept strings, use at own risk
		 * * "none"
		 * * "porcelain mask"
		 */
		this.faceAccessory = "none";
		/**
		 * may accept strings, use at own risk
		 * @type {FC.WithNone<FC.MouthAccessory>}
		 */
		this.mouthAccessory = "none";
		/** what accessory, if any, are on their nipples */
		this.nipplesAccessory = "none";
		/**
		 * may accept strings, use at own risk
		 *
		 * * "none"
		 * * "a small empathy belly"
		 * * "a medium empathy belly"
		 * * "a large empathy belly"
		 * * "a huge empathy belly"
		 * * "a corset"
		 * * "an extreme corset"
		 * * "a support band"
		 * @type {FC.BellyAccessory}
		 */
		this.bellyAccessory = "none";
		/**
		 * may accept strings, use at own risk
		 * * "none"
		 * * "bullet vibrator"
		 * * "smart bullet vibrator"
		 * * "dildo"
		 * * "large dildo"
		 * * "huge dildo"
		 * * "long dildo"
		 * * "long, large dildo"
		 * * "long, huge dildo"
		 */
		this.vaginalAccessory = "none";
		/**
		 * may accept strings, use at own risk
		 * * "none"
		 * * "vibrator"
		 * * "smart vibrator"
		 */
		this.vaginalAttachment = "none";
		/**
		 * may accept strings, use at own risk
		 * * "none"
		 * * "sock"
		 * * "bullet vibrator"
		 * * "smart bullet vibrator"
		 */
		this.dickAccessory = "none";
		/**
		 * whether the they have a chastity device on their anus
		 * 0 - no
		 * 1 - yes
		 * @type {FC.Bool}
		 */
		this.chastityAnus = 0;
		/**
		 * whether the they have a chastity device on their penis
		 * 0 - no
		 * 1 - yes
		 * @type {FC.Bool}
		 */
		this.chastityPenis = 0;
		/**
		 * whether the they have a chastity device on their vagina
		 * 0 - no
		 * 1 - yes
		 * @type {FC.Bool}
		 */
		this.chastityVagina = 0;
		/**
		 * may accept strings, use at own risk
		 * * "none"
		 * * "hand gloves"
		 * * "elbow gloves"
		 */
		this.armAccessory = "none";
		/**
		 * may accept strings, use at own risk
		 * * "none"
		 * * "short stockings"
		 * * "long stockings"
		 */
		this.legAccessory = "none";
		/**
		 * may accept strings, use at own risk
		 * * "none"
		 * * "plug"
		 * * "large plug"
		 * * "huge plug"
		 * * "long plug"
		 * * "long, large plug"
		 * * "long, huge plug"
		 */
		this.buttplug = "none";
		/**
		 * Do they have an attachment on their buttplug
		 *
		 * may accept strings, use at own risk
		 * * "none"
		 * * "tail"
		 * * "fox tail"
		 * * "cat tail"
		 * * "cow tail"
		 */
		this.buttplugAttachment = "none";

		/**
		 * sex drive
		 * * 0 - 20: no sex drive
		 * * 21 - 40: poor sex drive
		 * * 41 - 60: average sex drive
		 * * 61 - 80: good sex drive
		 * * 81 - 95: powerful sex drive
		 * * 96+: nymphomaniac
		 */
		this.energy = 50;
		/**
		 * how badly they need sex.
		 * 0: sated
		 */
		this.need = 0;

		/** @type {FC.PCDrug} */
	   this.drugs = "no drugs";
		/**
		 *
		 * A list of IDs of anyone they have ever slept with
		 *
		 * Only contains unique entries
		 *
		 * | ***ID***             | **Type**                          |
		 * |----------------------|-----------------------------------|
		 * | *SID.RAPIST*         | a rapist                          |
		 * | *SID.FUTA*           | a futanari sister                 |
		 * | *SID.ANIMAL*         | an animal                         |
		 * | *SID.LAB*            | designer baby                     |
		 * | *SID.ELITE*          | a member of the Societal Elite    |
		 * | *SID.CLIENT*         | one of your clients               |
		 * | *SID.ARCOWNER*       | another arcology owner            |
		 * | *SID.FORMERMASTER*   | your former Master                |
		 * | *SID.CITIZEN*        | citizen of your arcology          |
		 * | *SID.PC*             | the player                        |
		 * | *SID.GENERIC*        | unidentifiable                    |
		 * | *A positive number*  | a slave                           |
		 * @type {Set<FC.SpecialID|FC.HumanID>}
		 */
		this.partners = new Set();

		/**
		 * attraction to women
		 * * 0 - 5: disgusted by women
		 * * 6 - 15: turned off by women
		 * * 15 - 35: not attracted to women
		 * * 36 - 65: indifferent to women
		 * * 66 - 85: attracted to women
		 * * 86 - 95: aroused by women
		 * * 96+: passionate about women
		 *
		 * *if both attrXX and attrXY > 95, slave will be omnisexual*
		 *
		 * *if energy > 95 and either attrXX or attrXY > 95, slave will be nymphomaniac*
		 */
		this.attrXX = 0;
		/**
		 * attraction to men
		 * * 0 - 5: disgusted by men
		 * * 6 - 15: turned off by men
		 * * 15 - 35: not attracted to men
		 * * 36 - 65: indifferent to men
		 * * 66 - 85: attracted to men
		 * * 86 - 95: aroused by men
		 * * 96+: passionate about men
		 *
		 * *if both attrXX and attrXY > 95, slave will be omnisexual*
		 *
		 * *if energy > 95 and either attrXX or attrXY > 95, slave will be nymphomaniac*
		 */
		this.attrXY = 0;
		/** 0: no; 1: yes
		 * @type {FC.Bool} */
		this.attrKnown = 0;
		/**
		 * * "none"
		 * * "mindbroken"
		 * * "submissive"
		 * * "cumslut"
		 * * "humiliation"
		 * * "buttslut"
		 * * "boobs"
		 * * "sadist"
		 * * "masochist"
		 * * "dom"
		 * * "pregnancy"
		 * * "bestiality"
		 * @type {FC.Fetish}
		 */
		this.fetish = "none";
		/** how strong their fetish is (10-100)
		 *
		 * * 10+: enjoys fetish
		 * * 60+: likes fetish
		 * * 95+: loves fetish
		 */
		this.fetishStrength = 70;
		/** is fetish known to player
		 * @type {FC.Bool}
		 * 0: no; 1: yes */
		this.fetishKnown = 0;
		/**
		 * * -1 - no preference
		 * * 0 - anus
		 * * 1 - vagina
		 * @see getPCPreferredHole() for how no preference is handled
		 * @type {-1|0|1}
		 */
		this.preferredHole = -1;

		/** Counts various acts they have participated in */
		this.counter = new App.Entity.HumanActionCountersState();
		/** Their skills */
		this.skill = new App.Entity.HumanSkillsState();
		/** Their rule set */
		this.rules = new App.Entity.RuleState();
		/** Values provided by players */
		this.custom = new App.Entity.CustomAddonsState();

		/** They will give birth this week.
		 * @type {FC.Bool}
		 * 1: true; 0: false */
		this.induce = 0;
		/** They have an anal womb and can get pregnant anally.
		 * @type {FC.Bool}
		 * 1: true; 0: false */
		this.mpreg = 0;
		/** How much fluid is distending them.
		 *
		 * 1: 2L; 2: 4L; 3: 8L */
		this.inflation = 0;
		/**
		 * What kind of fluid is in them.
		 * * "none"
		 * * "water"
		 * * "cum"
		 * * "milk"
		 * * "food"
		 * * "aphrodisiac"
		 * * "curative"
		 * * "tightener"
		 * * "urine"
		 * @type {FC.InflationLiquid}
		 */
		this.inflationType = "none";
		/**
		 * How they are being filled.
		 * * 0: not
		 * * 1: oral
		 * * 2: anal
		 * * 3: orally by another human
		 */
		this.inflationMethod = 0;
		/** If inflationMethod === 3, ID of the human filling them with milk. */
		this.milkSource = 0;
		/** If inflationMethod 3, ID of the human filling them with cum. */
		this.cumSource = 0;
		/** Their internals have ruptured. Used with poor health and overinflation.
		 * @type {FC.Bool}
		 * 1: true; 0: false */
		this.burst = 0;
		/** Do you and them know that they are pregnant.
		 * @type {FC.Bool}
		 * 0: no; 1: yes */
		this.pregKnown = 0;
		/** How long they have been pregnant
		 *
		 * used in place of .preg when pregnancy speed up and slow down are used on a human
		 *
		 * if negative, designates postpartum. */
		this.pregWeek = 0;
		/**
		 * how big their belly is in CCs
		 *
		 * ||thresholds:|
		 * |-|-|
		 * 100	| bloated
		 * 1500   | early pregnancy
		 * 5000   | obviously pregnant
		 * 10000  | very pregnant
		 * 15000  | full term
		 * 30000  | full term twins
		 * 45000  | full term triplets
		 * 60000  | full term quads
		 * 75000  | full term quints
		 * 90000  | full term sextuplets
		 * 105000 | full term septuplets
		 * 120000 | full term octuplets
		 * 150000 | oversized pregnancy
		 * 300000 | hyperpreg state 1
		 * 450000 | hyperpreg state 2
		 * 600000 | hyperpreg state 3
		 * 750000 | hyperpreg state 4
		 */
		this.belly = 0;
		/**
		 * how big their belly is in CCs (pregnancy only)
		 *
		 * ||thresholds|
		 * |-|-|
		 * 100	| bloated
		 * 1500   | early pregnancy
		 * 5000   | obviously pregnant
		 * 10000  | very pregnant
		 * 15000  | full term
		 * 30000  | full term twins
		 * 45000  | full term triplets
		 * 60000  | full term quads
		 * 75000  | full term quints
		 * 90000  | full term sextuplets
		 * 105000 | full term septuplets
		 * 120000 | full term octuplets
		 * 150000 | oversized pregnancy (9+ babies)
		 * 300000 | hyperpreg state 1 (20+ babies)
		 * 450000 | hyperpreg state 2 (30+ babies)
		 * 600000 | hyperpreg state 3 (40+ babies)
		 * 750000 | hyperpreg state 4 (50+ babies)
		 */
		this.bellyPreg = 0;
		/**
		 * how big their belly is in CCs (fluid distension only)
		 *
		 * ||thresholds|
		 * |-|-|
		 * 100   | bloated
		 * 2000  | clearly bloated (2 L)
		 * 5000  | very full (~1 gal)
		 * 10000 | full to bursting (~2 gal)
		 */
		this.bellyFluid = 0;
		/**
		 * Do they have a fillable abdominal implant.
		 * * -1: no
		 * * 0+: yes
		 * * 2000+: Early pregnancy
		 * * 4000+: looks pregnant
		 * * 8000+: looks full term
		 * * 16000+: hyperpregnant 1
		 * * 32000+: hyperpregnant 2
		 */
		this.bellyImplant = -1;
		/** How saggy their belly is after being distended for too long.
		 *
		 * 1+ changes belly description */
		this.bellySag = 0;
		/** How saggy their belly is from being too pregnant.
		 *
		 * 1+ changes belly description and overrides/coincides with bellySag */
		this.bellySagPreg = 0;
		/**
		 * Has their belly implant been filled this week. Causes health damage for overfilling.
		 *
		 * * 0: no pain
		 * * 1: will experience pain
		 * * 2: cannot be filled this week
		 */
		this.bellyPain = 0;
		/** Do they have a cervical implant that slowly feeds cum from being fucked into a fillable implant.
		 *
		 * * 0: no
		 * * 1: vaginal version only
		 * * 2: anal version only
		 * * 3: both vaginal and anal
		 */
		this.cervixImplant = 0;
		/** Can that fluid be pumped throughout her body?
		 * @type {FC.Bool}
		 * 0: no; 1: yes */
		this.cervixImplantTubing = 0;
		/** Where the fluid being routed. */
		this.cervixImplantTarget = "bellyImplant";
		/**
		 * In a eugenics society, are they a designated breeder.
		 * @type {FC.Bool}
		 * * 1: yes
		 * * 0: no */
		this.breedingMark = 0;
		/**
		 * Are they on gestation altering drugs?
		 * * "none"
		 * * "slow gestation"
		 * * "speed up"
		 * * "labor suppressors"
		 * @type {FC.WithNone<FC.GestationDrug>}
		 */
		this.pregControl = "none";
		this.ageAdjust = 0;
		/** have they undergone hair removal surgery
		 * @type {FC.Bool}
		 * 0: no; 1: yes */
		this.bald = 0;
		/** Are they in their original body.
		 *
		 * * 0: yes
		 * * 1+: number of swaps (increases upkeep each time)
		 */
		this.bodySwap = 0;
		/** Who, if relevant, their current body belonged to originally. */
		this.origBodyOwner = "";
		/** Who, if relevant, their original body currently belongs to (i.e. the exact opposite of the variable above). */
		this.origBodyOwnerID = 0;
		/** Their lactation
		 *
		 * 0: none; 1: natural; 2: implant
		 * @type {FC.LactationType} */
		this.lactation = 0;
		/** how many more weeks until lactation dries up
		 *
		 * usually 2 as interactions and lactation implant reset it to 2 */
		this.lactationDuration = 0;
		/**
		 * odds of inducing lactation
		 *
		 * begins trying on breast play if over 10 */
		this.induceLactation = 0;
		/** 0: 10: not used to producing milk(no bonuses);
		 * 11: 50: used to producing milk;
		 * 51: 100: heavily adapted to producing milk(big bonus)
		 * 101: 200: devoted to producing milk(bigger bonus) */
		this.lactationAdaptation = 0;
		/** breast engorgement from unmilked tits */
		this.boobsMilk = 0;
		/** Do they have the breast shape maintaining mesh implant.
		 * @type {FC.Bool}
		 * 0: no; 1: yes */
		this.breastMesh = 0;
		/** Used to denote that they are giving birth prematurely.
		 * @type {FC.Bool}
		 * 0: no; 1: yes */
		this.prematureBirth = 0;

		this.NCSyouthening = 0;

		/** Are their hair under constant maintenance?
		 * @type {FC.Bool}
		 * 0: no; 1: yes */
		this.haircuts = 0;
		/** Whether they were put in the incubator at birth
		 *
		 * * 0: no
		 * * 1: yes, comforting
		 * * 2: yes, terrifying
		 * * 3: yes, husk/body swap
		 * @type {0|1|2|3}
		 */
		this.tankBaby = 0;
		/** erratic weight gain
		 *
		 * 0: stable; 1: gaining; -1: losing */
		this.weightDirection = 0;

		/** @type {FC.PregnancyData | undefined} */
		this.pregData = undefined;
	}

	/**
	 * Will add all properties that are missing compared to PlayerState to the object.
	 * All properties that don't exist on PlayerState will be lost.
	 * This is costly because we do a deep clone op.
	 * A new object is returned. The original is left untouched.
	 * @param {FC.HumanState} slaveOrPC
	 * @returns {FC.PlayerState}
	 */
	static ConvertToPlayerState(slaveOrPC) {
		const actor = new App.Entity.PlayerState();
		deepAssign(actor, slaveOrPC); // deep clone and merge
		const base = new App.Entity.PlayerState();
		for (const key in actor) {
			if (!(key in base)) {
				delete actor[key];
			}
		}
		actor.counter = App.Entity.HumanActionCountersState.convertToPlayerActionCountersState(actor.counter);
		const newSkills = new App.Entity.PlayerSkillsState();
		for (const key in newSkills) {
			if (key in actor.skill) {
				newSkills[key] = actor.skill[key];
			}
		}
		actor.skill = newSkills;
		return actor;
	}

	/**
	 * Will add all properties that are missing compared to SlaveState to the object.
	 * All properties that don't exist on SlaveState will be lost.
	 * This is costly because we do a deep clone op.
	 * A new object is returned. The original is left untouched.
	 * @param {FC.HumanState} PCorSlave
	 * @returns {FC.SlaveState}
	 */
	static ConvertToSlaveState(PCorSlave) {
		const actor = new App.Entity.SlaveState();
		deepAssign(actor, PCorSlave); // deep clone and merge
		const base = new App.Entity.SlaveState();
		for (const key in actor) {
			if (!(key in base)) {
				delete actor[key];
			}
		}
		actor.counter = App.Entity.HumanActionCountersState.convertToSlaveActionCountersState(actor.counter);
		const newSkills = new App.Entity.SlaveSkillsState();
		for (const key in newSkills) {
			if (key in actor.skill) {
				newSkills[key] = actor.skill[key];
			}
		}
		actor.skill = newSkills;
		return actor;
	}

	/**
	 * Converts the PC from a `PlayerState` object to a `SlaveState` object
	 * @param {App.Entity.enslavePCOptions} [options]
	 * @returns {FC.SlaveState}
	 */
	static enslavePC(options) {
		options = sanitizeOptions(options, {
			removeKeys: true,
			setSkills: true,
			badEnding: "boring",
		});
		const slave = new App.Entity.SlaveState();
		deepAssign(slave, V.PC); // deep clone and merge
		if (options.removeKeys) {
			const base = new App.Entity.SlaveState();
			for (const key in slave) {
				if (!(key in base)) {
					delete slave[key];
				}
			}
		}
		if (options.setSkills) {
			const newSkills = new App.Entity.SlaveSkillsState();
			for (const key in slave.skill) {
				if (key in newSkills) {
					let value = 100;
					if (key === "vaginal") {
						value = slave.vagina > 0 ? 100 : 0;
					} else if ([
						"bodyguard", "DJ", "teacher", "attendant", "matron",
						"milkmaid", "farmer", "servant"
					].includes(key)) {
						value = 0;
					} else if (["headGirl", "recruiter", "wardeness"].includes(key)) {
						value = 200;
					} else if (key === "nurse") {
						value = (slave.career === "medicine") ? 200 : 0;
					} else if (key === "whore" || key === "whoring") {
						value = (slave.career === "escort") ? 100 : 0;
					} else if (key === "stewardess") {
						value = (slave.career === "servant" ? 200 : 0);
					}
					newSkills[key] = value;
				}
			}
			slave.skill = newSkills;
		}
		slave.career = "an arcology owner";
		slave.origin = "A former arcology owner that made some poor decisions in $his life.";
		const newCounter = App.Entity.HumanActionCountersState.convertToSlaveActionCountersState(slave.counter);
		newCounter.births = 0;
		newCounter.PCChildrenFathered = 0;
		newCounter.PCKnockedUp = 0;
		newCounter.slavesFathered = 0;
		newCounter.slavesKnockedUp = 0;
		newCounter.timesBred = 0;
		newCounter.PCChildrenBeared = 0;
		newCounter.events = 0;
		slave.counter = newCounter;
		slave.rules = new App.Entity.RuleState();
		const newCustom = new App.Entity.CustomAddonsState();
		newCustom.aiPose = slave.custom.aiPose;
		newCustom.aiPrompts = slave.custom.aiPrompts;
		newCustom.hairVector = slave.custom.hairVector;
		newCustom.image = slave.custom.image;
		slave.custom = newCustom;
		slave.father = 0;
		slave.mother = 0;
		slave.daughters = 0;
		slave.sisters = 0;
		slave.training = 0;
		slave.devotion = -100;
		slave.oldDevotion = -100;
		slave.useRulesAssistant = 1;
		slave.clitSetting = "vanilla";
		slave.clothes = "no clothing";
		slave.attrKnown = 0;
		slave.fetishKnown = 0;
		slave.readyProsthetics = [];
		slave.haircuts = 0;
		slave.slaveCost = 0;

		slave.ID = generateSlaveID();

		switch (options.badEnding) {
			case "notSupreme":
				// @ts-ignore while boobsTat does take custom strings we have intentionally omitted that from its type to allow for correct auto completes, causing this to throw a TSC error - franklygeorge
				slave.boobsTat = `'Subhuman ${capFirstChar(slave.race)}' is printed across $his chest.`;
				break;
			case "subjugated":
				// @ts-ignore while stampTat does take custom strings we have intentionally omitted that from its type to allow for correct auto completes, causing this to throw a TSC error - franklygeorge
				slave.stampTat = `'${capFirstChar(slave.race)} Fuckmeat' is printed above $his butt.`;
				break;
			case "none":
				break;
			default:
				// @ts-ignore while buttTat does take custom strings we have intentionally omitted that from its type to allow for correct auto completes, causing this to throw a TSC error - franklygeorge
				slave.buttTat = `'Product of ${V.arcologies[0].name}.' is stamped on $his left buttock.`;
		}
		return slave;
	}
};


code:
/** @file This holds data structures that are unique to `App.Entity.PlayerState` */

/**
 * Keeps track of the many relationships the PC has
 */
App.Entity.PlayerRelationshipsState = class PlayerRelationshipsState {
	// in the future this will be used to determine who will be used to sate player lust
	constructor() {
		/** player's wives */
		this.marriage = [];
		/** player's lovers */
		this.lovers = [];
		/** player's friends with benefits */
		this.FWBs = [];
		/** player's best friends */
		this.BFFs = [];
		/** player's friends */
		this.friends = [];
		/** slaves player likes */
		this.likes = [];
		/** slaves player dislikes */
		this.dislikes = [];
		/** slaves player hates */
		this.hates = [];
		/** slaves player loathes */
		this.loathes = [];
		/**
		 * player's emotional obsession
		 * * -2: emotionally bound to you
		 * * -1: emotional slut
		 * * 0: none
		 * * (ID): target of obsession
		 */
		this.obsession = 0;
	}
};

/**
 * Keeps track of lots of stats (births, sexual interactions, etc)
 * Has extra stats that only relate to the PlayerState
 * @see App.Entity.PlayerState
 */
App.Entity.PlayerActionCountersState = class PlayerActionCountersState extends App.Entity.HumanActionCountersState {
	constructor() {
		super();
		/** how many children you've carried for the SE */
		this.birthElite = 0;
		/** how many children you've carried for your former master (servant start only) */
		this.birthMaster = 0;
		/** how many slave babies you've had */
		this.birthDegenerate = 0;
		/** how many whoring babies you've had */
		this.birthClient = 0;
		/** how many children you've carried for other arc owners */
		this.birthArcOwner = 0;
		/** how many children you've had by sex with citizens (not whoring) */
		this.birthCitizen = 0;
		/** how many children you've had with the Sisters */
		this.birthFutaSis = 0;
		/** how many times you've giving birth to your own selfcest babies */
		this.birthSelf = 0;
		/** how many designer babies you've produced */
		this.birthLab = 0;
		/** hoy many children you've had fruit of unknown rapists */
		this.birthRape = 0;
		/** untracked births */
		this.birthOther = 0;
		/** how many units of your cum are stored away for artificially inseminating slaves */
		this.storedCum = 0;
		/** how many times you've been raped of forced to sex */
		this.raped = 0;
		/** content for pInsemination */
		this.moves = 0;
		this.quick = 0;
		this.crazy = 0;
		this.virgin = 0;
		this.futa = 0;
		this.preggo = 0;
	}
};

/**
 * Defines how skilled they are at a given subject
 * Has extra subjects that only relate to the PlayerState
 * @see App.Entity.PlayerState
 */
App.Entity.PlayerSkillsState = class PlayerSkillsState extends App.Entity.HumanSkillsState {
	constructor() {
		super();
		/** PC's skill in trading. */
		this.trading = 0;
		/** PC's skill in warfare. */
		this.warfare = 0;
		/** PC's skill in slaving. */
		this.slaving = 0;
		/** PC's skill in engineering. */
		this.engineering = 0;
		/** PC's skill in medicine. */
		this.medicine = 0;
		/** PC's skill in hacking. */
		this.hacking = 0;
		/** PC's skill in combat. */
		this.combat = 0;
		/** PC's expected skill in arena fights */
		this.fighting = 0;
		/** PC's skill in taking huge loads. */
		this.cumTap = 0;
	}
};

/**
 * This defines properties that are unique to the PC.
 * It also changes the value of some inherited properties.
 * Properties shared between the PC and slaves should be defined in `App.Entity.HumanState`.
 * @see App.Entity.HumanState
 */
App.Entity.PlayerState = class extends App.Entity.HumanState {
	/**
	 * @param {number|string} [seed=undefined]
	 */
	constructor(seed=undefined) {
		// NOTE if the property you are adding could apply to more than just the player then it likely belongs in HumanState instead
		super(seed);
		/**
		 * @type {FC.PCDrug}
		 */
		this.drugs = "no drugs";
		// // // // // // properties unique to the player \\ \\ \\ \\ \\ \\

		/** your title's gender
		 *
		 * 0: female; 1: male */
		this.title = 1;
		/**
		 * How strong are the rumors about you doing things unbecoming of a respectable arcology owner
		 * * 0: no rumors
		 * * 1 - 10: occasional whispers
		 * * 11	- 25: minor rumors
		 * * 26	- 50: rumors
		 * * 51	- 75: bad rumors
		 * * 70	- 100: severe rumors
		 * * 101+: life ruining rumors
		 */
		this.badRumors = {
			/** Strength of the rumors about you being penetrated by slaves */
			penetrative: 0,
			/** Strength of whispers that you have given birth to miscegenated or slave's babies */
			birth: 0,
			/** Strength of the rumors that you are too soft on your rebellious slaves. */
			weakness: 0,
		};
		/** your favorite refreshment
		 * @type {string} */
		this.refreshment = "cigar";
		/**
		 * * The method of consumption of .refreshment
		 * * 0: smoked
		 * * 1: drunk
		 * * 2: eaten
		 * * 3: snorted
		 * * 4: injected
		 * * 5: popped
		 * * 6: orally dissolved
		 */
		this.refreshmentType = 0;
		/**
		 * * career prior to becoming owner
		 * * (22+)			(14+)					(10+)
		 * * "wealth"		("trust fund")			("rich kid")
		 * * "capitalist"	("entrepreneur")		("business kid")
		 * * "mercenary"	("recruit")				("child soldier")
		 * * "slaver"		("slave overseer")		("slave tender")
		 * * "engineer"		("construction")		("worksite helper")
		 * * "medicine" 	("medical assistant")	("nurse")
		 * * "celebrity"	("rising star")			("child star")
		 * * "escort"		("prostitute")			("child prostitute")
		 * * "servant"		("handmaiden")			("child servant")
		 * * "gang"			("hoodlum")				("street urchin")
		 * * "BlackHat"		("hacker")				("script kiddy")
		 * * "arcology owner"
		 */
		this.career = "capitalist";
		/**
		 * * how player became owner
		 * * "wealth"
		 * * "diligence"
		 * * "force"
		 * * "social engineering"
		 * * "luck"
		 */
		this.rumor = "wealth"; // TODO:@franklygeorge refactor into origin
		/** your ability to function normally in day to day affairs
		 *
		 * 0: normal, 1: hindered, 2: unable */
		this.physicalImpairment = 0;
		/** the players relationships */
		this.relationships = new App.Entity.PlayerRelationshipsState();
		/**
		 * you have taken a major injury
		 * number of weeks laid up in bed until recovery
		 */
		this.majorInjury = 0;
		/**
		 * you have a life-changing injury/malaise
		 * @type {number | string}
		 */
		this.criticalDamage = 0;
		/** have had your vagina improved
		 * @type {FC.Bool}
		 * 0: no; 1: yes; */
		this.newVag = 0;
		/**
		 * * "normal"
		 * * "atrophied"
		 */
		this.digestiveSystem = "normal";
		/** progress until .digestiveSystem is swapped to "normal". Completes at 20.*/
		this.weaningDuration = 0;
		/**
		 * Used to give feedback on energy changes
		 */
		this.oldEnergy = 0;
		/**
		 * Used in endWeek to store .need adjustments
		 */
		this.deferredNeed = 0;
		/**
		 * If sexual need is not met, apply punishment for following week.
		 *
		 * 1: overtly horny
		 * 0: sated
		 */
		this.lusty = 0;
		/** have you been drugged with fertility drugs
		 *
		 * 0: no; 1+: how many weeks they will remain in your system */
		this.forcedFertDrugs = 0;
		/** @type {string|undefined} */
		this.customTitle = undefined; // TODO:@franklygeorge refactor this to use this.custom.title
		/** @type {string|undefined} */
		this.customTitleLisp = undefined; // TODO:@franklygeorge refactor this to use this.custom.titleLisp

		// // // // // // modified HumanState properties \\ \\ \\ \\ \\ \\

		/** Player's current name */
		this.slaveName = "Anonymous";
		/** Player's original name */
		this.birthName = "Anonymous";
		/** Player sex ("XX", "XY")
		 * @type {FC.GenderGenes} */
		this.genes = "XY";
		this.pronoun = App.Data.Pronouns.Kind.male;
		/** Player's ID
		 * @type {-1} */
		this.ID = SID.PC;
		this.actualAge = 35;
		this.visualAge = 35;
		this.physicalAge = 35;
		this.ovaryAge = 35;
		this.readyOva = 0;
		this.health.condition = 60;
		this.muscles = 30;
		this.height = 185;
		this.natural.height = 185;
		this.nationality = "Stateless";
		/** @type {0|1|2|3} Their voice; 0: mute, 1: deep, 2: feminine, 3: high, girly */
		this.voice = 1;
		this.boobs = 200;
		this.natural.boobs = 200;
		/** @type {FC.BreastShape} */
		this.boobShape = "perky";
		this.butt = 2;
		this.face = 100;
		this.hColor = "blonde";
		this.origHColor = "blonde";
		this.hLength = 2;
		this.eyebrowHColor = "blonde";
		this.pubicHColor = "blonde";
		this.underArmHColor = "blonde";
		/**
		 * @type {number} vagina type
		 * * -1: no vagina
		 * * 0: virgin
		 * * 1: tight
		 * * 2: reasonably tight
		 * * 3: loose
		 * * 4: cavernous
		 * * 10: ruined
		 */
		this.vagina = -1;
		this.preg = 0;
		this.dick = 4;
		/** @type {FC.ProstateType} */
		this.prostate = 1;
		this.balls = 3;
		this.scrotum = 4;
		/** @type {FC.Clothes} */
		this.clothes = "nice business attire";
		this.pubicHStyle = "hairless";
		this.underArmHStyle = "hairless";
		this.intelligence = 100;
		this.intelligenceImplant = 30;
		this.energy = 65;
		this.devotion = 100; // used in some functions like `milkAmount()`, `cumAmount()`, and probably others
		/**
		 * A list of IDs of anyone the PC has ever slept with.
		 *
		 * Only contains unique entries.
		 *
		 * | ***ID*** | **Type**               |
		 * |---------:|:-----------------------|
		 * | *1+*     | Normal slave		   |
		 * | *-2*     | Citizen*               |
		 * | *-3*     | PC's former master*    |
		 * | *-4*     | Fellow arcology owner* |
		 * | *-6*     | Societal Elite*        |
		 * | *-8*     | Animal*                |
		 * | *-9*     | Futanari Sister*       |
		 * | *-10*    | Rapist*                |
		 *
		 * *TODO: *not currently implemented*
		 * @type {Set<number>}
		 */
		this.partners = new Set();
		this.attrXX = 100;
		this.attrXY = 100;
		this.attrKnown = 1;
		/** Counts various thing you have done */
		this.counter = new App.Entity.PlayerActionCountersState();
		/** Your skills */
		this.skill = new App.Entity.PlayerSkillsState();
		/** Your rule set */
		this.rules = new App.Entity.RuleState(true);
		/** Controls if femPC lost virginity before or after taking over */
		this.trueVirgin = 0;
	}
};

repo_name: Nithhogg/fc

license: mit


code:
/** @file This holds data structures that are unique to `App.Entity.SlaveState` */

/**
 * Returns all SlaveState objects;
 * Doesn't return GenePoolRecords from the gene pool;
 * Doesn't return MissingParentRecords from V.missingTable;
 * @param {boolean} [legacy=false]
 * @returns {FC.SlaveState[]}
 */
globalThis.getSlaveStates = (legacy=false) => {
	// TODO:@franklygeorge once ChildState is implemented add it to this
	let allSlaveRecords = []
		.concat(getSlaves(legacy).asArray())
		.concat(getInfants(legacy).asArray())
		.concat(getTankSlaves(legacy).asArray())
		.concat(getAllDetachedSlaveStates());

	return allSlaveRecords;
};

/**
 * Returns all SlaveStates for detached slaves, skips any detached that are set to 0
 * @returns {FC.SlaveState[]}
 */
globalThis.getAllDetachedSlaveStates = () => {
	const actors = [];
	if (V.detached === undefined) {
		// we have to have the legacy paths in here or BC may fail :(
		const hostage = getProp(V, "hostage");
		if (hostage) { actors.push(hostage); }
		const hostageWife = getProp(V, "hostageWife");
		if (hostageWife) { actors.push(hostageWife); }
		const boomerang = getProp(V, "boomerangSlave");
		if (boomerang) { actors.push(boomerang); }
		const shelterSlave = getProp(V, "shelterSlave");
		if (shelterSlave) { actors.push(shelterSlave); }
		const traitor = getProp(V, "traitor");
		if (traitor) { actors.push(traitor); }
	} else {
		if (V.detached.hostage.wife) { actors.push(V.detached.hostage.wife); }
		for (const data of Object.values(V.detached)) {
			if (data.actor !== 0) { actors.push(data.actor); }
		}
	}

	return actors;
};

/**
 * Encapsulates porn performance of a slave. Used inside of the
 * App.Entity.SlaveState class.
 * @see App.Entity.SlaveState
 */
App.Entity.SlavePornPerformanceState = class {
	constructor() {
		/** is the studio outputting porn of her?
		 * 0: no; 1: yes
		 * @type {FC.Bool} */
		this.feed = 0;
		/** how famous her porn is? */
		this.viewerCount = 0;
		/** how much money is being spent on promoting her porn */
		this.spending = 0;
		/**
		 * how famous she is in porn
		 * * 0: not
		 * * 1: some
		 * * 2: recognized
		 * * 3: world renowned
		 */
		this.prestige = 0;
		/** description to go with @see pornPrestige
		 * @type {FC.Zeroable<string>} */
		this.prestigeDesc = 0;

		/** what porn she is known for */
		this.fameType = "none";
		/** what aspect of her the upgraded studio is focusing on for porn */
		this.focus = "none";

		/** fame values for each porn genre */
		this.fame = {};
		for (const genre of App.Porn.getAllGenres()) {
			this.fame[genre.fameVar] = 0;
		}
	}
};

/**
 * Keeps track of lots of stats (births, sexual interactions, etc)
 * Has extra stats that only apply to SlaveState objects
 * @see App.Entity.SlaveState
 */
App.Entity.SlaveActionCountersState = class SlaveActionCountersState extends App.Entity.HumanActionCountersState {
	constructor() {
		super();
		/** number of births as your slave */
		this.births = 0;
		/** number of times used by the general public */
		this.publicUse = 0;
		/** number of slaves killed in pit fights*/
		this.pitKills = 0;
		/** number of bestiality encounters */
		this.bestiality = 0;
		/** How many children they have fucked into you that you later birthed. */
		this.PCChildrenFathered = 0;
		/** How many times they have knocked you up. */
		this.PCKnockedUp = 0;
		/** How many times you've knocked them up. */
		this.timesBred = 0;
		/** How many of your children has they borne. */
		this.PCChildrenBeared = 0;
		/** In how many random events has been actor */
		this.events = 0;
	}
};

/**
 * Defines how skilled they are at a given subject
 * Has subjects that only apply to SlaveState objects
 * @see App.Entity.SlaveState
 */
App.Entity.SlaveSkillsState = class SlaveSkillsState extends App.Entity.HumanSkillsState {
	constructor() {
		super();
		/**
		 * whoring skill
		 * * 0-10: unskilled
		 * * 11-30: basic
		 * * 31-60: skilled
		 * * 61-99: expert
		 * * 100+: master
		 */
		this.whoring = 0;
		/**
		 * entertaining skill
		 * * 0-10: unskilled
		 * * 11-30: basic
		 * * 31-60: skilled
		 * * 61-99: expert
		 * * 100+: master
		 */
		this.entertainment = 0;
		/**
		 * combating skill
		 * * 0-10: unskilled
		 * * 11-30: basic - Basic weapon handling, no tactics
		 * * 31-60: skilled - Good weapon handling, basic tactics
		 * * 61-99: expert - Expert weapon handling, good tactics
		 * * 100+: master - Master weapon handling, master tactics
		 *
		 * Notably, tactics lags behind weapon skill.
		 * Weapon skill includes hand-to-hand, melee and ranged.
		 *
		 * For reference:
		 * * Stick 'em with the pointy end: 0
		 * * Can shoot, hit and reload: 20
		 * * Well trained thug: 40
		 * * Trained soldier: 70
		 * * Special Ops: 100
		 */
		this.combat = 0;

		/** Their skill as a Head Girl
		 *
		 * default cap is 200 */
		this.headGirl = 0;
		/** Their skill as a recruiter
		 *
		 * default cap is 200 */
		this.recruiter = 0;
		/** Their skill as a bodyguard
		 *
		 * default cap is 200 */
		this.bodyguard = 0;
		/** Their skill as a brothel madam
		 *
		 * default cap is 200 */
		this.madam = 0;
		/** Their skill as a DJ
		 *
		 * default cap is 200 */
		this.DJ = 0;
		/** Their skill as a nurse
		 *
		 * default cap is 200 */
		this.nurse = 0;
		/** Their skill as a teacher
		 *
		 * default cap is 200 */
		this.teacher = 0;
		/** Their skill as an attendant
		 *
		 * default cap is 200 */
		this.attendant = 0;
		/** Their skill as a matron
		 *
		 * default cap is 200 */
		this.matron = 0;
		/** Their skill as a stewardess
		 *
		 * default cap is 200 */
		this.stewardess = 0;
		/** Their skill as a milkmaid
		 *
		 * default cap is 200 */
		this.milkmaid = 0;
		/** Their skill as a farmer
		 *
		 * default cap is 200 */
		this.farmer = 0;
		/** Their skill as a wardeness
		 *
		 * default cap is 200 */
		this.wardeness = 0;
		/** Their skill as a servant.
		 *
		 * default cap is 200 */
		this.servant = 0;
		/** Their skill as an entertainer
		 *
		 * default cap is 200 */
		this.entertainer = 0;
		/** Their skill as a whore
		 *
		 * default cap is 200 */
		this.whore = 0;
	}
};

/**
 * This defines properties that are unique to slaves.
 * Properties shared between the PC and slaves should be defined in `App.Entity.HumanState`.
 * @see App.Entity.HumanState
 */
App.Entity.SlaveState = class extends App.Entity.HumanState {
	/**
	 * @param {number|string} [seed=undefined]
	 */
	constructor(seed=undefined) {
		// NOTE if the property you are adding could apply to more than just slaves then it likely belongs in HumanState instead
		super(seed);
		/**
		 * * "no drugs"
		 * * "breast injections"
		 * * "butt injections"
		 * * "clitoris enhancement"
		 * * "lip injections"
		 * * "fertility drugs"
		 * * "penis enhancement"
		 * * "testicle enhancement"
		 * * "psychosuppressants"
		 * * "psychostimulants"
		 * * "steroids"
		 * * "hormone enhancers"
		 * * "hormone blockers"
		 * * "super fertility drugs"
		 * * "intensive clitoris enhancement"
		 * * "hyper breast injections"
		 * * "hyper butt injections"
		 * * "hyper penis enhancement"
		 * * "hyper testicle enhancement"
		 * * "female hormone injections"
		 * * "male hormone injections"
		 * * "anti-aging cream"
		 * * "appetite suppressors"
		 * * "penis atrophiers"
		 * * "testicle atrophiers"
		 * * "clitoris atrophiers"
		 * * "labia atrophiers"
		 * * "nipple atrophiers"
		 * * "lip atrophiers"
		 * * "breast redistributors"
		 * * "butt redistributors"
		 * * "sag-B-gone"
		 * * "growth stimulants"
		 * * "stimulants" (planned)
		 * @type {FC.Drug}
		 */
		this.drugs = "no drugs";
		/** game week slave was acquired.
		 *
		 * _0: Obtained prior to game start / at game start_ */
		this.weekAcquired = 0;
		/** porn fame */
		this.porn = new App.Entity.SlavePornPerformanceState();
		/** reason for prestige
		 * @type {FC.Zeroable<string>} */
		this.prestigeDesc = 0;
		/**
		 * slave's relationship
		 * * -3: married to you
		 * * -2: emotionally bound to you
		 * * -1: emotional slut
		 * * 0: none
		 * * 1: friends with relationshipTarget
		 * * 2: best friends with relationshipTarget
		 * * 3: friends with benefits with relationshipTarget
		 * * 4: lover with relationshipTarget
		 * * 5: relationshipTarget 's slave wife
		 * @type {FC.RelationShipKind}
		 */
		this.relationship = 0;
		/** @type {FC.HumanID} target of relationship (ID) */
		this.relationshipTarget = 0;
		/**
		 * slave's rivalry
		 * * 0: none
		 * * 1: dislikes rivalryTarget
		 * * 2: rival of rivalryTarget
		 * * 3: bitterly hates rivalryTarget
		 * @type {FC.RivalryType}
		 */
		this.rivalry = 0;
		/** target of rival (ID) */
		this.rivalryTarget = 0;
		/** slave will serve subTarget (ID - 0 is all slaves, -1 is stud) */
		this.subTarget = 0;
		/** Can the slave recruit relatives. Non-random slaves should be left off. */
		this.canRecruit = 0;
		/**
		 * can slave choose own assignment
		 * @type {FC.Bool}
		 * 0: no; 1: yes */
		this.choosesOwnAssignment = 0;
		/** slave's assignment
		 * @type {FC.Assignment} */
		this.assignment = Job.REST;
		/** how many weeks a slave is sentenced to work a job */
		this.sentence = 0;
		/** which hole to focus on when serving you
		 * @type {FC.ToyHole} */
		this.toyHole = "all her holes";
		/**
		 * How long her servitude will be.
		 *
		 * -1: not; 0+: number of weeks remaining */
		this.indenture = -1;
		/** 2: complete protection; 1: some protection; 0: no protection
		 * @type {FC.IndentureType} */
		this.indentureRestrictions = 0;
		/**
		 * slave 's trust.
		 * * -96-: abjectly terrified
		 * * -95 - -51: terrified
		 * * -50 - -21: frightened
		 * * -20 - 20: fearful
		 * * 21 - 50: careful
		 * * 51 - 95: trusting
		 * * 96+: profoundly trusting
		 */
		this.trust = 0;
		/** used to calculate trust loss/gain */
		this.oldTrust = 0;
		/** used to calculate devotion loss/gain */
		this.oldDevotion = 0;
		/**
		 * smart piercing setting
		 * @type {FC.SmartPiercingSetting}
		 */
		this.clitSetting = "vanilla";
		/** follows rules or is exempt from them
		 *
		 * 0: exempt; 1: obeys
		 * @type {FC.Bool} */
		this.useRulesAssistant = 1;
		/**
		 * * "healthy"
		 * * "restricted"
		 * * "corrective"
		 * * "muscle building"
		 * * "fattening"
		 * * "slimming"
		 * * "XX"
		 * * "XY"
		 * * "XXY"
		 * * "cum production"
		 * * "cleansing"
		 * * "fertility"
		 * * "high caloric" (planned)
		 * @type {FC.SlaveDiet}
		 */
		this.diet = "healthy";
		/**
		 * how much of her diet is cum
		 * 0: none; 1: supplemented; 2: nearly entirely
		 * @type {FC.dietCumType}*/
		this.dietCum = 0;
		/** how much of her diet is milk
		 *
		 * 0: none; 1: supplemented; 2: nearly entirely
		 * @type {FC.dietMilkType}
		 * */
		this.dietMilk = 0;
		/** Fuckdoll degree
		 *
		 * 0: not; 1+: Fuckdoll */
		this.fuckdoll = 0;
		/** 0: no; 1: yes
		 * @type {FC.Bool} */
		this.choosesOwnClothes = 0;
		/**
		 * The amount of sex the slave had with customers for certain jobs during a week
		 */
		this.sexAmount = 0;
		/**
		 * The 'quality' of the sex a slave had with customers. High quality means they fetch a higher price for their services
		 */
		this.sexQuality = 0;
		/** Does this slave refer to you rudely?
		 * @type {FC.Bool}
		 * 0: not being rude; 1: insists on calling you a rude title */
		this.rudeTitle = 0;
		/** @type {string[]} */
		this.currentRules = [];
		/** Is the PC permitted to fuck this slave pregnant.
		 *  MB Cattle Ranch bulls will ignore this.
		 * @type {FC.Bool}
		 * * 0: no
		 * * 1: yes */
		this.PCExclude = 0;
		/** Is the Head Girl permitted to fuck this slave pregnant.
		 * @type {FC.Bool}
		 * * 0: no
		 * * 1: yes */
		this.HGExclude = 0;
		/** Is the Stud permitted to fuck this slave pregnant.
		 *  MB Cattle Ranch bulls will ignore this.
		 * @type {FC.Bool}
		 * * 0: no
		 * * 1: yes */
		this.StudExclude = 0;
		/** Is this slave excluded from insemination roulette.
		 * @type {FC.Bool}
		 * * 0: no
		 * * 1: yes */
		this.inseminationExclude = 0;
		/** Eugenics variable. Is the slave allowed to choose to wear chastity.
		 * @type {FC.Bool}
		 * 0: no; 1: yes */
		this.choosesOwnChastity = 0;
		/**
		 * Array that holds a slaves fitted prosthetics. Objects are used to ensure easier expansion later (tattoos for limbs and similar).
		 *
		 * Elements of the array should be objects.
		 * * .id: ID of the prosthetic, see App.Data.prostheticIDs
		 * @type {Array.<{id:string}>} */
		this.readyProsthetics = [];
		/** Whether a slave is permitted to eat Hedonistic Decadence's specialized slave food.
		 * @type {FC.Bool}
		 * 0: no; 1: yes */
		this.onDiet = 0;
		/** Used to tell if the slave is from this game or a previous.
		 * @type {FC.Bool}
		 * 0: no; 1: yes */
		this.newGamePlus = 0;
		/** @type {FC.Bool} */
		this.overrideRace = 0;
		/** @type {FC.Bool} */
		this.overrideSkin = 0;
		/** @type {FC.Bool} */
		this.overrideEyeColor = 0;
		/** @type {FC.Bool} */
		this.overrideHColor = 0;
		/** @type {FC.Bool} */
		this.overridePubicHColor = 0;
		/** @type {FC.Bool} */
		this.overrideArmHColor = 0;
		/** @type {FC.Bool} */
		this.overrideBrowHColor = 0;
		/** Amount of cash paid to acquire the slave
		 *
		 * accepts negative numbers, 0, or 1.
		 * 1: unknown price; 0: free; negative: amount paid */
		this.slaveCost = 0;
		/** Amount of cash you have spent because of this slave
		 *
		 * accepts negative numbers or 0 */
		this.lifetimeCashExpenses = 0;
		/** Total amount of cash you have earned because of this slave
		 *
		 * accepts positive numbers or 0 */
		this.lifetimeCashIncome = 0;
		/** Amount of cash you have earned because of this slave last week.
		 *
		 * Accepts positive numbers or 0 */
		this.lastWeeksCashIncome = 0;
		/** Not currently used, will work similarly to the cash variables above */
		this.lifetimeRepExpenses = 0;
		/** Not currently used, will work similarly to the cash variables above */
		this.lifetimeRepIncome = 0;
		/** Not currently used, will work similarly to the cash variables above */
		this.lastWeeksRepIncome = 0;
		/** Not currently used, will work similarly to the cash variables above */
		this.lastWeeksRepExpenses = 0;
		/** Player selected class for whore to target
		 * * 1: Lower class
		 * * 2: Middle class
		 * * 3: Upper class
		 * * 4: Top class
		 */
		this.whoreClass = 0;

		/** Week By Week History of selected slave stats
		 * @type {FC.SlaveHistory[]}
		 */
		this.history = [];

		/** Counts various thing they have done */
		this.counter = new App.Entity.SlaveActionCountersState();
		/** Their skills */
		this.skill = new App.Entity.SlaveSkillsState();

		this.clothingBaseColor = undefined;
		/** @type {string | undefined} */
		this.glassesColor = undefined;
		/** @type {string | undefined} */
		this.shoeColor = undefined;
		/** @type {number | undefined} */
		this.kindness = undefined;
	}
};

/**
 * @callback slaveOperation
 * @param {FC.SlaveState} s
 * @returns {void}
 */

/**
 * @callback slaveTestCallback
 * @param {FC.SlaveState} slave
 * @returns {boolean}
 */


code:
/**
 * Contains a list of properties that children have
 * TODO: This is currently not implemented anywhere. It is waiting on changes to the nursery
 * TODO: A discussion on changes to the nursery is located at: https://gitgud.io/pregmodfan/fc-pregmod/-/issues/5165
 */
App.Entity.ChildState = class ChildState extends App.Entity.SlaveState {
	/**
	 * @param {number|string} [seed=undefined]
	 */
	constructor(seed=undefined) {
		// NOTE if the property you are adding could apply to more then just children then it likely belongs in SlaveState or HumanState instead
		super(seed);
		/** Child's origin
		 * @type {string} */
		this.origin = "$He was born and raised in your arcology.";
	}
	// TODO: static function that converts InfantState to ChildState
};


code:
/**
 * Contains a list of the properties that infants have
 */
App.Entity.InfantState = class InfantState extends App.Entity.SlaveState {
	/**
	 * @param {number|string} [seed=undefined]
	 */
	constructor(seed=undefined) {
		// NOTE if the property you are adding could apply to more then just infants then it likely belongs in SlaveState or HumanState instead
		super(seed);
		this.releaseID = App.Version.release;
		/** how many weeks until the child is ready for release */
		this.growTime = 156;
		this.targetLocation = undefined;
	}
};


code:
App.Entity.incubatorSettings = class {
	constructor(child, settingsOverride = null) {
		let fullAdapt;
		const setting = (settingsOverride !== null) ? settingsOverride : (child.genes === "XX" ? V.incubator.femaleSetting : V.incubator.maleSetting);

		if (setting.pregAdaptationPower === 1) {
			fullAdapt = 45000 / 2000;	// 22.5
		} else if (setting.pregAdaptationPower === 2) {
			fullAdapt = 100000 / 2000;	// 50
		} else if (setting.pregAdaptationPower === 3) {
			fullAdapt = 150000 / 2000;	// 75
		} else {
			fullAdapt = 15000 / 2000;	// 7.5
		}
		this.imprint = setting.imprint;
		this.weight = setting.weight;
		this.muscles = setting.muscles;
		this.growthStims = setting.growthStims;
		this.reproduction = setting.reproduction;
		this.growTime = Math.trunc(setting.targetAge * 52);
		this.pregAdaptation = setting.pregAdaptation;
		this.pregAdaptationPower = setting.pregAdaptationPower;
		this.pregAdaptationInWeek = Math.max(((fullAdapt - child.pregAdaptation) / Math.trunc(setting.targetAge * 52)), 0);
	}
};

App.Entity.TankSlaveState = class extends App.Entity.SlaveState {
	/**
	 * Do not call this directly when creating a Tank Slave, call App.Entity.TankSlaveState.toTank() instead
	 * @see App.Entity.TankSlaveState.toTank
	 * @param {number|string} [seed=undefined]
	 */
	constructor(seed=undefined) {
		// NOTE if the property you are adding could apply to more slaves then just tank slaves then it likely belongs in SlaveState instead
		super(seed);
		/** @type {App.Entity.incubatorSettings} */
		this.incubatorSettings = new App.Entity.incubatorSettings(this);
		if (this.incubatorSettings.imprint === "terror") {
			/** @type {1 | 2 | 3} */
			this.tankBaby = 2;
		} else if (this.incubatorSettings.imprint === "trust") {
			/** @type {1 | 2 | 3} */
			this.tankBaby = 1;
		} else {
			applyMindbroken(this, this.intelligence);
			/** @type {1 | 2 | 3} */
			this.tankBaby = 3;
		}
		return this;
	}

	/**
	 * Converts the given SlaveState object into a TankSlaveState
	 * @param {FC.SlaveState} child
	 * @param {any} settingsOverride // TODO: documentation and type hinting
	 * @returns {FC.TankSlaveState}
	 */
	static toTank(child, settingsOverride = null) {
		/** @type {FC.TankSlaveState} */
		const tank = /** @type {FC.TankSlaveState} */ (child);
		tank.incubatorSettings = new App.Entity.incubatorSettings(child, settingsOverride);
		if (tank.tankBaby === 0) {
			if (tank.incubatorSettings.imprint === "terror") {
				tank.tankBaby = 2;
			} else if (tank.incubatorSettings.imprint === "trust") {
				tank.tankBaby = 1;
			} else {
				applyMindbroken(tank, tank.intelligence);
				tank.tankBaby = 3;
			}
		}
		return tank;
	}

	/**
	 * Converts the given TankSlaveState object into a SlaveState, removing `incubatorSettings`
	 * @param {FC.TankSlaveState} tankSlave
	 * @returns {FC.SlaveState}
	 */
	static fromTank(tankSlave) {
		delete tankSlave.incubatorSettings;
		return tankSlave;
	}
};


code:
/**
 * @param {FC.HumanState} actor
 * @returns {boolean}
 */
globalThis.burstCheck = function(actor) {
	return !!actor.burst;
};

/**
 * Marks the actor as ready to burst. Bursting will happen during the end of week
 * @param {FC.HumanState} actor
 */
globalThis.burst = function(actor) {
	actor.burst = 1;
};


code:
/**
 * True if slave has at least one eye
 *
 * @param {FC.HumanState} slave
 * @returns {boolean}
 */
globalThis.hasAnyEyes = function(slave) {
	return !!slave.eye.right || !!slave.eye.left;
};

/**
 * True if slave has at least one eye that is natural
 *
 * @param {FC.HumanState} slave
 * @returns {boolean}
 */
globalThis.hasAnyNaturalEyes = function(slave) {
	return getLeftEyeType(slave) === 1 || getRightEyeType(slave) === 1;
};

/**
 * True if slave has at least one eye that is prosthetic (cyber or glass)
 *
 * @param {FC.HumanState} slave
 * @returns {boolean}
 */
globalThis.hasAnyProstheticEyes = function(slave) {
	return getLeftEyeType(slave) > 1 || getRightEyeType(slave) > 1;
};

/**
 * True if slave has at least one eye that is cybernetic
 *
 * @param {FC.HumanState} slave
 * @returns {boolean}
 */
globalThis.hasAnyCyberneticEyes = function(slave) {
	return getLeftEyeType(slave) === 3 || getRightEyeType(slave) === 3;
};

/**
 * True if slave has both eyes
 *
 * @param {FC.HumanState} slave
 * @returns {boolean}
 */
globalThis.hasBothEyes = function(slave) {
	return !!slave.eye.right && !!slave.eye.left;
};

/**
 * True if slave has both eyes and they are natural
 *
 * @param {FC.HumanState} slave
 * @returns {boolean}
 */
globalThis.hasBothNaturalEyes = function(slave) {
	return getLeftEyeType(slave) === 1 && getRightEyeType(slave) === 1;
};

/**
 * True if slave has both eyes and they are prosthetic (cyber or glass)
 *
 * @param {FC.HumanState} slave
 * @returns {boolean}
 */
globalThis.hasBothProstheticEyes = function(slave) {
	return getLeftEyeType(slave) > 1 && getRightEyeType(slave) > 1;
};

/**
 * True if slave has both eyes and they are cybernetic
 *
 * @param {FC.HumanState} slave
 * @returns {boolean}
 */
globalThis.hasBothCyberneticEyes = function(slave) {
	return getLeftEyeType(slave) === 3 && getRightEyeType(slave) === 3;
};

/**
 * True if slave has left eye
 *
 * @param {FC.HumanState} slave
 * @returns {boolean}
 */
globalThis.hasLeftEye = function(slave) {
	return !!slave.eye.left;
};
/**
 * True if slave has right eye
 *
 * @param {FC.HumanState} slave
 * @returns {boolean}
 */
globalThis.hasRightEye = function(slave) {
	return !!slave.eye.right;
};

/**
 * Returns type of the left eye.
 *
 * @param {FC.HumanState} slave
 * @returns {number}
 */
globalThis.getLeftEyeType = function(slave) {
	if (hasLeftEye(slave)) {
		return slave.eye.left.type;
	} else {
		return 0;
	}
};

/**
 * Returns type of the right eye.
 *
 * @param {FC.HumanState} slave
 * @returns {number}
 */
globalThis.getRightEyeType = function(slave) {
	if (hasRightEye(slave)) {
		return slave.eye.right.type;
	} else {
		return 0;
	}
};

/**
 * @param {FC.HumanState} slave
 * @returns {number}
 */
globalThis.getLeftEyeVision = function(slave) {
	if (hasLeftEye(slave)) {
		return slave.eye.left.vision;
	} else {
		return 0;
	}
};

/**
 * @param {FC.HumanState} slave
 * @returns {number}
 */
globalThis.getRightEyeVision = function(slave) {
	if (hasRightEye(slave)) {
		return slave.eye.right.vision;
	} else {
		return 0;
	}
};

/**
 * @param {FC.HumanState} slave
 * @returns {number}
 */
globalThis.getBestVision = function(slave) {
	return Math.max(getRightEyeVision(slave), getLeftEyeVision(slave));
};

/**
 * @param {FC.HumanState} slave
 * @returns {number}
 */
globalThis.getWorstVision = function(slave) {
	return Math.min(getRightEyeVision(slave), getLeftEyeVision(slave));
};

/**
 *
 * @param {FC.HumanState} slave
 * @param {number} vision
 * @returns {boolean}
 */
globalThis.anyVisionEquals = function(slave, vision) {
	return getRightEyeVision(slave) === vision || getLeftEyeVision(slave) === vision;
};

/**
 * @param {FC.HumanState} slave
 * @returns {string}
 */
globalThis.getLeftEyeColor = function(slave) {
	if (hasLeftEye(slave)) {
		return slave.eye.left.iris;
	} else {
		return "empty";
	}
};

/**
 * @param {FC.HumanState} slave
 * @returns {string}
 */
globalThis.getRightEyeColor = function(slave) {
	if (hasRightEye(slave)) {
		return slave.eye.right.iris;
	} else {
		return "empty";
	}
};

/**
 * @param {FC.HumanState} slave
 * @returns {string}
 */
globalThis.getLeftEyePupil = function(slave) {
	if (hasLeftEye(slave)) {
		return slave.eye.left.pupil;
	} else {
		return "circular";
	}
};

/**
 * @param {FC.HumanState} slave
 * @returns {string}
 */
globalThis.getRightEyePupil = function(slave) {
	if (hasRightEye(slave)) {
		return slave.eye.right.pupil;
	} else {
		return "circular";
	}
};

/**
 * @param {FC.HumanState} slave
 * @returns {string}
 */
globalThis.getLeftEyeSclera = function(slave) {
	if (hasLeftEye(slave)) {
		return slave.eye.left.sclera;
	} else {
		return "empty";
	}
};

/**
 * @param {FC.HumanState} slave
 * @returns {string}
 */
globalThis.getRightEyeSclera = function(slave) {
	if (hasRightEye(slave)) {
		return slave.eye.right.sclera;
	} else {
		return "empty";
	}
};

/**
 * @param {FC.HumanState} slave
 * @returns {boolean}
 */
globalThis.hasVisibleHeterochromia = function(slave) {
	return hasAnyEyes(slave) && getLeftEyeColor(slave) !== getRightEyeColor(slave);
};

/**
 * Gives the genetic color of the specified eye part.
 *
 * @param {FC.HumanState} actor
 * @param {FC.BodySide} side
 * @param {"iris"|"pupil"|"sclera"} [eyePart="iris"]
 * @returns {string}
 */
globalThis.getGeneticEyeColor = function(actor, side, eyePart = "iris") {
	if (eyePart === "iris") {
		if (actor.geneticQuirks.albinism === 2 && "albinismOverride" in actor) {
			return actor.albinismOverride.eyeColor;
		} else {
			if (side === "left" && typeof actor.geneticQuirks.heterochromia === "string") {
				return actor.geneticQuirks.heterochromia;
			} else {
				return actor.eye.origColor;
			}
		}
	} else if (eyePart === "pupil") {
		return actor.race === "catgirl" ? "catlike" : "circular";
	} else {
		return "white";
	}
};

/**
 * Counts the number of eyes that are not the genetic color
 *
 * @param {FC.HumanState} slave
 * @returns {number}
 */
globalThis.getLenseCount = function(slave) {
	let count = 0;

	if (hasRightEye(slave) &&
		(getRightEyeColor(slave) !== getGeneticEyeColor(slave, "right", "iris")) ||
		(getRightEyePupil(slave) !== getGeneticEyeColor(slave, "right", "pupil")) ||
		(getRightEyeSclera(slave) !== getGeneticEyeColor(slave, "right", "sclera"))) {
		count++;
	}

	if (hasLeftEye(slave) &&
		getLeftEyeColor(slave) !== getGeneticEyeColor(slave, "left", "iris") ||
		(getLeftEyePupil(slave) !== getGeneticEyeColor(slave, "left", "pupil")) ||
		(getLeftEyeSclera(slave) !== getGeneticEyeColor(slave, "left", "sclera"))) {
		count++;
	}

	return count;
};


code:
/* limb checker */

/**
 * True if slave has no limbs, neither natural nor prosthetic
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.isAmputee = function(slave) {
	return !(slave.leg.right || slave.leg.left || slave.arm.right || slave.arm.left);
};

/**
 * True if slave has at least one natural limb
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasAnyNaturalLimbs = function(slave) {
	return getLeftArmID(slave) === 1 || getRightArmID(slave) === 1 || getLeftLegID(slave) === 1 || getRightLegID(slave) === 1;
};

/**
 * True if slave has at least one prosthetic limb
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasAnyProstheticLimbs = function(slave) {
	return getLeftArmID(slave) > 1 || getRightArmID(slave) > 1 || getLeftLegID(slave) > 1 || getRightLegID(slave) > 1;
};

/**
 * True if slave has at least one quadrupedal limb
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasAnyQuadrupedLimbs = function(slave) {
	return getLeftArmID(slave) > 6 || getRightArmID(slave) > 6 || getLeftLegID(slave) > 6 || getRightLegID(slave) > 6;
};

/**
 * True if slave has at least one leg
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasAnyLegs = function(slave) {
	return !!slave.leg.right || !!slave.leg.left;
};

/**
 * True if slave has at least one arm
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasAnyArms = function(slave) {
	return !!slave.arm.right || !!slave.arm.left;
};

/**
 * True if slave has at least one leg that is natural
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasAnyNaturalLegs = function(slave) {
	return getLeftLegID(slave) === 1 || getRightLegID(slave) === 1;
};

/**
 * True if slave has at least one arm that is natural
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasAnyNaturalArms = function(slave) {
	return getLeftArmID(slave) === 1 || getRightArmID(slave) === 1;
};

/**
 * True if slave has at least one leg that is prosthetic
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasAnyProstheticLegs = function(slave) {
	return getLeftLegID(slave) > 1 || getRightLegID(slave) > 1;
};

/**
 * True if slave has at least one leg that is quadrupedal
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasAnyQuadrupedLegs = function(slave) {
	return getLeftLegID(slave) > 6 || getRightLegID(slave) > 6;
};

/**
 * True if slave has at least one arm that is prosthetic
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasAnyProstheticArms = function(slave) {
	return getLeftArmID(slave) > 1 || getRightArmID(slave) > 1;
};

/**
 * True if slave has at least one arm that is quadrupedal
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasAnyQuadrupedArms = function(slave) {
	return getLeftArmID(slave) > 6 || getRightArmID(slave) > 6;
};

/**
 * True if slave has both legs
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasBothLegs = function(slave) {
	return !!slave.leg.right && !!slave.leg.left;
};

/**
 * True if slave has both arms
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasBothArms = function(slave) {
	return !!slave.arm.right && !!slave.arm.left;
};

/**
 * True if slave has both legs and they are natural
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasBothNaturalLegs = function(slave) {
	return getLeftLegID(slave) === 1 && getRightLegID(slave) === 1;
};

/**
 * True if slave has both arms and they are natural
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasBothNaturalArms = function(slave) {
	return getLeftArmID(slave) === 1 && getRightArmID(slave) === 1;
};

/**
 * True if slave has both arms and they are artificial
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasBothProstheticArms = function(slave) {
	return getLeftArmID(slave) > 1 && getRightArmID(slave) > 1;
};

/**
 * True if slave has both arms and they are quadrupedal
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasBothQuadrupedArms = function(slave) {
	return getLeftArmID(slave) > 6 && getRightArmID(slave) > 6;
};

/**
 * True if slave has both legs and they are artificial
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasBothProstheticLegs = function(slave) {
	return getLeftLegID(slave) > 1 && getRightLegID(slave) > 1;
};

/**
 * True if slave has both legs and they are quadrupedal
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasBothQuadrupedLegs = function(slave) {
	return getLeftLegID(slave) > 6 && getRightLegID(slave) > 6;
};

/**
 * True if slave has any limbs
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasAnyLimbs = function(slave) {
	return hasAnyArms(slave) || hasAnyLegs(slave);
};

/**
 * True if slave has all limbs
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasAllLimbs = function(slave) {
	return hasBothLegs(slave) && hasBothArms(slave);
};

/**
 * True if slave has all limbs and all are natural
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasAllNaturalLimbs = function(slave) {
	return hasBothNaturalLegs(slave) && hasBothNaturalArms(slave);
};

/**
 * True if slave has all limbs and all are quadrupedal
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.isQuadrupedal = function(slave) {
	return hasBothQuadrupedLegs(slave) && hasBothQuadrupedArms(slave);
};

/**
 * True if slave has left arm
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasLeftArm = function(slave) {
	return !!slave.arm.left;
};

/**
 * True if slave has right arm
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasRightArm = function(slave) {
	return !!slave.arm.right;
};

/**
 * True if slave has left leg
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasLeftLeg = function(slave) {
	return !!slave.leg.left;
};

/**
 * True if slave has right leg
 *
 * @param {FC.LimbsState} slave
 * @returns {boolean}
 */
globalThis.hasRightLeg = function(slave) {
	return !!slave.leg.right;
};

/**
 * Returns limb ID of the left arm. Uses new IDs.
 *
 * @param {FC.LimbsState} slave
 * @returns {FC.LimbType}
 */
globalThis.getLeftArmID = function(slave) {
	if (hasLeftArm(slave)) {
		return slave.arm.left.type;
	} else {
		return 0;
	}
};

/**
 * Returns limb ID of the right arm. Uses new IDs.
 *
 * @param {FC.LimbsState} slave
 * @returns {FC.LimbType}
 */
globalThis.getRightArmID = function(slave) {
	if (hasRightArm(slave)) {
		return slave.arm.right.type;
	} else {
		return 0;
	}
};

/**
 * Returns limb ID of the left leg. Uses new IDs.
 *
 * @param {FC.LimbsState} slave
 * @returns {FC.LimbType}
 */
globalThis.getLeftLegID = function(slave) {
	if (hasLeftLeg(slave)) {
		return slave.leg.left.type;
	} else {
		return 0;
	}
};

/**
 * Returns limb ID of the right leg. Uses new IDs.
 *
 * @param {FC.LimbsState} slave
 * @returns {FC.LimbType}
 */
globalThis.getRightLegID = function(slave) {
	if (hasRightLeg(slave)) {
		return slave.leg.right.type;
	} else {
		return 0;
	}
};

/**
 * Returns a very short description of the specified limb ID. Uses new limb IDs.
 *
 * @param {number} id
 * @returns {string}
 */
globalThis.idToDescription = function(id) {
	switch (id) {
		case 0:
			return "amputated";
		case 1:
			return "healthy";
		case 2:
			return "modern prosthetic";
		case 3:
			return "advanced, sex-focused prosthetic";
		case 4:
			return "advanced, beauty-focused prosthetic";
		case 5:
			return "advanced, combat-focused prosthetic";
		case 6:
			return "highly advanced cybernetic";
		case 7:
			return "feline prosthetic";
		case 8:
			return "canine prosthetic";
		case 9:
			return "feline combat-adapted prosthetic";
		case 10:
			return "canine combat-adapted prosthetic";
		default:
			return "unknown id: " + id;
	}
};

/**
 * Returns count of specified limb type. Uses new limb IDs:
 * * 0: no limb
 * * 1: natural
 * * 2: basic
 * * 3: sex
 * * 4: beauty
 * * 5: combat
 * * 6: cybernetic
 * * 7: felidaeL
 * * 8: canidaeL
 * * 9: felidaeCL
 * * 10: canidaeCL
 *
 * Can also be used to check for groups:
 * * 101: any limbs, that are not amputated
 * * 102: prosthetic limbs off all kind
 * * 103: sex-prosthetic
 * * 104: beauty-prosthetic
 * * 105: combat-prosthetic
 * * 106: quadruped
 * * 107: feline
 * * 108: canine
 * *
 *
 * !!! I think I adjusted this right but i didn't spend too long on it so it might be wrong. !!!
 *
 * @param {FC.LimbsState} slave
 * @param {number} [id] Defaults to all limb types if unspecified (id = 101)
 * @returns {number}
 */
globalThis.getLimbCount = function(slave, id = 101) {
	if (id < 100) {
		let n = 0;
		if (getLeftArmID(slave) === id) {
			n++;
		}
		if (getRightArmID(slave) === id) {
			n++;
		}
		if (getLeftLegID(slave) === id) {
			n++;
		}
		if (getRightLegID(slave) === id) {
			n++;
		}
		return n;
	}
	switch (id) {
		case 101:
			return getLimbCount(slave, 1) + getLimbCount(slave, 102);
		case 102:
			return getLimbCount(slave, 2) + getLimbCount(slave, 3) + getLimbCount(slave, 4) + getLimbCount(slave, 5) + getLimbCount(slave, 6) + getLimbCount(slave, 7) + getLimbCount(slave, 8) + getLimbCount(slave, 9) + getLimbCount(slave, 10);
		case 103:
			return getLimbCount(slave, 3) + getLimbCount(slave, 6);
		case 104:
			return getLimbCount(slave, 4) + getLimbCount(slave, 6);
		case 105:
			return getLimbCount(slave, 5) + getLimbCount(slave, 6) + getLimbCount(slave, 9) + getLimbCount(slave, 10);
		case 106:
			return getLimbCount(slave, 7) + getLimbCount(slave, 8) + getLimbCount(slave, 9) + getLimbCount(slave, 10);
		case 107:
			return getLimbCount(slave, 7) + getLimbCount(slave, 9);
		case 108:
			return getLimbCount(slave, 8) + getLimbCount(slave, 10);
	}
	// unknown id defaults to 0
	return 0;
};

/**
 * Returns count of specified leg type. Uses new limb IDs.
 *
 * @param {FC.LimbsState} slave
 * @param {number} id
 * @returns {number}
 */
globalThis.getLegCount = function(slave, id) {
	let n = 0;

	if (getLeftLegID(slave) === id) {
		n++;
	}
	if (getRightLegID(slave) === id) {
		n++;
	}

	return n;
};

/**
 * Returns count of specified arm type. Uses new limb IDs.
 *
 * @param {FC.LimbsState} slave
 * @param {number} id
 * @returns {number}
 */
globalThis.getArmCount = function(slave, id) {
	let n = 0;

	if (getLeftArmID(slave) === id) {
		n++;
	}
	if (getRightArmID(slave) === id) {
		n++;
	}

	return n;
};

/**
 * Returns a string depending on the limbs a slave has.
 * By default a variation of "arms and legs", but this can be changed via parameters.
 * Expects the slave to at least have one limb.
 *
 * @param {FC.LimbsState} slave
 * @param {string} [arms]
 * @param {string} [arm]
 * @param {string} [legs]
 * @param {string} [leg]
 */
globalThis.armsAndLegs = function(slave, arms = "arms", arm = "arm", legs = "legs", leg = "leg") {
	let r = "";
	if (hasAnyArms(slave)) {
		if (hasBothArms(slave)) {
			r += arms;
		} else {
			r += arm;
		}
		if (hasAnyLegs(slave)) {
			r += " and ";
		}
	}

	if (hasBothLegs(slave)) {
		r += legs;
	} else if (hasAnyLegs(slave)) {
		r += leg;
	}

	return r;
};


code:
/**
 * @param {FC.HumanState} actor
 * @returns {boolean}
 */
globalThis.isInLabor = function(actor) {
	return !!actor.labor;
};

/**
 * @param {FC.HumanState} actor
 */
globalThis.startLabor = function(actor) {
	actor.labor = 1;
};

/**
 * @param {FC.HumanState} actor
 * @returns {boolean}
 */
globalThis.isInduced = function(actor) {
	return !!actor.induce;
};

/**
 * @param {FC.HumanState} actor
 */
globalThis.induce = function(actor) {
	startLabor(actor);
	actor.induce = 1;
};