code:
declare namespace FC {
	type prostheticID = "interfaceP1" | "interfaceP2" | "interfaceP3" | "basicL" | "sexL" | "beautyL" | "combatL" | "felidaeL" | "canidaeL" | "felidaeCL" | "canidaeCL" | "cyberneticL"
		| "ocular" | "cochlear" | "electrolarynx" | "interfaceTail" | "modT" | "sexT" | "combatT" | "combatT2" |/* "erectile" |*/ "interfaceBack" | "modW" | "flightW"
		| "sexA" | "combatW" | "combatA1" | "combatA2";

	type prostheticName = "basic prosthetic interface" | "advanced prosthetic interface" | "quadrupedal prosthetic interface" | "set of basic prosthetic limbs" | "set of advanced sex limbs"
		| "set of advanced beauty limbs" | "set of advanced combat limbs" | "set of quadruped feline limbs" | "set of quadruped canine limbs" | "set of feline combat limbs"
		| "set of canine combat limbs" | "set of cybernetic limbs" | "ocular implant" | "cochlear implant" | "electrolarynx" | "prosthetic tail interface" | "modular tail"
		| "pleasure tail" | "combat tail" | `combat tail, type "Stinger"` | "prosthetic back interface" | "modular pair of wings" | "pair of flight capable wings"
		| "set of pleasure appendages" | `"set of combat appendages, type "Falcon"` | `set of combat appendages, type "Arachnid"` | `set of combat appendages, type "Kraken"`;

	type LimbArgument = "left arm" | "right arm" | "left leg" | "right leg"
	type LimbArgumentAll = "all" | LimbArgument

	type BodySide = "left" | "right"
	type BodySideAll = "both" | BodySide

	interface AdjustProsthetics {
		id: prostheticID;
		workLeft: number;
		slaveID: number;
	}

	type TaskType = "research" | "craft" | "craftFit";

	interface Tasks {
		type: TaskType;
		id: prostheticID;
		workLeft: number;
		craftFit?: number;
	}

	interface ResearchLab {
		level: number;
		aiModule: number;
		tasks: Tasks;
		maxSpace: number;
		hired: number;
		menials: number;
	}

	namespace Data.Medicine.SizingImplants {
		type DynamicName = (volume: number) => string;
		type DynamicCostFactor = (volume: number) => number;

		interface FillDrainData {
			limit: number;
			step: number[];
			healthCost: number;
			materialCostFactor: number;
		}
		interface ImplantType {
			name: string | DynamicName;
			specificMaterialCost: number | DynamicCostFactor;
			workCostFactor: {
				installation: number;
				removal: number;
			}
			healthCostFactor: {
				installation: number;
				removal: number;
			},
			availableSizes: number[] | (() => number[]);
			/** If defined the implant is fillable and can be filled up to the given size */
			fill?: FillDrainData;
			/** If defined the implant is drainable and can be drained down to the given size */
			drain?: FillDrainData;
		}

		type SizingImplantDatabase = {
			[K in SizingImplantTarget]: Record<BodyPartImplantTypeMap[K], FC.Data.Medicine.SizingImplants.ImplantType>;
		}
	}

	namespace Medicine {

		type ProcedureInstance = InstanceType<typeof App.Medicine.Surgery.Procedure>;
		type SizingImplantProcedure = InstanceType<typeof App.Medicine.Surgery.Procedures.SizingImplantProcedure>;

		type SizingImplantProcedureConstructor<T extends SizingImplantProcedure = SizingImplantProcedure> = new (...args: any[]) => T;

		interface ImplantProcedureCreators {
			install: (slave: FC.SlaveState, implantType: SizingImplantType, volume: number, fleshExcess: number) => ProcedureInstance;
			remove: (slave: FC.SlaveState) => ProcedureInstance;
			replace: (slave: FC.SlaveState, implantType: SizingImplantType, volume: number, fleshExcess: number) => ProcedureInstance;

			fill: (slave: FC.SlaveState, amount: number) => ProcedureInstance;
			drain: (slave: FC.SlaveState, amount: number) => ProcedureInstance;
		}

		type ImplantInfo<Target extends SizingImplantTarget> = {
			type: BodyPartInstalledImplantType<Target>;
			volume: number;
		}

		interface AssetSizingProcedureSet extends ImplantProcedureCreators {
			reduce: (slave: SlaveState, name: string, amount: number) => ProcedureInstance;
		}

		namespace Surgery {
			/**
			 * Describes surgical procedure
			 */
			interface Procedure {
				/**
				 * Type code that identifies this kind of procedure.
				 * Currently unused, but planned for future use by RA for prioritizing procedures
				 */
				typeId: string;
				/**
				 * Short label for the procedure. Can be used as a link text.
				 */
				label: string;
				/**
				 * If procedure is targeted at changing object characteristic, this is the net change (signed)
				 */
				targetEffect: number;
				/**
				 * Description of the procedure, more or less detailed
				 */
				description: string;
				/**
				 * Money costs (positive when you pay for it)
				 */
				costs: number;
				/**
				 * Projected health loss (positive when health decreases)
				 */
				healthCosts: number;
				/**
				 * Function to perform the procedure
				 * If action is undefined, the procedure can't be applied (and .description contains the reason)
				 */
				action: slaveOperation;
				/**
				 * surgery type for passages like "Surgery Degradation"
				 */
				surgeryType: string;
			}

			type SizingOptions<Target extends SizingImplantTarget> = {
				targetSize?: NumericRange;
				/**
				 * Allowed procedures for given implant type
				 */
				allowedTypes?: Set<BodyPartInstalledImplantType<Target>>;
				/** include options with implant change */
				replace?: boolean;
			}

			type DefinitiveSizedOptions<Target extends SizingImplantTarget> = Required<SizingOptions<Target>>;
		}
		namespace OrganFarm {
			interface GrowingOrgan {
				type: string;
				weeksToCompletion: number;
				ID: number;
			}
			namespace Organs {
			}
		}
	}
}


code:
declare namespace FC {
	type SlaveSchoolName = "GRI" | "HA" | "NUL" | "SCP" | "TCR" | "TFS" | "TGA" | "TSS" | "LDE" | "TUO";
	type LawlessMarkets = "generic" | "gangs and smugglers" | "heap" | "indentures" | "low tier criminals" | "military prison" | "juvenile detention" |
		"neighbor" | "wetware" | "white collar" | SlaveSchoolName;
	type OrdinaryMarkets = "kidnappers" | "trainers" | "hunters" | "raiders" | "underage raiders" | "corporate";
	type SlaveMarketName = LawlessMarkets | OrdinaryMarkets;
	type SpecialMarketName = "Elite Slave" | "Household Liquidator" | "Custom Slave" | "Husk Slave" | "Slave Shelter" | "JobFulfillmentCenterOrder" | "Prestigious Slave" | "Special Slave";
	type Gingering = Zeroable<"antidepressant" | "depressant" | "stimulant" | "vasoconstrictor" | "vasodilator" | "aphrodisiac" | "ginger">;
	type GingeringDetection = Zeroable<"slaver" | "mercenary" | "force">;
	type SlaveActs = "PCChildrenFathered" | "PCKnockedUp" | "anal" | "births" | "birthsTotal" | "cum" | "laborCount" | "mammary" | "milk" | "oral" | "penetrative" | "pitKills" | "miscarriages" | "publicUse" | "slavesFathered" | "slavesKnockedUp" | "vaginal" | "abortions" | "birth" | "bestiality";

	interface SlaveSchool {
		title: string,
		nickname: string,
		branchName: string,
		slaveNoun: string,
		readonly requirements: boolean,
	}

	namespace SlaveSummary {
		interface SmartPiercing {
			setting: {
				off: string,
				submissive: string,
				lesbian: string,
				oral: string,
				humiliation: string,
				anal: string,
				boobs: string,
				sadist: string,
				masochist: string,
				dom: string,
				pregnancy: string,
				vanilla: string,
				all: string,
				none: string,
				monitoring: string,
				men: string,
				women: string,
				"anti-men": string,
				"anti-women": string,
			};
		}
	}
}

repo_name: Arkerthan/fc

license: mit


code:
declare namespace FC {
	namespace Mods {
		/** The base mod interface, from which all mod interfaces inherit. */
		interface Base {
			/** Whether the mod is enabled. */
			enabled: boolean;
		}

		/** Properties relating to the food system mod. */
		interface Food extends Base {
			/** How much food the arcology has, in kg. */
			amount: number;
			/** The base price of 1kg of food. */
			cost: number;
			/** How much food the arcology had at the end of last week, in kg. */
			lastWeek: number;
			/** Whether the food market has been established. */
			market: boolean;
			/** The amount of food produced this week, in kg. */
			produced: number;
			/** The amount of food each class consumes in a week. */
			rate: {
				/** The amount of food a slave consumes in a week, in kg. */
				slave: 8,
				/** The amount of food the lower class consumes in a week, in kg. */
				lower: 14.5,
				/** The amount of food the middle class consumes in a week, in kg. */
				middle: 16,
				/** The amount of food the upper class consumes in a week, in kg. */
				upper: 17.5,
				/** The amount of food the elite class consumes in a week, in kg. */
				top: 19,
			},
			/**
			 * How much food you are providing your citizens weekly.
			 *
			 * Not in kg.
			 */
			rations: 0 | 1 | 2 | 3 | 4 | 5;
			/** How much food the arcology has produced in total, in kg. */
			total: number;
			/** Whether the player has received a warning about a lack of food. */
			warned: boolean;
		}
	}
}


code:
declare namespace FC {
    namespace Util {
        interface DiffBase<T> {
            /**
             * The original object
             */
            diffOriginal: T
            /**
             * The changes applied to the object
             */
            diffChange: Partial<T>
        }

        type DiffRecorder<T> = T & DiffBase<T>
	}

	interface NumericRange {
		min: number;
		max: number;
	}
}


code:
declare namespace App.UI.DOM {
	function makeElementTS<K extends keyof HTMLElementTagNameMap>(
		tag: K, content?: string | Node, classNames?: string[]): HTMLElementTagNameMap[K];
	/** @deprecated */
	function makeElementTS<K extends keyof HTMLElementTagNameMap>(
		tag: K, content?: string | Node, classNames?: string): HTMLElementTagNameMap[K];

	function appendNewElementTS<K extends keyof HTMLElementTagNameMap>(
		tag: K, parent: ParentNode, content?: string | Node, classNames?: string[]): HTMLElementTagNameMap[K];
	/** @deprecated */
	function appendNewElementTS<K extends keyof HTMLElementTagNameMap>(
		tag: K, parent: ParentNode, content?: string | Node, className?: string): HTMLElementTagNameMap[K];
}


code:
import {StoryMoment, Passage} from "twine-sugarcube";

declare module "twine-sugarcube" {
	interface SugarCubeStoryVariables extends FC.GameVariables {
	}

	interface SugarCubeSetupObject {
		// Any usages of the global setup object should be replaced by App.Data.misc
	}

	// These are SugarCube private APIs used in the project
	interface StateAPI {
		expired: StoryMoment[];
		clearTemporary(): void;
	}

	interface UIBarAPI {
		update(): void;
	}
}

export {};


code:
type assistantAppearance = "normal" | "monstergirl" | "shemale" | "amazon" | "businesswoman" | "goddess" | "hypergoddess" | "schoolgirl" | "loli" | "preggololi" | "fairy" | "pregnant fairy" | "slimegirl" | "cowgirl"| "harpygirl" | "kitsunegirl" | "lamiagirl" | "spidergirl" | "angel" | "cherub" | "imp" | "witch" | "ERROR_1606_APPEARANCE_FILE_CORRUPT" | "incubus" | "succubus";

type fsAssistantAppearance = "default" | "paternalist" | "degradationist" | "supremacist" | "subjugationist" | "roman revivalist" | "aztec revivalist" | "egyptian revivalist" | "edo revivalist" | "arabian revivalist" | "chinese revivalist" | "chattel religionist" | "repopulation focus" | "eugenics" | "physical idealist" | "hedonistic decadence" | "gender radicalist" | "gender fundamentalist" | "asset expansionist" | "transformation fetishist" | "pastoralist" | "maturity preferentialist" | "youth preferentialist" | "slimness enthusiast" | "body purist" | "intellectual dependency" | "slave professionalism" | "petite admiration" | "statuesque glorification" | "neoimperialist" | "antebellum revivalist";
interface assistant {
	appearance: assistantAppearance;
	fsAppearance: fsAssistantAppearance
	personality: -1 | 0 | 1;
	name: string;
	power: number;
	fsOptions: FC.Bool;
	options: FC.Bool;
	market: {
		relationship: "nonconsensual" | "incestuous" | "cute" | "romantic";
		limit: number;
		aggressiveness: number;
	};
	main: number;
	Extra1: FC.Bool;
	Extra2: FC.Bool;
	announcedName: FC.Bool;
	customImage: FC.CustomImage;
}

type appearance = Record<assistantAppearance, fsAppearance>;

type fsAppearance = Record<fsAssistantAppearance, string>;


code:
// Extension from mousetrap-record
interface MousetrapStatic {
	record(callback: (this: MousetrapStatic, sequence: string[]) => void): void;
}

interface Number {
	/**
	 * Returns whether the value is between the given bounds, with optional inclusivity. Exclusive by default.
	 * @param min The minimum value to check against.
	 * @param max The maximum value to check against.
	 * @param inclusive Whether to include the bounds values.
	 */
	 isBetween(min: number, max: number, inclusive?: boolean): boolean;
}

type EnumerablePropertyKey<T extends PropertyKey> = T extends symbol ? never : (T extends number ? string : T);

interface ObjectConstructor {
	keys<K extends PropertyKey, V>(o: Partial<Record<K, V>>): EnumerablePropertyKey<K>[];
	entries<K extends PropertyKey, V>(o: Partial<Record<K, V>>): [EnumerablePropertyKey<K>, V][];
}


code:
import {Tippy} from "./tippy";

declare global {
    const tippy: Tippy;
}


code:
import * as Popper from '@popperjs/core';

export type BasePlacement = Popper.BasePlacement;

export type Placement = Popper.Placement;

export type Content =
  | string
  | Element
  | DocumentFragment
  | ((ref: Element) => string | Element | DocumentFragment);

export type SingleTarget = Element;

export type MultipleTargets = string | Element[] | NodeList;

export type Targets = SingleTarget | MultipleTargets;

export interface ReferenceElement<TProps = Props> extends Element {
  _tippy?: Instance<TProps>;
}

export interface PopperElement<TProps = Props> extends HTMLDivElement {
  _tippy?: Instance<TProps>;
}

export interface LifecycleHooks<TProps = Props> {
  onAfterUpdate(
    instance: Instance<TProps>,
    partialProps: Partial<TProps>
  ): void;
  onBeforeUpdate(
    instance: Instance<TProps>,
    partialProps: Partial<TProps>
  ): void;
  onCreate(instance: Instance<TProps>): void;
  onDestroy(instance: Instance<TProps>): void;
  onHidden(instance: Instance<TProps>): void;
  onHide(instance: Instance<TProps>): void | false;
  onMount(instance: Instance<TProps>): void;
  onShow(instance: Instance<TProps>): void | false;
  onShown(instance: Instance<TProps>): void;
  onTrigger(instance: Instance<TProps>, event: Event): void;
  onUntrigger(instance: Instance<TProps>, event: Event): void;
  onClickOutside(instance: Instance<TProps>, event: Event): void;
}

export interface RenderProps {
  allowHTML: boolean;
  animation: string | boolean;
  arrow: boolean | string | SVGElement | DocumentFragment;
  content: Content;
  inertia: boolean;
  maxWidth: number | string;
  role: string;
  theme: string;
  zIndex: number;
}

export interface GetReferenceClientRect {
  (): ClientRect | DOMRect;
  contextElement?: Element;
}

export interface Props extends LifecycleHooks, RenderProps {
  animateFill: boolean;
  appendTo: 'parent' | Element | ((ref: Element) => Element);
  aria: {
    content?: 'auto' | 'describedby' | 'labelledby' | null;
    expanded?: 'auto' | boolean;
  };
  delay: number | [number | null, number | null];
  duration: number | [number | null, number | null];
  followCursor: boolean | 'horizontal' | 'vertical' | 'initial';
  getReferenceClientRect: null | GetReferenceClientRect;
  hideOnClick: boolean | 'toggle';
  ignoreAttributes: boolean;
  inlinePositioning: boolean;
  interactive: boolean;
  interactiveBorder: number;
  interactiveDebounce: number;
  moveTransition: string;
  offset:
    | [number, number]
    | (({
        placement,
        popper,
        reference,
      }: {
        placement: Placement;
        popper: Popper.Rect;
        reference: Popper.Rect;
      }) => [number, number]);
  placement: Placement;
  plugins: Plugin<unknown>[];
  popperOptions: Partial<Popper.Options>;
  render:
    | ((
        instance: Instance
      ) => {
        popper: PopperElement;
        onUpdate?: (prevProps: Props, nextProps: Props) => void;
      })
    | null;
  showOnCreate: boolean;
  sticky: boolean | 'reference' | 'popper';
  touch: boolean | 'hold' | ['hold', number];
  trigger: string;
  triggerTarget: Element | Element[] | null;
}

export interface DefaultProps extends Omit<Props, 'delay' | 'duration'> {
  delay: number | [number, number];
  duration: number | [number, number];
}

export interface Instance<TProps = Props> {
  clearDelayTimeouts(): void;
  destroy(): void;
  disable(): void;
  enable(): void;
  hide(): void;
  hideWithInteractivity(event: MouseEvent): void;
  id: number;
  plugins: Plugin<TProps>[];
  popper: PopperElement<TProps>;
  popperInstance: Popper.Instance | null;
  props: TProps;
  reference: ReferenceElement<TProps>;
  setContent(content: Content): void;
  setProps(partialProps: Partial<TProps>): void;
  show(): void;
  state: {
    isEnabled: boolean;
    isVisible: boolean;
    isDestroyed: boolean;
    isMounted: boolean;
    isShown: boolean;
  };
  unmount(): void;
}

export interface TippyStatics {
  readonly currentInput: {isTouch: boolean};
  readonly defaultProps: DefaultProps;
  setDefaultProps(partialProps: Partial<DefaultProps>): void;
}

export interface Tippy<TProps = Props> extends TippyStatics {
  (targets: SingleTarget, optionalProps?: Partial<TProps>): Instance<TProps>;
}

export interface Tippy<TProps = Props> extends TippyStatics {
  (targets: MultipleTargets, optionalProps?: Partial<TProps>): Instance<
    TProps
  >[];
}

declare const tippy: Tippy;

// =============================================================================
// Addon types
// =============================================================================
export interface DelegateInstance<TProps = Props> extends Instance<TProps> {
  destroy(shouldDestroyTargetInstances?: boolean): void;
}

export interface Delegate<TProps = Props> {
  (
    targets: SingleTarget,
    props: Partial<TProps> & {target: string}
  ): DelegateInstance<TProps>;
}

export interface Delegate<TProps = Props> {
  (
    targets: MultipleTargets,
    props: Partial<TProps> & {target: string}
  ): DelegateInstance<TProps>[];
}

export type CreateSingletonProps<TProps = Props> = TProps & {
  overrides: Array<keyof TProps>;
};

export type CreateSingletonInstance<TProps = CreateSingletonProps> = Instance<
  TProps
> & {
  setInstances(instances: Instance<any>[]): void;
  show(target?: ReferenceElement | Instance | number): void;
  showNext(): void;
  showPrevious(): void;
};

export type CreateSingleton<TProps = Props> = (
  tippyInstances: Instance<any>[],
  optionalProps?: Partial<CreateSingletonProps<TProps>>
) => CreateSingletonInstance<CreateSingletonProps<TProps>>;

declare const delegate: Delegate;
declare const createSingleton: CreateSingleton;

// =============================================================================
// Plugin types
// =============================================================================
export interface Plugin<TProps = Props> {
  name?: string;
  defaultValue?: any;
  fn(instance: Instance<TProps>): Partial<LifecycleHooks<TProps>>;
}

export interface AnimateFill extends Plugin {
  name: 'animateFill';
  defaultValue: false;
}

export interface FollowCursor extends Plugin {
  name: 'followCursor';
  defaultValue: false;
}

export interface InlinePositioning extends Plugin {
  name: 'inlinePositioning';
  defaultValue: false;
}

export interface Sticky extends Plugin {
  name: 'sticky';
  defaultValue: false;
}

declare const animateFill: AnimateFill;
declare const followCursor: FollowCursor;
declare const inlinePositioning: InlinePositioning;
declare const sticky: Sticky;

// =============================================================================
// Misc types
// =============================================================================
export interface HideAllOptions {
  duration?: number;
  exclude?: Instance | ReferenceElement;
}

export type HideAll = (options?: HideAllOptions) => void;

declare const hideAll: HideAll;
declare const roundArrow: string;

export default tippy;
export {
  hideAll,
  delegate,
  createSingleton,
  animateFill,
  followCursor,
  inlinePositioning,
  sticky,
  roundArrow,
};


code:
#!/bin/bash
repo=upstream
branch=pregmod-master
function displayHelp() {
cat <<HelpText
Package required: git

Usage: $0 [OPTION]...
Options:
 -r, --repo  X    : Repo to check. Default: $repo
 -b, --branch X   : Branch to check. Default: $branch
 -v, --verbose X  : Be verbose. Default: $verbose
 -h, --help       : Show this help text
HelpText
}
while [[ "$1" ]]; do
	case $1 in
	-r | --repo)
		repo=$2
		shift
		;;
	-b | --branch)
		branch=$2
		shift
		;;
	-v | --verbose)
		verbose=true
		;;
	-h | --help)
		displayHelp
		exit 0
		;;
	*)
		echo "Unknown argument $1."
		displayHelp
		exit -1
		;;
	esac
	shift
done

git fetch $repo -q
if [[ $(git rev-list HEAD...$repo/$branch) ]]; then # https://adamj.eu/tech/2020/01/18/a-git-check-for-missing-commits-from-a-remote/
	if [[ $(git rev-parse --abbrev-ref HEAD) = "$branch" ]]; then
		git reset --hard $repo/$branch -q
	elif [[ $(git rev-parse --abbrev-ref HEAD) != "$branch" ]]; then
		git merge $repo/$branch -q
	fi
	exit=1
fi
if [ $verbose ]; then
	echo "Updated: $(if [ $exit ]; then echo "Yes"; else echo "No"; fi)" # stackoverflow.com/a/73539272
fi


code:
#!/bin/bash
email=xyz1256@corp.com
password=12345678
local=fc/
remote=FC/
gen=0;
runstep=0

function displayHelp() {
cat <<HelpText
Packages required: git, megacmd

Usage: $0 [OPTION]...
Options:
 -s, --step  X    : Step # to manually execute e.g. 1 to run "Applying WebGL decensor patch."
 -e, --email X    : Set MEGA email to X. Default: $email
 -p, --password X : Set MEGA password to X. Default: $password
 -l, --local X    : Set local (File System) folder/directory to X. Default: $local
 -r, --remote X   : Set remote (MEGA) folder/directory to X. Default: $remote
 -h, --help       : Show this help text
HelpText
}

if [[ "$#" -eq 0 ]]; then echo -e "For more options see $0 -h.\n"; fi
while [[ "$1" ]]; do
	case $1 in
	-s | --step)
		runstep=$2
		shift
		;;
	-e | --email)
		email=$2
		shift
		;;
	-p | --password)
		password=$2
		shift
		;;
	-l | --local)
		local=$2
		shift
		;;
	-r | --remote)
		remote=$2
		shift
		;;
	-h | --help)
		displayHelp
		exit 0
		;;
	*)
		echo "Unknown argument $1."
		displayHelp
		exit 1
		;;
	esac
	shift
done

find $local > /dev/null
if [[ `echo $?` > 0 ]]; then git clone -q https://gitgud.io/pregmodfan/fc-pregmod.git --depth 1 $local && gen=1; fi
cd $local

find js/000-browserSupport.js > /dev/null
if [[ `echo $?` > 0 ]]; then
	echo "creating file to work with browsers that do not support globalThis."
	echo "const globalThis = eval.call(undefined, 'this');" > js/000-browserSupport.js
fi

if [[ $runstep == 0 && $(./devTools/updateTool.sh -r origin) && `echo $?` > 0 ]]; then
	git reset --hard HEAD -q && git pull -q && gen=1;
fi # stackoverflow.com/a/17192101

if [[ $gen > 0 || $runstep == 1 ]]; then
	echo "Applying WebGL decensor patch."
	sed -i '1,/p.applyPanty = true/ s/p.applyPanty = true/p.applyPanty = false/' src/art/webgl/art.js
	sed -i 's/p.underage = slave.age < 18 || slave.visualAge < 18/p.underage = false/' src/art/webgl/art.js
	sed -i 's#p.height = Math.max(slave.height, 140); // clamp underage#p.height = slave.height;#' src/art/webgl/art.js
	sed -i 's#morphs.push(["physicalAgeYoung", -(Math.max(slave.visualAge, 18)-20)/10]) // clamp underage#morphs.push(["physicalAgeYoung", -(slave.visualAge-20)/10])#' src/art/webgl/art.js
fi

if [[ $gen > 0 || $runstep == 2 ]]; then
	rm bin/*.html > /dev/null 2>&1 ; ./compile.sh -q -m -f preCompiled-FC-`git log -1 --format=%cd --date=format:%Y-%m-%d-%H-%M`-$(git rev-parse --short HEAD)
fi

mega-login $email $password > /dev/null 2>&1
if [[ $gen > 0 || $runstep == 3 ]]; then mega-put -c bin/*.html $remote; fi
if [[ $gen > 0 || $runstep == 4 ]]; then
		if [[ `mega-ls $remote|grep .html|grep -v revert-RA|wc -l` > 2 ]]; then
			mega-rm $remote`mega-ls $remote|grep .html|grep -v revert-RA|head -n 1`
		fi
fi


code:
# Build and run an FC Docker container
# $ docker build -t fc .
# $ docker run -d --rm -p 8080:80 fc
# Access with brownser at http://localhost:8080

# Compile
FROM ubuntu
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=America/New_York
RUN apt update
RUN apt install -y git
WORKDIR /opt
RUN git clone https://gitgud.io/pregmodfan/fc-pregmod.git
WORKDIR /opt/fc-pregmod
RUN chmod +x devTools/tweeGo/tweego_nix*
RUN bash ./compile.sh

# Install the 3d renders hosted on mega.nz
RUN apt install -y megatools unzip
WORKDIR /tmp
RUN megadl --path=renders.zip https://mega.nz/#!upoAlBaZ!EbZ5wCixxZxBhMN_ireJTXt0SIPOywO2JW9XzTIPhe0
RUN unzip renders.zip
RUN cp -r 'Free Cities v 0.10.4 renders'/resources /opt/fc-pregmod/bin/

# Serve with nginx
FROM nginx
COPY --from=0 /opt/fc-pregmod/bin /usr/share/nginx/html
WORKDIR /usr/share/nginx/html
RUN mv FC_pregmod.html index.html

path: docker/fc-docker/Dockerfile


code:
# Create docker and upload to gitgud.io
# docker build -t gitgud.io:5050/user/project:megacmd .
# docker login gitgud.io
# docker push gitgud.io:5050/user/project:megacmd
#
# Debug build process
# docker build --no-cache --progress=plain -t megacmd .

# Create debian as base image
FROM debian

# Update
RUN apt-get -y update
RUN apt-get -y upgrade

# Install zip
RUN apt-get -y install zip

# mega cmd
RUN apt-get -y install wget
RUN wget https://mega.nz/linux/repo/Debian_11/amd64/megacmd-Debian_11_amd64.deb
RUN apt-get -y install ./megacmd-Debian_11_amd64.deb

path: docker/megacmd/Dockerfile


code:
# fc-pregmod:build
# Compiling needs node and Go installed

# Start with the base golang image, cause installing go is way to complicated
FROM golang

RUN curl -sL https://deb.nodesource.com/setup_19.x | bash
RUN apt-get install -y nodejs

path: docker/stage-build/Dockerfile


code:
/**
 * @file defines the gulp tasks that we use to build FC
 */

/* globals process */
// cSpell: words embedsourcemaps, sourcemapsincludecontent, pmodversion, fnames, nothrow, pathcmp, autoprefix

import yargs from "yargs";
import {hideBin} from "yargs/helpers";
import terser from "gulp-terser";
import cleanCSS from "gulp-clean-css";
import autoprefixer from "autoprefixer";
import gulp from "gulp";
import concat from "gulp-concat";
import gulpIgnore from "gulp-ignore";
import log from "fancy-log-levels";
import noop from "gulp-noop";
import postcss from "gulp-postcss";
import shell from "gulp-shell";
import sort from "gulp-sort";
import sourcemaps from "gulp-sourcemaps";
import stripCssJSComments from "gulp-strip-comments";
import {execSync} from "child_process";
import which from "which";
import jetpack from "fs-jetpack";
import path from "path";
import os from "os";
import {fileURLToPath} from "url";

// run `npx gulp` to display help
const args = yargs(hideBin(process.argv))
	.showHelpOnFail(true)
	.option('verbosity', {
		alias: 'v',
		type: 'number',
		description: 'Logging verbosity level, 1-6',
		default: 6,
	})
	.option('release', {
		alias: 'r',
		type: 'boolean',
		description: 'Build source maps if not supplied',
		default: false,
	})
	.options('ci', {
		type: 'boolean',
		description: 'Assumes gitlab CI environment',
		default: false,
	})
	.option('minify', {
		alias: 'm',
		type: 'boolean',
		description: 'Makes builds smaller at the cost of readability and compiling time',
		default: false,
	})
	.options('embedsourcemaps', {
		type: 'boolean',
		description: 'Embed source maps into final html file',
		default: true,
	})
	.options('sourcemapsincludecontent', {
		type: 'boolean',
		description: 'Add original js and css code to the source maps. Loses file names and paths!',
		default: false,
	})
	.options('debug', {
		type: 'boolean',
		description: 'Add files that match the *.debug.* pattern, otherwise ignore them',
		default: false,
	})
	.options('hash', {
		type: 'boolean',
		description: 'Adds the git commit hash to the output filename',
		default: false,
	})
	.options('epoch', {
		type: 'boolean',
		description: 'Adds the current epoch time to the output filename',
		default: false,
	})
	.options('pmodversion', {
		type: 'boolean',
		description: 'Adds the current pregmod version number to the output filename',
		default: false,
	})
	// commands should exist as exported gulp tasks
	.command('html', "Build FC")
	.command('themes', "Build themes")
	.command('mods', "Build mods")
	.command('all', "Build FC, themes, and mods")
	.demandCommand()
	.parse();

// load json without using "...assert {type: "json"}" which is still in to proposal stage
// https://github.com/tc39/proposal-json-modules

const cfg = jetpack.read(fileURLToPath(new URL('./build.config.json', import.meta.url)), "json");

/**
 * Options used to minify js code using terser
 * @type {import("terser").MinifyOptions}
 */
const terserMinifyConfig = {
	// https://www.npmjs.com/package/terser#minify-options
	mangle: true,
	// eslint-disable-next-line camelcase
	keep_classnames: true,
	// eslint-disable-next-line camelcase
	keep_fnames: true,
};
/**
 * Options used to minify css code using CleanCSS
 */
const cleanCssMinifyConfig = {
	// https://www.npmjs.com/package/clean-css#constructor-options
};
/** Filename for the temporary output. Tweego will write here and then it will be moved into the output dir */
const htmlOut = "tmp.html";

// set log verbosity basing on the command line argument
log(args.verbosity);

// -------------- Helper functions -----------------------

/**
 * Locates a tweego executable.
 * Looks in the host $PATH, otherwise uses one of the bundled executables in devTools/tweeGo
 * @returns {string} Returns the path to the tweego executable
 */
function tweeCompilerExecutable() {
	const systemTweego = which.sync("tweego", {nothrow: true});

	if (systemTweego) {
		log.info("Found system tweego at ", systemTweego);
		return systemTweego;
	}

	const archSuffix = os.arch() === "x64" ? "64" : "86";
	const platformSuffix = {
		"Darwin": "osx",
		"Linux": "nix",
		"Windows_NT": "win"
	}[os.type()];
	const extension = os.type() === "Windows_NT" ? ".exe" : "";
	const res = path.join(".", "devTools", "tweeGo", `tweego_${platformSuffix}${archSuffix}${extension}`);

	log.info("Using bundled tweego at ", res);
	return res;
}

/**
 * Composes tweego invocation command
 *
 * Combines paths to tweego and options defined in the build.config.json file to
 * return a full tweego launch command, which will combine all story elements, pick up modules,
 * and generate an HTML file in the intermediate directory
 * @returns {string} Full tweego command string
 */
function tweeCompileCommand() {
	const sources = [path.join(cfg.dirs.intermediate, "story")];

	sources.push(...cfg.sources.story.media);

	const modules = [path.join(cfg.dirs.intermediate, "module")];
	const moduleArgs = modules.map(fn => `--module=${fn}`);

	return `"${tweeCompilerExecutable()}" --head=${cfg.sources.head} -o ${path.join(cfg.dirs.intermediate, htmlOut)} ${moduleArgs.join(" ")} ${sources.join(" ")}`;
}

/**
 * gulp-sort uses String.localeCompare() by default, which may be case-insensitive,
 * while we require case -sensitive sorting for sources
 * @param {Vinyl} a
 * @param {Vinyl} b
 * @returns {number}
 */
function pathcmp(a, b) {
	return (a.path < b.path ? -1 : (a.path > b.path ? 1 : 0));
}

/**
 * Creates a pipeline that sorts and combines files
 * @param {string|string[]} srcGlob Glob(s) to combine
 * @param {string} destDir destination directory to save to
 * @param {string} destFileName the filename to save as
 * @returns {NodeJS.ReadWriteStream}
 */
function concatFiles(srcGlob, destDir, destFileName) {
	return gulp.src(srcGlob)
		.pipe(args.debug
			? noop()
			: gulpIgnore.exclude("*.debug.*")
		)
		.pipe(sort(pathcmp))
		.pipe(concat(destFileName))
		.pipe(gulp.dest(destDir));
}

/**
 * Creates a pipeline for processing JS scripts
 *
 * The pipeline collects sources, sorts them, concatenates and
 * saves in the destination dir. If no "release" command line switch was
 * supplied, sourcemaps will be written for the files
 * @param {string} srcGlob Glob to process
 * @param {string} destDir destination directory to save to
 * @param {string} destFileName the filename to save as
 * @returns {NodeJS.ReadWriteStream}
 */
function processScripts(srcGlob, destDir, destFileName) {
	const addSourcemaps = !args.release;
	const prefix = path.relative(destDir, srcGlob.substr(0, srcGlob.indexOf("*")));

	return gulp.src(srcGlob)
		.pipe(args.debug
			? noop()
			: gulpIgnore.exclude("*.debug.*")
		)
		.pipe(sort(pathcmp))
		.pipe(addSourcemaps ? sourcemaps.init() : noop())
		.pipe(concat(destFileName))
		.pipe(args.minify
			// @ts-expect-error This is correct
			? terser(terserMinifyConfig)
			: noop())
		.pipe(addSourcemaps
			? sourcemaps.write(args.embedsourcemaps ? undefined : ".", {
				includeContent: args.sourcemapsincludecontent,
				sourceRoot: prefix,
				sourceMappingURLPrefix: path.relative(cfg.dirs.output, destDir)
			})
			: noop())
		.pipe(gulp.dest(destDir));
}

/**
 * Creates a pipeline for processing CSS stylesheets
 *
 * The pipeline collects sources, sorts them, concatenates, pass through
 * an autoprefixer and saves in the destination dir. If no "release" command
 * line switch was supplied, sourcemaps will be written for the files
 * @param {string} srcGlob Glob to process
 * @param {string} destDir destination directory to save to
 * @param {string} destFileName the filename to save as
 * @returns {NodeJS.ReadWriteStream}
 */
function processStylesheets(srcGlob, destDir, destFileName) {
	const addSourcemaps = !args.release;
	const prefix = path.relative(destDir, srcGlob.substr(0, srcGlob.indexOf("*")));

	return gulp.src(srcGlob)
		.pipe(args.debug
			? noop()
			: gulpIgnore.exclude("*.debug.*")
		)
		.pipe(sort(pathcmp))
		.pipe(addSourcemaps ? sourcemaps.init() : noop())
		.pipe(concat(destFileName))
		.pipe(cfg.options.css.autoprefix
			? postcss([autoprefixer({overrideBrowserslist: ["last 2 versions"]})])
			: noop())
		// minify css using CleanCSS
		.pipe(args.minify
			? cleanCSS(cleanCssMinifyConfig)
			: noop())
		.pipe(addSourcemaps
			? sourcemaps.write(args.embedsourcemaps ? undefined : ".", {
				includeContent: args.sourcemapsincludecontent,
				sourceRoot: prefix,
				sourceMappingURLPrefix: path.relative(cfg.dirs.output, destDir)
			})
			: noop())
		.pipe(gulp.dest(destDir));
}

/**
 * Creates tasks for processing sources with provided function
 *
 * This function is a workaround. Gulp can handle multiple globs at time, but
 * for writing sourcemaps we have to process them one by one, because we detect
 * where to write source maps from each glob pattern in order to make them accessible
 * from the compiled HTML
 * @param {("module-js"|"module-css"|"story-js"|"story-css"|"story-twee"|"story-media")} name
 * @param {Function} processorFunc
 * @param {string[]} globs
 * @param {string} destDir destination directory to save to
 * @param {string} destFileName filename to save as
 * @param  {...any} args extra args to pass to processorFunc
 * @returns {import("gulp").TaskFunction}
 */
function processSrc(name, processorFunc, globs, destDir, destFileName, ...args) {
	/** @type {import("gulp").TaskFunction[]} */
	const tasks = [];

	if (!Array.isArray(globs) || globs.length === 1) {
		const src = Array.isArray(globs) ? globs[0] : globs;

		tasks.push(() => processorFunc(src, destDir, destFileName, args));
		tasks[tasks.length - 1].displayName = "process-" + name;
	} else { // many globs
		const ext = path.extname(destFileName);
		const bn = path.basename(destFileName, ext);

		for (let i = 0; i < globs.length; ++i) {
			tasks.push(() => processorFunc(globs[i], destDir, `${bn}-${i}${ext}`, args));
			tasks[tasks.length - 1].displayName = `process-${name}-${i}`;
		}
	}

	const res = gulp.parallel(...tasks);

	res.displayName = name;
	return res;
}

function gitExecutableExists() {
	return which.sync('git', {nothrow: true}) !== null;
}

/**
 * Returns true if the working directory is a Git repository
 * @returns {boolean}
 */
function isGitCheckout() {
	return jetpack.exists(".git") === "dir";
}

let gitHash = "UNKNOWN";

/**
 * Invokes git and writes the hash of the head commit to the file, specified in the 'gitVersionFile' config property
 * @param {Function} cb callback function
 */
function injectGitCommit(cb) {
	// check if we are in CI mode, if yes, just read out the hash from environment variables
	gitHash = args.ci
		? execSync("echo $CI_COMMIT_SHORT_SHA").toString().trim()
		: execSync("git rev-parse --short HEAD").toString().trim();

	if (gitHash === "$CI_COMMIT_SHORT_SHA" || (gitHash === "" && args.ci === true)) {
		// This should only fire if the CI flag is used out of GitLab CI
		// If it fires in GitLab CI then $CI_COMMIT_SHORT_SHA is no longer valid or something has gone terribly wrong.
		log.warn(`git hash === "${gitHash}"!`);
		log.warn("Are you in a GitLab CI environment?");
		log.warn("If not please remove the --ci flag from the commands arguments to remove this message.");
		log.warn("Sleeping for 10 seconds and then setting ci to false");
		// sleep for 10 second
		Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10000);
		args.ci = false;
		return injectGitCommit(cb);
	} else if (gitHash === "") {
		throw new Error("Failed to get git hash!");
	}

	log.info("current git hash:", gitHash);
	jetpack.write(cfg.gitVersionFile, `App.Version.commitHash = '${gitHash}';\n`, {atomic: true});
	cb();
}

/**
 * Ensures the file with the git commit hash does not exists
 * @param {Function} cb callback function
 */
function cleanupGit(cb) {
	jetpack.remove(cfg.gitVersionFile);
	cb();
}

/**
 * Copies a file/directory to a destination
 * @param {string} source
 * @param {string} destination
 * @param {boolean} removeExisting
 * @param {Function} callback
 */
function copy(source, destination, removeExisting, callback) {
	// if not debug mode filter out *.debug.* files
	let matching = "!*.debug.*";

	if (args.debug === true) {
		matching = "*";
	}

	if (removeExisting) {
		jetpack.remove(destination);
	}

	jetpack.copy(source, destination, {overwrite: true, matching: matching});
	callback();
}

// --------------- build tasks definitions -----------------
/*
The main build process, which produces the assembled HTML file, consists of several stages.
The game sources, which are .twee, .js, and .css files, belong to one of the two categories:
they are either part of the story in Tweego terms, or modules. These categories are called
"components" below. For each component files of the same type are processed in the same way
and results for each component/file type pair are concatenated file in the intermediate builds
directory and a source map for that file.
When all intermediate files are ready, tweego picks them up and assembles the HTML file.
*/

// Create task to execute tweego
gulp.task("compileStory", shell.task(tweeCompileCommand(), {
	env: {...process.env, ...cfg.options.twee.environment},
	verbose: args.verbosity >= 3
}));

/**
 * Creates tasks for preparing intermediate files for a component
 * @param {"story"|"module"} name "story" or "module"
 * @returns {import("gulp").TaskFunction}
 */
function prepareComponent(name) {
	const processors = {
		"css": {
			func: processStylesheets,
			output: "-styles.css"
		},
		"js": {
			func: processScripts,
			output: "-script.js"
		},
		"twee": {
			func: concatFiles,
			output: "-story.twee"
		},
		"media": {
			func: null
		}
	};
	const c = cfg.sources[name];
	const outDir = path.join(cfg.dirs.intermediate, name);
	const subTasks = [];

	/** @type {"css"|"js"|"twee"|"media"} */
	let srcType;

	for (srcType in c) {
		const proc = processors[srcType];

		if (proc.func) {
			// @ts-expect-error ${name}-${srcType} is valid
			subTasks.push(processSrc(`${name}-${srcType}`, proc.func, c[srcType], outDir, `${name}${proc.output}`, cfg.options[srcType]));
		}
	}

	const r = gulp.parallel(subTasks);

	r.displayName = "prepare-" + name;
	return r;
}

/**
 *  Creates a task for compiling a theme
 * @param {string} themeName theme directory name
 * @returns {string} task name
 */
function makeThemeCompilationTask(themeName) {
	// make sure it's a name, not a path
	if (themeName.replace(/\/+$/, "").replace(/\\+$/, "").includes(path.sep)) {
		// only keep the last path component
		themeName = themeName.split(path.sep).pop();
	}

	const taskName = `make-theme-${themeName}`;

	gulp.task(taskName, function() {
		return concatFiles(`${cfg.sources.themes}/${themeName}/**/*.css`, cfg.dirs.output, `${themeName}.css`);
	});
	return taskName;
}

/**
 *  Creates a task for compiling a mod
 * @param {string} modName mod directory name
 * @returns {string} task name
 */
function makeModCompilationTask(modName) {
	// make sure it's a name, not a path
	if (modName.replace(/\/+$/, "").replace(/\\+$/, "").includes(path.sep)) {
		// only keep the last path component
		modName = modName.split(path.sep).pop();
	}

	const taskName = `make-mod-${modName}`;

	gulp.task(taskName, function(cb) {
		return copy(`${cfg.sources.mods}/${modName}`, `${cfg.dirs.modOutput}/${modName}`, true, cb);
	});
	return taskName;
}

/**
 * Moves compiled HTML file from the intermediate location to the final output
 * @param {Function} cb callback function
 */
function moveHTML(cb) {
	if (jetpack.exists(path.join(cfg.dirs.intermediate, htmlOut)) === "file") {
		let finalPath = path.join(cfg.dirs.output, cfg.output);
		let extraString = "";

		if (args.epoch) {
			extraString += "." + String(Math.floor(Date.now() / 1000));
		}

		if (args.pmodversion) {
			// open ./src/002-config/fc-version.js
			jetpack.read("./src/002-config/fc-version.js").split("\n").forEach(line => {
				if (line.trim().includes("pmod: ")) {
					// add everything between first and last " to extraString
					extraString += "." + line.split("\"")[1].split("\"")[0];
				}
			});
		}

		if (args.hash) {
			extraString += "." + gitHash;
		}

		finalPath = finalPath.replace("[extras]", extraString);

		jetpack.move(path.join(cfg.dirs.intermediate, htmlOut), path.resolve(finalPath), {overwrite: true});

		log.info("FC saved to \"" + finalPath + "\"");
		cb();
	}
}

/**
 *  Removes intermediate compilation files if any
 * @param {Function} cb callback function
 */
function removeIntermediateFiles(cb) {
	jetpack.remove(cfg.dirs.intermediate);
	cb();
}

// Creates task to assemble components in the intermediate dir where they are ready for tweego
gulp.task("prepare", gulp.parallel(prepareComponent("module"), prepareComponent("story")));

// Creates theme build task for each subdirectory in the 'themes' dir
const themeTasks = jetpack.find(cfg.sources.themes, {files: false, directories: true, recursive: false})
	.map(entry => makeThemeCompilationTask(entry));

// create cfg.sources.mods directory if it doesn't exist
jetpack.dir(cfg.sources.mods);

// Creates mod build task for each subdirectory in the 'mods' dir
const modTasks = jetpack.find(cfg.sources.mods, {files: false, directories: true, recursive: false})
	.map(entry => makeModCompilationTask(entry));

// If modTasks is empty, create a task that does nothing
if (modTasks.length === 0) {
	gulp.task("make-mod-empty-folder", function(cb) {
		return cb();
	});

	modTasks.push("make-mod-empty-folder");
}

// Create the main build and clean tasks, which include writing Git commit hash if we are working in a Git repo
let cleanOp = noop();

if (isGitCheckout() && gitExecutableExists()) {
	cleanOp = gulp.parallel(cleanupGit, removeIntermediateFiles);

	gulp.task("buildHTML", gulp.series(cleanOp, injectGitCommit, "prepare", "compileStory", cleanupGit));
} else if (args.ci) {
	// CI environment is already clean

	gulp.task("buildHTML", gulp.series(injectGitCommit, "prepare", "compileStory"));
} else {
	if (isGitCheckout()) {
		log.info("git executable not found.");
	}
	cleanOp = gulp.parallel(removeIntermediateFiles);
	gulp.task("buildHTML", gulp.series(cleanOp, "prepare", "compileStory"));
}

export const clean = cleanOp;

// Create user-invocable targets for building HTML and themes
export const html = gulp.series("buildHTML", moveHTML);
export const themes = gulp.parallel(themeTasks);
export const mods = gulp.parallel(modTasks);

// Convenient shortcut to build everything (HTML, themes, and mods)
export const all = gulp.parallel(html, themes, mods);

// legacy tasks

gulp.task("twineCSS", function() {
	return concatFiles([...cfg.sources.module.css, ...cfg.sources.story.css], "devNotes", "twine CSS.txt");
});

gulp.task("twineJS", function() {
	return gulp.src([...cfg.sources.module.js, ...cfg.sources.story.js, "!src/art/assistantArt.js"])
		.pipe(args.debug
			? noop()
			: gulpIgnore.exclude("*.debug.*")
		)
		.pipe(stripCssJSComments({trim: true}))
		.pipe(sort(pathcmp))
		.pipe(concat("twine JS.txt"))
		.pipe(gulp.dest("devNotes"));
});

export const twine = gulp.parallel("twineCSS", "twineJS");


code:
/* eslint-disable no-var */
// @ts-ignore
"use strict";

var App = {}; // eslint-disable-line no-redeclare

// When adding namespace declarations, please consider needs of those using VSCode:
// when you declare App.A{ A1:{}, A2:{} }, VSCode considers A, A1, and A2 to be
// sealed namespaces and no new members are added to them with nested declaration later on.
// This breaks code completion completely. Please instead declare them as:
// App.A = {}; App.A.A1 = {}; App.A.A2 = {}. Thank you.

// Also, such declaration basically required only for namespaces that span more than a single file.

App.Arcology = {};
App.Arcology.Cell = {};
App.Art = {};
App.Art.GenAI = {};
App.Budget = {};
App.Corporate = {};
App.Corporate.Division = {};
App.Corporate.Shared = {};
App.Data = {};
App.Data.Arcology = {};
App.Data.Art = {};
App.Data.FCTV = {};
App.Data.HeroSlaves = {};
App.Data.Medicine = {};
App.Data.Policies = {};
App.Data.Policies.Selection = {};
App.Data.SecExp = {};
App.Data.Slave = {};
App.Data.Weather = {};
App.Debug = {};
App.Desc = {};
App.Desc.Player = {};
App.Encyclopedia = {};
App.EndWeek = {};
App.EndWeek.Player = {};
App.EndWeek.Shared = {};
App.Entity = {};
App.Entity.Utils = {};
App.Events = {};
App.Facilities = {};
App.Facilities.Arcade = {};
App.Facilities.Brothel = {};
App.Facilities.Cellblock = {};
App.Facilities.Clinic = {};
App.Facilities.Club = {};
App.Facilities.Dairy = {};
App.Facilities.Farmyard = {};
App.Facilities.HGSuite = {};
App.Facilities.Incubator = {};
App.Facilities.MasterSuite = {};
App.Facilities.Nursery = {};
App.Facilities.Pit = {};
App.Facilities.Pit.Fights = {};
App.Facilities.Schoolroom = {};
App.Facilities.ServantsQuarters = {};
App.Facilities.Spa = {};
App.Facilities.TransportHub = {};
App.Interact = {};
App.Interact.Sale = {};
App.Intro = {};
App.MainView = {};
App.Markets = {};
App.Medicine = {};
App.Medicine.Modification = {};
App.Medicine.Modification.Brands = {};
App.Medicine.Modification.Select = {};
App.Medicine.OrganFarm = {};
App.Medicine.Salon = {};
App.Medicine.Surgery = {};
App.Medicine.Surgery.Procedures = {};
App.Medicine.Surgery.Reactions = {};
App.Mods = {};
App.Mods.DinnerParty = {};
App.Mods.Drugs = {};
App.Mods.events = {
	nonRandom: [],
	random: {
		individual: [],
		nonindividual: [],
		recruitment: [],
	},
	recruit: [],
};
App.Mods.SecExp = {};
App.Mods.SF = {};
App.Neighbor = {};
App.PersonalAttention = {};
App.Porn = {};
App.RA = {};
App.RA.Activation = {};
App.Ratings = {};
App.SlaveAssignment = {};
App.StartingGirls = {};
App.Status = {};
App.Status.storyReady = false;
App.UI = {};
App.UI.Cheat = {};
App.UI.DOM = {};
App.UI.DOM.Widgets = {};
App.UI.SlaveInteract = {};
App.UI.View = {};
App.Update = {};
App.Utils = {};
App.Utils.Math = {};


code:
App.Data.Arcology.Terrain = ["marine", "marine", "oceanic", "ravine", "rural", "rural", "rural", "urban", "urban"];
App.Data.Arcology.Continents = ["Africa", "Asia", "Asia", "Australia", "Western Europe", "Southern Europe", "Central Europe", "Eastern Europe", "Scandinavia", "Japan", "North America", "North America", "South America", "the Middle East"];


code:
App.Data.Assistant = {};
/**
 * Assistant appearances mapped to the FS that love seeing her look like she do.
 * @type {Map<FC.FutureSociety, Array<assistantAppearance>>}
 */
App.Data.Assistant.appearanceForFS = new Map([
	["FSSupremacist", ["amazon", "monstergirl", "succubus"]],
	["FSSubjugationist", ["amazon", "businesswoman", "imp"]],
	["FSGenderRadicalist", ["incubus", "monstergirl", "shemale", "succubus", "witch"]],
	["FSGenderFundamentalist", ["angel", "cherub", "fairy", "goddess", "hypergoddess", "loli", "preggololi", "pregnant fairy", "schoolgirl", "succubus", "witch"]],
	["FSDegradationist", ["businesswoman", "imp", "incubus", "monstergirl", "preggololi", "succubus"]],
	["FSPaternalist", ["angel", "cherub", "fairy", "goddess", "hypergoddess", "loli", "preggololi", "pregnant fairy", "schoolgirl"]],
	["FSBodyPurist", ["amazon", "angel", "fairy", "goddess", "incubus", "loli", "pregnant fairy", "succubus", "witch"]],
	["FSTransformationFetishist", ["businesswoman", "ERROR_1606_APPEARANCE_FILE_CORRUPT", "incubus", "shemale", "succubus", "witch"]],
	["FSYouthPreferentialist", ["angel", "cherub", "imp", "loli", "preggololi", "schoolgirl", "shemale", "succubus", "witch"]],
	["FSMaturityPreferentialist", ["angel", "businesswoman", "goddess", "incubus", "succubus", "witch"]],
	["FSSlimnessEnthusiast", ["cherub", "imp", "loli", "schoolgirl", "shemale", "succubus", "witch"]],
	["FSAssetExpansionist", ["businesswoman", "hypergoddess", "incubus", "shemale", "succubus", "witch"]],
	["FSPastoralist", ["cowgirl", "goddess", "hypergoddess", "incubus", "shemale", "succubus", "witch"]],
	["FSPhysicalIdealist", ["amazon", "incubus", "shemale", "succubus", "witch"]],
	["FSHedonisticDecadence", ["goddess", "hypergoddess", "imp", "incubus", "preggololi", "succubus", "witch"]],
	["FSChattelReligionist", ["angel", "cherub", "goddess", "imp", "incubus", "monstergirl", "succubus", "witch"]],
	["FSNull", []],
	["FSRomanRevivalist", ["amazon", "businesswoman", "incubus", "succubus"]],
	["FSNeoImperialist", ["amazon", "businesswoman", "incubus", "shemale", "angel"]],
	["FSEgyptianRevivalist", ["goddess", "incubus", "monstergirl", "succubus"]],
	["FSEdoRevivalist", ["amazon", "incubus", "kitsunegirl", "loli", "monstergirl", "succubus"]],
	["FSArabianRevivalist", ["businesswoman", "incubus", "schoolgirl", "succubus"]],
	["FSChineseRevivalist", ["incubus", "monstergirl", "schoolgirl", "succubus"]],
	["FSAztecRevivalist", ["amazon", "businesswoman", "incubus", "succubus"]],
	["FSAntebellumRevivalist", ["angel", "businesswoman", "goddess", "monstergirl", "succubus", "witch"]],
	["FSRepopulationFocus", ["goddess", "hypergoddess", "preggololi", "pregnant fairy", "succubus", "witch"]],
	["FSRestart", ["angel", "businesswoman", "goddess", "incubus", "loli", "schoolgirl", "succubus", "witch"]],
	["FSIntellectualDependency", ["shemale", "succubus", "witch"]],
	["FSSlaveProfessionalism", ["angel", "businesswoman", "incubus", "goddess", "schoolgirl", "succubus"]],
	["FSPetiteAdmiration", ["cherub", "fairy", "imp", "incubus", "loli", "preggololi", "pregnant fairy", "schoolgirl", "succubus", "witch"]],
	["FSStatuesqueGlorification", ["amazon", "goddess", "incubus", "succubus", "witch"]]
]);

/**
 * @type {Map<assistantAppearance, {readonly requirements: boolean}>}
 */
App.Data.Assistant.appearances = new Map([
	["normal", {get requirements() { return true; }}],
	["monstergirl", {get requirements() { return (V.seeDicks > 0); }}],
	["shemale", {get requirements() { return V.seeDicks > 0; }}],
	["amazon", {get requirements() { return true; }}],
	["businesswoman", {get requirements() { return true; }}],
	["goddess", {get requirements() { return (V.seePreg !== 0); }}],
	["hypergoddess", {get requirements() { return (V.seePreg !== 0 && V.seeHyperPreg !== 0); }}],
	["schoolgirl", {get requirements() { return true; }}],
	["loli", {get requirements() { return (V.minimumSlaveAge < 13); }}],
	["preggololi", {get requirements() { return (V.seePreg !== 0 && V.minimumSlaveAge < 13); }}],
	["fairy", {get requirements() { return true; }}],
	["pregnant fairy", {get requirements() { return (V.seePreg !== 0); }}],

	// Extra
	["slimegirl", {get requirements() { return (V.assistant.Extra1 === 1); }}],
	["cowgirl", {get requirements() { return (V.assistant.Extra1 === 1); }}],
	["harpygirl", {get requirements() { return (V.assistant.Extra1 === 1); }}],
	["kitsunegirl", {get requirements() { return (V.assistant.Extra1 === 1); }}],
	["lamiagirl", {get requirements() { return (V.assistant.Extra1 === 1); }}],
	["spidergirl", {get requirements() { return (V.assistant.Extra1 === 1); }}],

	// Extra 2
	["angel", {get requirements() { return (V.assistant.Extra2 === 1); }}],
	["cherub", {get requirements() { return (V.assistant.Extra2 === 1); }}],
	["incubus", {get requirements() { return (V.assistant.Extra2 === 1 && V.seeDicks > 0); }}],
	["succubus", {get requirements() { return (V.assistant.Extra2 === 1); }}],
	["imp", {get requirements() { return (V.assistant.Extra2 === 1); }}],
	["witch", {get requirements() { return (V.assistant.Extra2 === 1); }}],
	["ERROR_1606_APPEARANCE_FILE_CORRUPT", {get requirements() { return (V.assistant.Extra2 === 1); }}],
]);


code:
/**
 * @typedef {object} itemFS
 * @property {FC.FutureSociety} [unlocks] Automatically unlocked with this FS.
 * @property {Set<FC.FutureSociety>} [loves] FS loves to see this outfit.
 * @property {Set<FC.FutureSociety>} [tolerates] FS tolerates this outfit.
 * @property {Set<FC.FutureSociety>} [hates] FS hates to see this outfit.
 */

/**
 * @typedef {object} clothingDescription
 * @property {function(App.Entity.SlaveState):string} [summary] General description of the outfit, tailored to the slave, but not hyper focused on a body part like boobs or butt.
 * @property {function(App.Entity.SlaveState):string} [upperFace]
 * @property {function(App.Entity.SlaveState):string} [hStyle]
 * @property {function(App.Entity.SlaveState):string} [earPiercing]
 * @property {function(App.Entity.SlaveState):string} [corsetPiercing]
 * @property {function(App.Entity.SlaveState):string} [bellyImplant]
 * @property {function(App.Entity.SlaveState):string} [belly]
 * @property {function(App.Entity.SlaveState):string} [inflation]
 * @property {function(App.Entity.SlaveState):string} [crotch]
 * @property {function(App.Entity.SlaveState):string} [butt]
 * @property {function(App.Entity.SlaveState):string} [buttplug]
 * @property {function(App.Entity.SlaveState):string} [clothingCorset]
 * @property {function(App.Entity.SlaveState):string} [boobs]
 */

/**
 * @typedef {object} clothes
 * @property {string} name
 * @property {0|1|2|3|4} exposure 0: Modest, 1: Acceptable, 2: Slutty, 3: Humiliating (exposes genitals), 4: Might as well be nude
 * @property {itemFS} [fs]
 * @property {boolean} [requirements]
 * @property {boolean} [harsh]
 * @property {boolean} [topless]
 * @property {boolean} [fuckdoll]
 * @property {string} [note]
 * @property {clothingDescription} [desc]
 */

/** @type {Map.<FC.Clothes, clothes>} The string here will be what is applied to the relevant slave property. Slave.clothes = "a bunny outfit", not "Bunny outfit".*/
App.Data.clothes = (new Map())
	.set("a Fuckdoll suit",
		{
			name: "Fuckdoll suit",
			exposure: 3,
			fuckdoll: true
		}
	)
	.set("attractive lingerie for a pregnant woman",
		{
			name: "Maternity lingerie",
			fs: {
				unlocks: "FSRepopulationFocus",
				loves: new Set(["FSIntellectualDependency", "FSRepopulationFocus"]),
			},
			get requirements() { return V.boughtItem.clothing.maternityLingerie === 1; },
			exposure: 2
		}
	)
	.set("a bunny outfit",
		{
			name: "Bunny outfit",
			fs: {
				unlocks: "FSGenderFundamentalist",
				loves: new Set(["FSGenderFundamentalist"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.bunny === 1; },
			exposure: 1
		}
	)
	.set("body oil",
		{
			name: "Body oil",
			fs: {
				unlocks: "FSPhysicalIdealist",
				loves: new Set(["FSPhysicalIdealist"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.oil === 1; },
			exposure: 4
		}
	)
	.set("a chattel habit",
		{
			name: "Chattel habit",
			fs: {
				unlocks: "FSChattelReligionist",
				loves: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.habit === 1; },
			exposure: 3
		}
	)
	.set("conservative clothing",
		{
			name: "Conservative clothing",
			fs: {
				unlocks: "FSPaternalist",
				loves: new Set(["FSPaternalist"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.conservative === 1; },
			exposure: 0
		}
	)
	.set("harem gauze",
		{
			name: "Harem gauze",
			fs: {
				unlocks: "FSArabianRevivalist",
				loves: new Set(["FSArabianRevivalist"]),
			},
			get requirements() { return V.boughtItem.clothing.harem === 1; },
			exposure: 1
		}
	)
	.set("a huipil",
		{
			name: "Huipil",
			fs: {
				unlocks: "FSAztecRevivalist",
				loves: new Set(["FSAztecRevivalist"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.huipil === 1; },
			exposure: 0
		}
	)
	.set("a kimono",
		{
			name: "Kimono",
			fs: {
				unlocks: "FSEdoRevivalist",
				loves: new Set(["FSEdoRevivalist"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return (V.boughtItem.clothing.kimono === 1 || V.continent === "Japan"); },
			exposure: 0
		}
	)
	.set("a maternity dress",
		{
			name: "Maternity dress",
			fs: {
				unlocks: "FSRepopulationFocus",
				loves: new Set(["FSRepopulationFocus"]),
			},
			get requirements() { return V.boughtItem.clothing.maternityDress === 1; },
			exposure: 0,
		}
	)
	.set("a slutty qipao",
		{
			name: "Qipao (slutty)",
			fs: {
				unlocks: "FSChineseRevivalist",
				loves: new Set(["FSChineseRevivalist"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.qipao === 1; },
			exposure: 2
		}
	)
	.set("a long qipao",
		{
			name: "Qipao (long)",
			fs: {
				unlocks: "FSChineseRevivalist",
				loves: new Set(["FSChineseRevivalist"]),
			},
			get requirements() { return V.boughtItem.clothing.cultural === 1; },
			exposure: 0
		}
	)
	.set("Imperial Plate",
		{
			name: "Imperial Plate",
			fs: {
				unlocks: "FSNeoImperialist",
				loves: new Set(["FSNeoImperialist"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.imperialarmor === 1; },
			exposure: 0
		}
	)
	.set("a tight Imperial bodysuit",
		{
			name: "Imperial Bodysuit",
			fs: {
				unlocks: "FSNeoImperialist",
				loves: new Set(["FSNeoImperialist"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.imperialsuit === 1; },
			exposure: 2
		}
	)
	.set("stretch pants and a crop-top",
		{
			name: "Stretch pants and a crop-top",
			fs: {unlocks: "FSHedonisticDecadence"},
			get requirements() { return V.boughtItem.clothing.lazyClothes === 1; },
			exposure: 0
		}
	)
	.set("a toga",
		{
			name: "Toga",
			fs: {
				unlocks: "FSRomanRevivalist",
				loves: new Set(["FSRomanRevivalist"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.toga === 1; },
			exposure: 1
		}
	)
	.set("Western clothing",
		{
			name: "Western clothing",
			fs: {
				unlocks: "FSPastoralist",
				loves: new Set(["FSPastoralist"]),
			},
			get requirements() { return V.boughtItem.clothing.western === 1; },
			exposure: 3
		}
	)
	.set("a courtesan dress",
		{
			name: "Courtesan dress",
			fs: {
				unlocks: "FSSlaveProfessionalism",
				loves: new Set(["FSSlaveProfessionalism"]),
			},
			get requirements() { return V.boughtItem.clothing.courtesan === 1; },
			exposure: 1
		}
	)
	.set("a bimbo outfit",
		{
			name: "Bimbo outfit",
			fs: {
				unlocks: "FSIntellectualDependency",
				loves: new Set(["FSIntellectualDependency"]),
			},
			get requirements() { return V.boughtItem.clothing.bimbo === 1; },
			exposure: 2
		}
	)
	.set("petite admi outfit",
		{
			name: "Petite admi outfit",
			fs: {
				unlocks: "FSPetiteAdmiration",
				loves: new Set(["FSPetiteAdmiration"]),
			},
			get requirements() { return V.boughtItem.clothing.petite === 1; },
			exposure: 0
		}
	)
	.set("battlearmor",
		{
			name: "Battlearmor",
			get requirements() { return V.boughtItem.clothing.military === 1; },
			exposure: 0
		}
	)
	.set("a military uniform",
		{
			name: "Military uniform",
			fs: {
				tolerates: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.military === 1; },
			exposure: 0
		}
	)
	.set("a red army uniform",
		{
			name: "Red Army uniform",
			get requirements() { return V.boughtItem.clothing.military === 1; },
			exposure: 0
		}
	)
	.set("battledress",
		{
			name: "Battledress",
			get requirements() { return V.boughtItem.clothing.military === 1; },
			exposure: 0
		}
	)
	.set("a biyelgee costume",
		{
			name: "Biyelgee costume",
			get requirements() { return V.boughtItem.clothing.cultural === 1; },
			exposure: 0
		}
	)
	.set("a dirndl",
		{
			name: "Dirndl",
			get requirements() { return V.boughtItem.clothing.cultural === 1; },
			exposure: 0
		}
	)
	.set("lederhosen",
		{
			name: "Lederhosen",
			get requirements() { return V.boughtItem.clothing.cultural === 1; },
			exposure: 0
		}
	)
	.set("a mounty outfit",
		{
			name: "Mounty outfit",
			get requirements() { return V.boughtItem.clothing.cultural === 1; },
			exposure: 0
		}
	)
	.set("a hanbok",
		{
			name: "Hanbok",
			get requirements() { return V.boughtItem.clothing.cultural === 1; },
			exposure: 0
		}
	)
	.set("a burqa",
		{
			name: "Burqa",
			fs: {
				loves: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.middleEastern === 1 || V.continent === "the Middle East"; },
			exposure: 0
		}
	)
	.set("a niqab and abaya",
		{
			name: "Niqab and abaya",
			fs: {
				loves: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.middleEastern === 1 || V.continent === "the Middle East"; },
			exposure: 0
		}
	)
	.set("a hijab and blouse",
		{
			name: "Hijab and blouse",
			fs: {
				tolerates: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return (V.boughtItem.clothing.conservative === 1 || V.continent === "the Middle East"); },
			exposure: 0
		}
	)
	.set("a burkini",
		{
			name: "Burkini",
			get requirements() { return V.boughtItem.clothing.swimwear === 1 && (V.boughtItem.clothing.swimwear === 1 || V.continent === "the Middle East"); },
			exposure: 1
		}
	)
	.set("a Santa dress",
		{
			name: "Santa dress",
			get requirements() { return V.boughtItem.clothing.costume === 1; },
			exposure: 2
		}
	)
	.set("a klan robe",
		{
			name: "Klan robe",
			get requirements() { return V.boughtItem.clothing.pol === 1; },
			exposure: 0
		}
	)
	.set("a slutty klan robe",
		{
			name: "Slutty klan robe",
			get requirements() { return V.boughtItem.clothing.pol === 1; },
			exposure: 2
		}
	)
	.set("a schutzstaffel uniform",
		{
			name: "Schutzstaffel uniform",
			get requirements() { return V.boughtItem.clothing.pol === 1; },
			exposure: 0
		}
	)
	.set("a slutty schutzstaffel uniform",
		{
			name: "Slutty schutzstaffel uniform",
			get requirements() { return V.boughtItem.clothing.pol === 1; },
			exposure: 2
		}
	)
	.set("nice business attire",
		{
			name: "Nice business attire",
			get requirements() { return V.boughtItem.clothing.career === 1; },
			exposure: 0,
			fs: {
				loves: new Set(["FSMaturityPreferentialist"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
		}
	)
	.set("a nice nurse outfit",
		{
			name: "Nurse (nice)",
			fs: {
				tolerates: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.career === 1; },
			exposure: 0
		}
	)
	.set("a police uniform",
		{
			name: "Police uniform",
			get requirements() { return V.boughtItem.clothing.career === 1; },
			exposure: 0
		}
	)
	.set("a nice maid outfit",
		{
			name: "Maid (nice)",
			get requirements() { return V.boughtItem.clothing.career === 1 || V.PC.career === "servant" || V.PC.career === "handmaiden" || V.PC.career === "child servant"; },
			exposure: 0,
			fs: {
				loves: new Set(["FSMaturityPreferentialist"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
		}
	)
	.set("a ball gown",
		{
			name: "Ballgown",
			get requirements() { return V.boughtItem.clothing.dresses === 1; },
			exposure: 0,
			fs: {
				loves: new Set(["FSAntebellumRevivalist"])
			}
		}
	)
	.set("a gothic lolita dress",
		{
			name: "Gothic lolita dress",
			get requirements() { return V.boughtItem.clothing.dresses === 1; },
			exposure: 0,
			fs: {
				tolerates: new Set(["FSAntebellumRevivalist"])
			}
		}
	)
	.set("a cybersuit",
		{
			name: "Cybersuit",
			get requirements() { return V.boughtItem.clothing.bodysuits === 1; },
			exposure: 0
		}
	)
	.set("a latex catsuit",
		{
			name: "Latex catsuit",
			get requirements() { return V.boughtItem.clothing.bodysuits === 1; },
			exposure: 1
		}
	)
	.set("a button-up shirt and panties",
		{
			name: "Button-up shirt and panties",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 2
		}
	)
	.set("a button-up shirt",
		{
			name: "Button-up shirt",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 3
		}
	)
	.set("cutoffs",
		{
			name: "Cutoffs",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 2
		}
	)
	.set("jeans",
		{
			name: "Jeans",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 2,
			topless: true
		}
	)
	.set("leather pants and a tube top",
		{
			name: "Leather pants and a tube top",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 1
		}
	)
	.set("leather pants",
		{
			name: "Leather pants",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 2,
			topless: true
		}
	)
	.set("an oversized t-shirt",
		{
			name: "Oversized t-shirt",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 3
		}
	)
	.set("a sweater and cutoffs",
		{
			name: "Sweater and cutoffs",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 0
		}
	)
	.set("a sweater and panties",
		{
			name: "Sweater and panties",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 2
		}
	)
	.set("a sweater",
		{
			name: "Sweater",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 3
		}
	)
	.set("a t-shirt and jeans",
		{
			name: "T-shirt and jeans",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 0
		}
	)
	.set("a t-shirt and panties",
		{
			name: "T-shirt and panties",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 2
		}
	)
	.set("a t-shirt",
		{
			name: "T-shirt",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 3
		}
	)
	.set("a tank-top and panties",
		{
			name: "Tank-top and panties",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 2
		}
	)
	.set("a tank-top",
		{
			name: "Tank-top",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 3
		}
	)
	.set("a tube top",
		{
			name: "Tube top",
			get requirements() { return V.boughtItem.clothing.casual === 1; },
			exposure: 3
		}
	)
	.set("boyshorts",
		{
			name: "Boyshorts",
			get requirements() { return V.boughtItem.clothing.underwear === 1; },
			exposure: 2,
			topless: true
		}
	)
	.set("a bra",
		{
			name: "Bra",
			get requirements() { return V.boughtItem.clothing.underwear === 1; },
			exposure: 3
		}
	)
	.set("kitty lingerie",
		{
			name: "Kitty lingerie",
			get requirements() { return V.boughtItem.clothing.underwear === 1; },
			exposure: 2
		}
	)
	.set("panties and pasties",
		{
			name: "Panties and pasties",
			get requirements() { return V.boughtItem.clothing.underwear === 1; },
			exposure: 2
		}
	)
	.set("a skimpy loincloth",
		{
			name: "Skimpy loincloth",
			fs: {
				tolerates: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.underwear === 1; },
			exposure: 3
		}
	)
	.set("a thong",
		{
			name: "Thong",
			get requirements() { return V.boughtItem.clothing.underwear === 1; },
			exposure: 3,
			topless: true
		}
	)
	.set("pasties",
		{
			name: "Pasties",
			get requirements() { return V.boughtItem.clothing.underwear === 1; },
			exposure: 3
		}
	)
	.set("leather pants and pasties",
		{
			name: "Leather pants and pasties",
			get requirements() { return V.boughtItem.clothing.underwear === 1 && V.boughtItem.clothing.casual === 1; },
			exposure: 2,
		}
	)
	.set("a t-shirt and thong",
		{
			name: "T-shirt and thong",
			get requirements() { return V.boughtItem.clothing.underwear === 1 && V.boughtItem.clothing.casual === 1; },
			exposure: 3,
		}
	)
	.set("a tube top and thong",
		{
			name: "Tube top and thong",
			get requirements() { return V.boughtItem.clothing.underwear === 1 && V.boughtItem.clothing.casual === 1; },
			exposure: 3,
		}
	)
	.set("an oversized t-shirt and boyshorts",
		{
			name: "Oversized t-shirt and boyshorts",
			get requirements() { return V.boughtItem.clothing.underwear === 1 && V.boughtItem.clothing.casual === 1; },
			exposure: 0,
		}
	)
	.set("sport shorts and a sports bra",
		{
			name: "Sport shorts and a sports bra",
			get requirements() { return V.boughtItem.clothing.sports === 1; },
			exposure: 1,
		}
	)
	.set("sport shorts",
		{
			name: "Sport shorts",
			get requirements() { return V.boughtItem.clothing.sports === 1; },
			exposure: 2,
			topless: true
		}
	)
	.set("a sports bra",
		{
			name: "Sports bra",
			get requirements() { return V.boughtItem.clothing.sports === 1; },
			exposure: 3
		}
	)
	.set("sport shorts and a t-shirt",
		{
			name: "Sport shorts and a t-shirt",
			get requirements() { return V.boughtItem.clothing.sports === 1 && V.boughtItem.clothing.casual === 1; },
			exposure: 0
		}
	)
	.set("a nice pony outfit",
		{
			name: "Pony outfit (nice)",
			get requirements() { return V.boughtItem.clothing.pony === 1; },
			exposure: 1
		}
	)
	.set("a slutty pony outfit",
		{
			name: "Pony outfit (slutty)",
			get requirements() { return V.boughtItem.clothing.pony === 1; },
			exposure: 2
		}
	)
	.set("a monokini",
		{
			name: "Monokini",
			get requirements() { return V.boughtItem.clothing.swimwear === 1; },
			exposure: 3,
			note: "Boob fetishists enjoy."
		}
	)
	.set("a one-piece swimsuit",
		{
			name: "One-piece swimsuit",
			get requirements() { return V.boughtItem.clothing.swimwear === 1; },
			exposure: 1
		}
	)
	.set("a striped bra",
		{
			name: "Striped bra",
			get requirements() { return V.boughtItem.clothing.pantsu === 1 || V.continent === "Japan"; },
			exposure: 3
		}
	)
	.set("striped panties",
		{
			name: "Striped panties",
			get requirements() { return V.boughtItem.clothing.pantsu === 1 || V.continent === "Japan"; },
			exposure: 2,
			topless: true
		}
	)
	.set("striped underwear",
		{
			name: "Striped underwear",
			get requirements() { return V.boughtItem.clothing.pantsu === 1 || V.continent === "Japan"; },
			exposure: 2,
			topless: true
		}
	)
	.set("a confederate army uniform",
		{
			name: "Confederate Army uniform",
			get requirements() { return V.boughtItem.clothing.antebellum === 1; },
			exposure: 0,
			fs: {
				loves: new Set(["FSAntebellumRevivalist"])
			}
		}
	)
	.set("an evening dress",
		{
			name: "Evening dress",
			get requirements() { return V.boughtItem.clothing.antebellum === 1; },
			exposure: 1,
			fs: {
				loves: new Set(["FSAntebellumRevivalist"])
			}
		}
	)

	// "Normal" things:
	.set("an apron",
		{
			name: "Apron",
			exposure: 2,
			note: "Increases just devotion for submissives, humiliation fetishists and visibly pregnant pregnancy fetishists regardless of devotion level."
		}
	)
	.set("slutty jewelry",
		{
			name: "Bangles",
			exposure: 3,
			fs: {loves: new Set(["FSEgyptianRevivalist"])}
		}
	)
	.set("clubslut netting",
		{
			name: "Clubslut netting",
			exposure: 3,
			fs: {loves: new Set(["FSBodyPurist", "FSIntellectualDependency"])},
		}
	)
	.set("cutoffs and a t-shirt",
		{
			name: "Cutoffs and a t-shirt",
			exposure: 0
		}
	)
	.set("a comfortable bodysuit",
		{
			name: "Bodysuit",
			exposure: 1,
			fs: {loves: new Set(["FSBodyPurist"])},
		}
	)
	.set("a cheerleader outfit",
		{
			name: "Cheerleader",
			exposure: 2,
			fs: {loves: new Set(["FSGenderFundamentalist", "FSYouthPreferentialist"])},
		}
	)
	.set("a fallen nuns habit",
		{
			name: "Fallen nun",
			fs: {
				loves: new Set(["FSChattelReligionist"]),
			},
			exposure: 3
		}
	)
	.set("a hijab and abaya",
		{
			name: "Hijab and abaya",
			fs: {
				loves: new Set(["FSChattelReligionist"]),
			},
			exposure: 0
		}
	)
	.set("a leotard",
		{
			name: "Leotard",
			exposure: 1,
			fs: {loves: new Set(["FSBodyPurist"])},
		}
	)
	.set("a slutty maid outfit",
		{
			name: "Maid (slutty)",
			exposure: 2,
			fs: {loves: new Set(["FSSlaveProfessionalism", "FSIntellectualDependency"])},
		}
	)
	.set("a mini dress",
		{
			name: "Mini dress",
			exposure: 2
		}
	)
	.set("attractive lingerie",
		{
			name: "Nice lingerie",
			exposure: 2
		}
	)
	.set("a slutty nurse outfit",
		{
			name: "Nurse (slutty)",
			exposure: 2
		}
	)
	.set("overalls",
		{
			name: "Overalls",
			exposure: 1
		}
	)
	.set("panties",
		{
			name: "Panties",
			exposure: 2,
			topless: true
		}
	)
	.set("a scalemail bikini",
		{
			name: "Scalemail bikini",
			exposure: 2
		}
	)
	.set("a schoolgirl outfit",
		{
			name: "Schoolgirl",
			exposure: 1,
			fs: {loves: new Set(["FSGenderFundamentalist", "FSYouthPreferentialist"])},
		}
	)
	.set("a slutty outfit",
		{
			name: "Slutty outfit",
			exposure: 2,
			fs: {loves: new Set(["FSIntellectualDependency"])},
		}
	)
	.set("spats and a tank top",
		{
			name: "Spats and a tank top",
			exposure: 0
		}
	)
	.set("a string bikini",
		{
			name: "String bikini",
			exposure: 3,
			fs: {loves: new Set(["FSBodyPurist", "FSIntellectualDependency"])},
		}
	)
	.set("a succubus outfit",
		{
			name: "Succubus costume",
			fs: {
				loves: new Set(["FSChattelReligionist"]),
			},
			exposure: 3
		}
	)
	.set("slutty business attire",
		{
			name: "Suit (slutty)",
			exposure: 2,
			fs: {
				loves: new Set(["FSMaturityPreferentialist", "FSSlaveProfessionalism"])
			},
		}
	)
	.set("a halter top dress",
		{
			name: "Haltertop dress",
			exposure: 0,
			fs: {loves: new Set(["FSBodyPurist", "FSSlaveProfessionalism"])},
		}
	)
	.set("chains",
		{
			name: "Chains",
			fs: {
				unlocks: "FSDegradationist",
				loves: new Set(["FSDegradationist"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
			get requirements() { return V.boughtItem.clothing.chains === 1; },
			exposure: 4,
			harsh: true,
			note: "Increases devotion and fear for slaves who are disobedient and not terrified. Masochists who are at least ambivalent gain devotion, may also cause masochism."
		}
	)
	.set("no clothing",
		{
			name: "Naked",
			exposure: 4,
			harsh: true,
			fs: {
				loves: new Set(["FSPhysicalIdealist", "FSIntellectualDependency"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
		}
	)
	.set("a penitent nuns habit",
		{
			name: "Penitent nun",
			fs: {
				loves: new Set(["FSChattelReligionist"]),
			},
			exposure: 0,
			harsh: true,
			note: "Increases devotion and fear but damages health, may cause masochism."
		}
	)
	.set("restrictive latex",
		{
			name: "Restrictive latex",
			fs: {loves: new Set(["FSBodyPurist"])},
			exposure: 3,
			harsh: true,
			note: "Increases fear and devotion for resistant slaves and just devotion for obedient, non-terrified submissives."
		}
	)
	.set("shibari ropes",
		{
			name: "Shibari ropes",
			fs: {
				loves: new Set(["FSDegradationist"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
			exposure: 4,
			harsh: true,
			note: "Increases fear and devotion for resistant slaves and just devotion for obedient, non-terrified submissives."
		}
	)
	.set("uncomfortable straps",
		{
			name: "Uncomfortable straps",
			exposure: 3,
			harsh: true,
			fs: {
				loves: new Set(["FSDegradationist"]),
				tolerates: new Set(["FSChattelReligionist"]),
			},
			note: "Increases devotion and fear for slaves who are disobedient and not terrified. Masochists who are at least ambivalent gain devotion, may also cause masochism."
		}
	);

/**
 * @typedef {object} slaveWear
 * @property {string} name
 * @property {itemFS} [fs]
 * @property {boolean} [requirements]
 * @property {boolean} [harsh]
 * @property {string} [note]
 */

/**
 * @typedef {Map<string, slaveWear>} slaveWearCategory
 */

/** @type {Object.<string, slaveWearCategory>} string should be the slave property being modified.*/
App.Data.slaveWear = {
	collar: new Map([
		["stylish leather", {name: "Stylish leather"}],
		["satin choker", {name: "Satin choker"}],
		["silk ribbon", {name: "Silken ribbon"}],
		["heavy gold", {name: "Heavy gold"}],
		["pretty jewelry", {name: "Pretty jewelry"}],
		["nice retirement counter",
			{
				name: "Nice retirement counter",
				get requirements() {
					return V.seeAge === 1;
				}
			}
		],
		["preg biometrics",
			{
				name: "Pregnancy biometrics",
				get requirements() {
					return V.seePreg === 1;
				},
				note: "Increases devotion for those who have pregnancy fetish while fertile or a humiliation fetish. For others obedient ones gain devotion, ambivalent ones gain fear and devotion and resistant ones lose devotion and gain fear."
			}
		],
		["bell collar",
			{
				name: "Bell",
				note: "On non-obedient slaves reduces fear a lot and devotion somewhat."
			},
		],
		["leather with cowbell",
			{
				name: "Cowbell",
				note: "On obedient slaves with boob fetish increases devotion, on disobedient slaves reduces fear a lot and devotion somewhat."
			}
		],
		["bowtie",
			{
				name: "Bowtie collar",
				fs: {
					unlocks: "FSGenderFundamentalist",
					loves: new Set(["FSGenderFundamentalist"]),
				},
				get requirements() {
					return V.boughtItem.clothing.bunny === 1;
				}
			}
		],
		["neck tie",
			{
				name: "Neck tie",
				fs: {
					unlocks: "FSPaternalist",
					loves: new Set(["FSPaternalist"]),
				},
				get requirements() {
					return V.boughtItem.clothing.conservative === 1;
				}
			}
		],
		["ancient Egyptian",
			{
				name: "Ancient Egyptian",
				fs: {
					unlocks: "FSEgyptianRevivalist",
					loves: new Set(["FSEgyptianRevivalist"]),
				},
				get requirements() {
					return V.boughtItem.clothing.egypt === 1;
				}
			}
		],
		["tight steel",
			{
				name: "Tight steel",
				fs: {
					loves: new Set(["FSChattelReligionist"])
				},
				harsh: true
			}
		],
		["cruel retirement counter",
			{
				name: "Cruel retirement counter",
				fs: {
					loves: new Set(["FSChattelReligionist"])
				},
				get requirements() {
					return V.seeAge === 1;
				},
				harsh: true
			}
		],
		["uncomfortable leather",
			{
				name: "Uncomfortable leather",
				fs: {
					loves: new Set(["FSChattelReligionist"])
				},
				harsh: true
			}
		],
		["shock punishment",
			{
				name: "Shock punishment",
				harsh: true,
				note: "For non-obedient slaves increases fear a great deal and reduces devotion, for resistant non-odd slaves it affects both much more a single time and gives the odd flaw."
			}
		],
		["neck corset",
			{
				name: "Neck corset",
				harsh: true
			}
		],
	]),

	faceAccessory: new Map([
		["porcelain mask",
			{
				name: "Porcelain mask",
				note: "Obscures the face, increases fear and devotion for disobedient slaves, submissives and nymphos also enjoy wearing one."
			}
		],
		["cat ears",
			{
				name: "Cat ears",
				note: "Increases fear and devotion for disobedient slaves, submissives and nymphos also enjoy wearing one."
			}
		],
	]),

	vaginalAttachment: new Map([
		["none", {name: "None"}],
		["vibrator",
			{
				name: "Vibrating attachment",
				get requirements() {
					return V.boughtItem.toys.vaginalAttachments === 1;
				},
				vibrates: 1
			}
		],
		["smart vibrator",
			{
				name: "Smart vibrating attachment",
				get requirements() {
					return V.boughtItem.toys.smartVaginalAttachments === 1;
				},
				vibrates: 2
			}
		]
	]),

	dickAccessory: new Map([
		["none", {name: "None"}],
		["bullet vibrator",
			{
				name: "Bullet vibrator",
				get requirements() {
					return V.boughtItem.toys.dildos === 1;
				},
				vibrates: 1,
			}
		],
		["smart bullet vibrator",
			{
				name: "Smart bullet vibrator",
				get requirements() {
					return V.boughtItem.toys.smartVibes === 1;
				},
				vibrates: 2,
			}
		]
	]),

	buttplugAttachment: new Map([
		["none", {name: "None"}],
		["tail",
			{
				name: "Tail",
				get requirements() {
					return V.boughtItem.toys.buttPlugTails === 1;
				}
			}
		],
		["fox tail",
			{
				name: "Fox tail",
				get requirements() {
					return V.boughtItem.toys.buttPlugTails === 1;
				}
			}
		],
		["cat tail",
			{
				name: "Cat tail",
				get requirements() {
					return V.boughtItem.toys.buttPlugTails === 1;
				}
			}
		],
		["cow tail",
			{
				name: "Cow tail",
				get requirements() {
					return V.boughtItem.toys.buttPlugTails === 1;
				}
			}
		]
	]),

	armAccessory: new Map([
		// ["none", {name: "None"}],
		["hand gloves", {name: "Hand gloves"}],
		["elbow gloves", {name: "Elbow gloves"}]

	]),

	legAccessory: new Map([
		// ["none", {name: "None"}],
		["short stockings", {name: "Short stockings"}],
		["long stockings", {name: "Long stockings"}]

	]),
};

/**
 * @typedef {object} mouthAccessory
 * @property {string} name
 * @property {boolean} [requirements]
 * @property {string} [note]
 */

/** @type {Map<FC.MouthAccessory, mouthAccessory>} */
App.Data.mouthAccessory = new Map([
	["ball gag",
		{
			name: "Ball gag",
			note: "Increases fear and devotion for disobedient slaves, submissives and nymphos also enjoy wearing one."
		}
	],
	["bit gag",
		{
			name: "Bit gag",
			note: "Increases fear and devotion for disobedient slaves, submissives and nymphos also enjoy wearing one."
		}
	],
	["ring gag",
		{
			name: "Ring gag",
			note: "Increases fear and devotion for disobedient slaves, submissives and nymphos also enjoy wearing one."
		}
	],
	["massive dildo gag",
		{
			name: "Massive dildo gag",
			get requirements() {
				return V.boughtItem.toys.gags === 1;
			},
			note: "Increases oral skill up to a point and causes fear for disobedient slaves."
		}
	],
	["dildo gag",
		{
			name: "Dildo gag",
			note: "Increases oral skill up to a point and causes fear for disobedient slaves."
		}
	],
]);

/**
 * @typedef {object} slaveShoes
 * @property {string} name
 * @property {itemFS} [fs]
 * @property {boolean} [requirements]
 * @property {boolean} [harsh]
 * @property {number} heelHeight height in cm. Over 4cm they may totter. 21cm and over (8 inch heels) will be painful/extreme
 * @property {number} platformHeight height in cm. Adds to heel height.
 */

/**
 * @type {Map<FC.WithNone<FC.Shoes>, slaveShoes>} slaveShoesCategory
 */
App.Data.shoes = new Map([ // TODO: add lift property
	["none",
		{
			name: "Barefoot",
			heelHeight: 0,
			platformHeight: 0
		}
	],
	["flats",
		{
			name: "Flats",
			heelHeight: 0,
			platformHeight: 0
		}
	],
	["pumps",
		{
			name: "Pumps",
			heelHeight: 5, // 2 inch heels
			platformHeight: 0
		}
	],
	["heels",
		{
			name: "Heels",
			heelHeight: 13, // 5 inch heels
			platformHeight: 0
		}
	],
	["boots",
		{
			name: "Thigh boots",
			heelHeight: 13, // 5 inch heels
			platformHeight: 0
		}
	],
	["extreme heels",
		{
			name: "Painfully extreme heels",
			heelHeight: 21, // 8 inch heels
			platformHeight: 0
		}
	],
	["platform shoes",
		{
			name: "Platforms",
			fs: {
				unlocks: "FSStatuesqueGlorification",
				loves: new Set(["FSStatuesqueGlorification"])
			},
			get requirements() {
				return (V.boughtItem.shoes.heels === 1);
			},
			heelHeight: 0,
			platformHeight: 8 // 3 inch platform, no heels
		}
	],
	["platform heels",
		{
			name: "Platform heels",
			fs: {
				unlocks: "FSStatuesqueGlorification",
				loves: new Set(["FSStatuesqueGlorification"])
			},
			get requirements() {
				return (V.boughtItem.shoes.heels === 1);
			},
			heelHeight: 13, // 8 inches, but not painful like extremes (3 inch platforms)
			platformHeight: 8
		}
	],
	["extreme platform heels",
		{
			name: "Painfully extreme platform heels",
			fs: {
				unlocks: "FSStatuesqueGlorification",
				loves: new Set(["FSStatuesqueGlorification"])
			},
			get requirements() {
				return (V.boughtItem.shoes.heels === 1);
			},
			heelHeight: 21, // 12 inches! 8 inch heel, 4 inch platform
			platformHeight: 10
		}
	],
]);

/**
 * @typedef {object} slaveButtplugs
 * @property {string} name
 * @property {itemFS} [fs]
 * @property {boolean} [requirements]
 * @property {0|1|2|3} width
 * @property {0|1|2} length
 */

/**
 * @type {Map<string, slaveButtplugs>}
 */
App.Data.buttplug = new Map([
	["none",
		{
			name: "None",
			width: 0,
			length: 0
		}
	],
	["plug",
		{
			name: "Standard plug",
			width: 1,
			length: 1
		}
	],
	["long plug",
		{
			name: "Long plug",
			get requirements() {
				return V.boughtItem.toys.buttPlugs === 1;
			},
			width: 1,
			length: 2
		}
	],
	["large plug",
		{
			name: "Large plug",
			width: 2,
			length: 1
		}
	],
	["long, large plug",
		{
			name: "Long, large plug",
			get requirements() {
				return V.boughtItem.toys.buttPlugs === 1;
			},
			width: 2,
			length: 2
		}
	],
	["huge plug",
		{
			name: "Huge plug",
			width: 3,
			length: 1
		}
	],
	["long, huge plug",
		{
			name: "Long, huge plug",
			get requirements() {
				return V.boughtItem.toys.buttPlugs === 1;
			},
			width: 3,
			length: 2
		}
	]
]);
/**
 * @typedef {object} vaginalAccessories
 * @property {string} name
 * @property {itemFS} [fs]
 * @property {boolean} [requirements]
 * @property {0|1|2|3} width
 * @property {0|1|2} length
 * @property {0|1|2} [vibrates=0] 0: none, 1: standard, 2: "smart" vibe
 */

/**
 * @type {Map<string, vaginalAccessories>}
 */
App.Data.vaginalAccessory = new Map([
	["none",
		{
			name: "None",
			width: 0,
			length: 0
		}
	],
	["bullet vibrator",
		{
			name: "Bullet vibrator",
			width: 0,
			length: 1,
			vibrates: 1
		}
	],
	["smart bullet vibrator",
		{
			name: "Smart bullet vibrator",
			get requirements() {
				return V.boughtItem.toys.smartVibes === 1;
			},
			width: 0,
			length: 1,
			vibrates: 2
		}
	],
	["dildo",
		{
			name: "Dildo",
			width: 1,
			length: 1
		}
	],
	["long dildo",
		{
			name: "Long dildo",
			get requirements() {
				return V.boughtItem.toys.dildos === 1;
			},
			width: 1,
			length: 2
		}
	],
	["large dildo",
		{
			name: "Large dildo",
			width: 2,
			length: 1
		}
	],
	["long, large dildo",
		{
			name: "Long, large dildo",
			get requirements() {
				return V.boughtItem.toys.dildos === 1;
			},
			width: 2,
			length: 2
		}
	],
	["huge dildo",
		{
			name: "Huge dildo",
			get requirements() {
				return V.boughtItem.toys.dildos === 1;
			},
			width: 3,
			length: 1
		}
	],
	["long, huge dildo",
		{
			name: "Long, huge dildo",
			get requirements() {
				return V.boughtItem.toys.dildos === 1;
			},
			width: 3,
			length: 2
		}
	]
]);

/**
 * @typedef {slaveWear} bellyAccessory
 * @property {1|2|3|4} [empathyBelly]
 */
/** @type {Map<FC.BellyAccessory, bellyAccessory>} */
App.Data.bellyAccessory = new Map([
	["none", {name: "None"}],
	["a corset",
		{
			name: "Tight corset",
			note: "Slowly narrows the waist through femininity into the start of a waspish hourglass."
		}
	],
	["an extreme corset",
		{
			name: "Extreme corset",
			note: "Narrows the waist through waspish into absurd levels, painfully if waist is feminine or wider (increasing obedience and fear in resistant slaves), but risks miscarriage if a pregnant belly becomes too big."
		}
	],
	["a support band",
		{
			name: "Supportive band",
			note: "Eases the pains of pregnancy and reduces chance of miscarriage."
		}
	],
	["a small empathy belly",
		{
			name: "1st Trimester belly",
			fs: {
				unlocks: "FSRepopulationFocus",
				loves: new Set(["FSRepopulationFocus"])
			},
			get requirements() {
				return V.boughtItem.clothing.belly === 1;
			},
			empathyBelly: 1
		}
	],
	["a medium empathy belly",
		{
			name: "2nd Trimester belly",
			fs: {
				unlocks: "FSRepopulationFocus",
				loves: new Set(["FSRepopulationFocus"])
			},
			get requirements() {
				return V.boughtItem.clothing.belly === 1;
			},
			empathyBelly: 2
		}
	],
	["a large empathy belly",
		{
			name: "3rd Trimester belly",
			fs: {
				unlocks: "FSRepopulationFocus",
				loves: new Set(["FSRepopulationFocus"])
			},
			get requirements() {
				return V.boughtItem.clothing.belly === 1;
			},
			empathyBelly: 3
		}
	],
	["a huge empathy belly",
		{
			name: "3rd Trimester twins belly",
			fs: {
				unlocks: "FSRepopulationFocus",
				loves: new Set(["FSRepopulationFocus"])
			},
			get requirements() {
				return V.boughtItem.clothing.belly === 1;
			},
			empathyBelly: 4
		}
	]
]);
/**
 * @typedef {object} slaveWearChastity
 * @property {string} name
 * @property {object} updateSlave
 * @property {string} [note]
 * @property {itemFS} [fs]
 */

/** @type {Map<string, slaveWearChastity>} */
App.Data.chastity = new Map([
	// '.value' must be a string, so using update slave so I can update multiple values.
	["none",
		{
			name: "None",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 0,
				chastityPenis: 0,
				chastityVagina: 0
			},
		}
	],
	["anal chastity",
		{
			name: "Anal chastity",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 1,
				chastityPenis: 0,
				chastityVagina: 0
			},
			note: "Prevents losing anal virginity."
		}
	],
	["chastity belt",
		{
			name: "Chastity belt",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 0,
				chastityPenis: 0,
				chastityVagina: 1
			},
			note: "Prevents losing virginity, has various effects, obedient virgins, buttsluts and ones with relatively high sex drive are most affected."
		}
	],
	["combined chastity belt",
		{
			name: "Combined chastity belt",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 1,
				chastityPenis: 0,
				chastityVagina: 1
			},
			note: "Prevents losing virginities, has various effects, obedient virgins, buttsluts and ones with relatively high sex drive are most affected."
		}
	],
	["chastity cage",
		{
			name: "Chastity cage",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 0,
				chastityPenis: 1,
				chastityVagina: 0
			},
			note: "Prevents using penis, has various effects, devotion, trust and sex drive of unresistant slaves with healthy sex drive all suffer from wearing one unless they're a masochist, nympho, neglectful, buttslut, sterile or lack balls."
		}
	],
	["combined chastity cage",
		{
			name: "Combined chastity cage",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 1,
				chastityPenis: 1,
				chastityVagina: 0
			},
			note: "Protects both penis and anus from sex, has various effects, devotion and trust and sex drive of unresistant slaves with healthy sex drive all suffer from wearing one unless they're a masochist, nympho, neglectful, buttslut, sterile or lack balls."
		}
	],
	["genital chastity",
		{
			name: "Genital chastity",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 0,
				chastityPenis: 1,
				chastityVagina: 1
			},
			note: "Protects both penis and vagina from sex, has various effects."
		}
	],
	["full chastity",
		{
			name: "Full chastity",
			updateSlave: {
				choosesOwnChastity: 0,
				chastityAnus: 1,
				chastityPenis: 1,
				chastityVagina: 1
			},
			note: "Protects penis, vagina and anus, has various effects."
		}
	],
	["choose own chastity",
		{
			name: "Choose own chastity",
			fs: {unlocks: "FSRestart"},
			updateSlave: {
				choosesOwnChastity: 1
			},
		}
	],
	["revoke choosing own chastity",
		{
			name: "Revoke choosing own chastity",
			fs: {unlocks: "FSRestart"},
			updateSlave: {
				choosesOwnChastity: 0
			},
		}
	]
]);