code:
(function() {
	const readOnlySymbol = Symbol("readonly proxy");
	globalThis.createReadonlyProxy = function(target) {
		if (target == null) { return target; } // intentionally ==
		if (target[readOnlySymbol]) { return target; }
		if (_.isArray(target)) {
			return new Proxy(target, {
				get: function(o, prop) {
					if (prop === readOnlySymbol) { return true; }
					const val = o[prop];
					if (typeof val === 'function') {
						if (['push', 'unshift', 'pop'].includes(prop)) {
							return function() {
								throw Error("Cannot modify a readonly array");
							};
						}
						return val.bind(target);
					}
					return createReadonlyProxy(val);
				},
				set: function(o, prop, value) {
					return true;
				},
				deleteProperty: function(o, prop) {
					return true;
				}
			});
		}
		if (_.isObject(target)) {
			return new Proxy(target, {
				get: function(o, prop) {
					if (prop === readOnlySymbol) { return true; }
					return createReadonlyProxy(o[prop]);
				},
				set: function(o, prop, value) {
					return true;
				},
				deleteProperty: function(o, prop) {
					return true;
				}
			});
		}
		return target;
	};
	const cheaterSymbol = Symbol("cheating proxy");
	globalThis.createCheatProxy = function(target) {
		if (target == null) { return target; } // intentionally ==
		if (target[cheaterSymbol]) { return target; }
		if (_.isArray(target)) {
			return new Proxy(target, {
				get: function(o, prop) {
					if (prop === cheaterSymbol) { return true; }
					const val = o[prop];
					if (typeof val === 'function') {
						if (['push', 'unshift', 'pop'].includes(prop)) {
							return function(el) {
								const retval = Array.prototype[prop].apply(o, arguments);
								// Make sure we set cheater after calling the function
								State.variables.cheater = 1;// Can't use `V` because it probably points to a proxy.
								return retval;
							};
						}
						return val.bind(target);
					}
					return createCheatProxy(val);
				},
				set: function(o, prop, value) {
					o[prop] = value;
					State.variables.cheater = 1;// Can't use `V` because it probably points to a proxy.
					return true;
				},
				deleteProperty: function(o, prop) {
					delete o[prop];
					State.variables.cheater = 1;// Can't use `V` because it probably points to a proxy.
					return true;
				}
			});
		}
		if (_.isObject(target)) {
			return new Proxy(target, {
				get: function(o, prop) {
					if (prop === cheaterSymbol) { return true; }
					return createCheatProxy(o[prop]);
				},
				set: function(o, prop, value) {
					o[prop] = value;
					State.variables.cheater = 1;
					return true;
				},
				deleteProperty: function(o, prop) {
					delete o[prop];
					State.variables.cheater = 1;
					return true;
				}
			});
		}
		return target;
	};
})();
Object.defineProperty(window, "V", {
	get: function() {
		if (window.storyProxy != null) { return window.storyProxy; }
		return State.variables;
	}
});

// This should be used if the user might use V under normal, non-cheating circumstances but shouldn't be punished for accidentally setting the value. The attempt to make the change will simply be disregarded.
globalThis.runWithReadonlyProxy = function(callback) {
	globalThis.storyProxy = createReadonlyProxy(State.variables);
	try {
		return callback();
	} finally {
		globalThis.storyProxy = null;
	}
};
// This should be used if setting values would constitute cheating. For example, a debug view that shows all variables in an editable form; showing isn't cheating, but making a change would be.
globalThis.runWithCheatProxy = function(callback) {
	globalThis.storyProxy = createCheatProxy(State.variables);
	try {
		return callback();
	} finally {
		globalThis.storyProxy = null;
	}
};


code:
App.Arcology.Cell.BaseCell = class extends App.Entity.Serializable {
	/**
	 * @param {number} owner
	 */
	constructor(owner) {
		super();
		/**
		 * 0: private
		 * 1: player
		 * @type {number}
		 */
		this.owner = owner;
		/**
		 * List of classes this cell can be converted to.
		 * Format: "BaseCell" stands for App.Arcology.Cell.BaseCell
		 * Note: The current class needs to be included as well, otherwise converting back is not possible.
		 * @type {string[]}
		 */
		this.allowedConversions = [];
	}

	/**
	 * @returns {string}
	 */
	static get cellName() {
		return "baseCell";
	}

	/**
	 * @returns {string}
	 */
	get colorClass() {
		return "empty";
	}

	/**
	 * @returns {number}
	 */
	get width() {
		return 1;
	}

	/**
	 * @returns {string}
	 */
	get name() {
		return "Base Cell";
	}

	/**
	 * @returns {boolean}
	 */
	isBaseType() {
		return true;
	}

	renderCell(path, width) {
		const outerCell = document.createElement("div");
		outerCell.classList.add("outerCell");
		outerCell.style.width = `${width * this.width}%`;

		const innerCell = document.createElement("div");
		innerCell.classList.add("innerCell");
		innerCell.classList.add(this.owner === 1 ? this.colorClass : "private");
		innerCell.append(this.cellContent(path));

		outerCell.append(innerCell);

		return outerCell;
	}

	/**
	 * @param {Array<number>} path
	 * @returns {Node}
	 */
	cellContent(path) {
		return document.createDocumentFragment();
	}

	/**
	 * @param {App.Arcology.Building} containingBuilding
	 * @returns {DocumentFragment}
	 */
	cellPassage(containingBuilding) {
		/**
		 * Thanks JS!
		 * @type {App.Arcology.Cell.BaseCell}
		 */
		const that = this;

		const fragment = document.createDocumentFragment();

		const scene = document.createElement("p");
		scene.classList.add("scene-intro");
		scene.append(this._setting());
		if (this.canBeSold()) {
			scene.append(' ', ownership(this));
		}
		fragment.append(scene);

		if (this.owner === 1) {
			const upgrades = document.createElement("p");

			upgrades.append(this._body(containingBuilding));

			fragment.append(upgrades);

			if (this.allowedConversions.length > 0 && this.isBaseType()) {
				const p = document.createElement("p");
				if (V.rep < 5000) {
					fragment.append(App.UI.DOM.makeElement("p", "You don't have the reputation required to convert the sector base type."));
				} else {
					const cost = 50000;
					const rep = 5000;
					for (const ac of this.allowedConversions) {
						const cellClass = App.Arcology.Cell[ac];
						if (!(this instanceof cellClass)) {
							p.append(App.UI.DOM.link(`Convert sector to ${cellClass.cellName}.`, () => {
								if (Dialog.isOpen()) {
									Dialog.close();
								}

								/** @type {App.Arcology.Cell.BaseCell} */
								const newCell = new cellClass(1);
								newCell.allowedConversions = this.allowedConversions;
								containingBuilding.replaceCell(this, newCell);
								cashX(-cost, "capEx");
								repX(-rep, "capEx");

								containingBuilding.refresh();
								Dialog.setup(newCell.name);
								$(Dialog.body()).empty().append(newCell.cellPassage(containingBuilding));
								Dialog.open();
							}));

							App.UI.DOM.appendNewElement("span", p, ` Costs ${cashFormat(cost)} and ${rep} reputation as many citizens will lose most of what they own.`);
						}
					}
				}
				fragment.append(p);
			}
		}

		return fragment;

		/**
		 * @returns {DocumentFragment}
		 */
		function ownership(cell) {
			const DEMAND_FACTOR_DIFF = 20;
			const fragment = document.createDocumentFragment();
			const A = V.arcologies[0];
			const allCells = V.building.findCells(cell => !(cell instanceof App.Arcology.Cell.Filler));
			const ownedCells = allCells.filter(cell => cell.owner === 1).length;
			const oneCellPercentage = ((1 / allCells.length) * 100).toPrecision(3);

			/**
			 * @param {boolean} buy
			 * @returns {number}
			 */
			function price(buy) {
				let demand = A.demandFactor;
				if (buy) {
					demand += DEMAND_FACTOR_DIFF;
				}
				return 1000 * Math.trunc(A.prosperity * (1 + (demand / 100)));
			}

			if (cell.owner === 1) {
				fragment.append(`Selling this sector would relinquish a ${oneCellPercentage}% interest in ${A.name}. Such an interest is worth ${cashFormat(price(false))}.`);

				if (ownedCells > 1) {
					const span = document.createElement("span");
					span.classList.add("clear-formatting");
					span.append(App.Arcology.getCellLink("Sell", that.name, () => that.cellPassage(containingBuilding),
						() => {
							cashX(price(false), "capEx");
							App.Arcology.updateOwnership();
							A.demandFactor -= DEMAND_FACTOR_DIFF;
							cell.owner = 0;
						}));
					fragment.append(" ", span);
				}
			} else {
				fragment.append(`You will have to acquire an additional ${oneCellPercentage}% interest in ${A.name} to take control of this sector. Such an interest is worth ${cashFormat(price(true))} and will require a transaction cost of ${cashFormat(10000)} to acquire for a total cost of ${cashFormat(price(true) + 10000)}.`);
				const availableCells = allCells.length * ((100 - A.minority) / 100) - ownedCells;
				if (availableCells >= 1) {
					const buySpan = document.createElement("span");
					buySpan.classList.add("clear-formatting");
					buySpan.append(App.Arcology.getCellLink("Buy", that.name, () => that.cellPassage(containingBuilding),
						() => {
							cashX(-(price(true) + 10000), "capEx");
							A.demandFactor += DEMAND_FACTOR_DIFF;
							App.Arcology.updateOwnership();
							cell.owner = 1;
						}));
					fragment.append(" ", buySpan);

					if (V.rep >= 18000) {
						const repDiv = document.createElement("div");
						repDiv.classList.add("clear-formatting");

						const repPrice = Math.clamp(price(true) / 2, 0, 18000);
						repDiv.append("You have so much political capital that you can spend reputation to acquire ownership by spending reputation. ",
							App.Arcology.getCellLink("Use reputation", that.name, () => that.cellPassage(containingBuilding),
								() => {
									repX(-(repPrice), "capEx");
									A.demandFactor += DEMAND_FACTOR_DIFF;
									App.Arcology.updateOwnership();
									cell.owner = 1;
								}));
						fragment.append(repDiv);
					}
				} else {
					fragment.append("Too much of the arcology is owned by a single minority holder for you to force a purchase of this sector right now. Your control of the arcology should naturally resolve this situation in a few weeks.");
				}
			}
			return fragment;
		}
	}

	/**
	 * @returns {string|Node}
	 * @protected
	 */
	_setting() {
		return "baseCell";
	}

	/**
	 * @param {App.Arcology.Building} containingBuilding
	 * @returns {Node}
	 * @protected
	 */
	_body(containingBuilding) {
		return document.createDocumentFragment();
	}

	/**
	 * @param {string} name
	 * @param {function(): void} action
	 * @param {number} cost
	 * @param {App.Arcology.Building} containingBuilding
	 * @param {string} [note]
	 * @param {Node} [domNote]
	 * @returns {HTMLDivElement}
	 * @protected
	 */
	_makeInternalUpgrade(name, action, cost, containingBuilding, note, domNote) {
		const div = document.createElement("div");

		div.append(App.Arcology.getCellLink(name, this.name, () => this.cellPassage(containingBuilding),
			() => {
				cashX(-cost, "capEx");
				action();
			}));

		this._upgradeNote(div, cost, note, domNote);

		return div;
	}

	/**
	 * @param {string} name
	 * @param {function(): void} action
	 * @param {number} cost
	 * @param {string} passage
	 * @param {string} [note]
	 * @param {Node} [domNote]
	 * @returns {HTMLDivElement}
	 * @protected
	 */
	_makeExternalUpgrade(name, action, cost, passage, note, domNote) {
		const div = document.createElement("div");

		div.append(App.UI.DOM.passageLink(name, passage,
			() => {
				cashX(-cost, "capEx");
				action();
				Dialog.close();
			}));

		this._upgradeNote(div, cost, note, domNote);

		return div;
	}

	/**
	 * @param {HTMLDivElement} div
	 * @param {number} cost
	 * @param {string} [note]
	 * @param {Node} [domNote]
	 * @private
	 */
	_upgradeNote(div, cost, note, domNote) {
		if (cost > 0 || note === undefined) {
			note = ` Costs ${cashFormat(cost)}${note !== undefined ? ` ${note}` : ""}.`;
		}
		App.UI.DOM.appendNewElement("span", div, note, "detail");

		if (domNote !== undefined) {
			div.append(domNote); // this only exists for the farmyard, remove once that is out of alpha
		}
	}

	/**
	 * @returns {boolean}
	 */
	canBeSold() {
		return false;
	}

	static _cleanupConfigScheme(config) {
		super._cleanupConfigScheme(config);
		// BC code
	}

	/** @returns {App.Arcology.Cell.BaseCell} */
	clone() {
		return new App.Arcology.Cell.BaseCell(this.owner)._init(this);
	}

	get className() { return "App.Arcology.Cell.BaseCell"; }
};

repo_name: CloudyCoffee/fc

license: mit


code:
/** base class for class-based events */
App.Events.BaseEvent = class BaseEvent {
	/**
	 * build a new event
	 * parameters are necessary for serialization (so that saving with the event active will work correctly) and should not normally be used directly
	 * child classes will inherit this implementation automatically, and should not normally need their own constructor implementation
	 * @param {Array<number>} [actors]
	 * @param {object} [params]
	 */
	constructor(actors, params) {
		/** @member {Array<number>} actors - a list of IDs for the actors participating in this event. */
		this.actors = actors || [];
		/** @member {object} params - a set of parameters to pass to the event. */
		this.params = params || {};
	}

	/** Event predicates determine whether the event will occur or not.
	 * @callback eventPredicate
	 * @returns {boolean}
	 */
	/** generate an array of zero or more predicates which must all return true in order for the event to be valid.
	 * lambda predicates may add properties to {@link App.Events.BaseEvent#params the params member} in order to pass information on to the event.
	 * child classes should implement this.
	 * @returns {Array<eventPredicate>}
	 */
	eventPrerequisites() {
		return [];
	}

	/** Actor predicates determine whether an actor is qualified for an event or not.
	 * @callback actorPredicate
	 * @param {App.Entity.SlaveState} slave
	 * @returns {boolean}
	 */
	/** generate an array of zero or more arrays, each corresponding to an actor in the event, which contain zero or more predicates which must be satisfied by the actor.
	 * child classes should implement this, unless they don't need any actors, or are overriding castActors.
	 * @returns {Array<Array<actorPredicate>>}
	 */
	actorPrerequisites() {
		return [];
	}

	/** get the choice weight for the event. Weights should be integral; higher weight means higher probability of selection.
	 * @returns {number}
	 */
	get weight() {
		return 1;
	}

	/** run the event and attach DOM output to the event passage.
	 * child classes must implement this.
	 * @param {ParentNode} node - Document fragment which event output should be attached to
	 */
	execute(node) {
	}

	/** clone the event (needed for serialization).
	 * default implementation should suffice for child classes */
	clone() {
		return _.cloneDeep(this);
	}

	/** serialize the event instance so it persists through saves.
	 * default implementation should suffice for child classes assigned to App.Events */
	toJSON() {
		const reviveData = {actors: this.actors, params: this.params};
		return JSON.reviveWrapper(`new App.Events.${this.constructor.name}($ReviveData$.actors, $ReviveData$.params)`, reviveData);
	}

	/** get the event's (human-readable) name. must not include ":".
	 * default implementation should generally suffice (though we might want to de-camelcase here or something).
	 * @returns {string}
	 */
	get eventName() {
		return this.constructor.name;
	}

	/** build the actual list of actors that will be involved in this event.
	 * default implementation should suffice for child classes with a fixed number of actors; may be overridden for events with variable actor count.
	 * @param {App.Entity.SlaveState} [firstActor] - if supplied, the first actor should be this slave (fail if she is not qualified)
	 * @returns {boolean} - return false if sufficient qualified actors could not be found (cancel the event)
	 */
	castActors(firstActor) {
		const prereqs = this.actorPrerequisites();

		// if casting is already complete and still valid, don't do it again
		if (this.actors.length === prereqs.length) {
			let recast = false;
			for (let i = 0; i < this.actors.length; ++i) {
				const actor = getSlave(this.actors[i]);
				recast = actor && prereqs[i].some(p => !p(actor));
			}
			if (!recast) {
				return true; // all roles previously cast & prereqs still met
			}
		}

		this.actors = [];

		let i = 0;
		if (firstActor && prereqs.length > 0) {
			if (prereqs[0].every(p => p(firstActor))) {
				this.actors.push(firstActor.ID);
			} else {
				return false; // preselected first actor was not qualified
			}
			i = 1; // first actor is cast
		}

		for (; i < prereqs.length; ++i) {
			const qualified = V.slaves.filter(s => !this.actors.includes(s.ID) && prereqs[i].every(p => p(s)));
			if (qualified.length === 0) {
				return false; // a required actor was not found
			}
			this.actors.push(qualified.pluck().ID);
		}

		return true; // all actors cast
	}
};

/** This is a trivial event for use as an example. */
App.Events.TestEvent = class TestEvent extends App.Events.BaseEvent {
	constructor(actors, params) {
		super(actors, params);
	}

	eventPrerequisites() {
		return [];
	}

	actorPrerequisites() {
		return [
			[] // one actor, no requirements
		];
	}

	execute(node) {
		let [eventSlave] = this.actors.map(a => getSlave(a)); // mapped deconstruction of actors into local slave variables
		App.Events.drawEventArt(node, eventSlave);
		node.appendChild(document.createTextNode(`This test event for ${eventSlave.slaveName} was successful.`));
	}
};


code:
/**
 * A pure DOM Passage, the SugarCube Wikifier never gets invoked.
 */
// @ts-ignore
App.DomPassage = class extends Passage {
	/**
	 * @param {string} title
	 * @param {function():Node} callback
	 * @param {string[]} tags
	 */
	constructor(title, callback, tags = []) {
		super(title, {
			hasAttribute: a => a === "tags",
			getAttribute: () => tags.join(" ")
		});
		this.callback = callback;

		// @ts-ignore
		Story.add(this);
	}

	/**
	 * @returns {Node}
	 */
	render() {
		// In case the callback fails give out a nice error message instead of breaking completely.
		try {
			return this.callback();
		} catch (ex) {
			return App.UI.DOM.formatException(ex);
		}
	}
};


code:
// Articles map
/**
 * @typedef {object} article
 * @property {function():(HTMLElement|DocumentFragment)} render
 * @property {string} [category]
 */
/**
 * @type {Map<string, article>}
 */
App.Encyclopedia.articles = new Map();

/**
 * Adds an article if it does not exist yet.
 *
 * @param {string} key
 * @param {function():(HTMLElement|DocumentFragment)} article
 * @param {string} [category]
 */
App.Encyclopedia.addArticle = function(key, article, category) {
	if (!App.Encyclopedia.articles.has(key)) {
		App.Encyclopedia.articles.set(key, {render: article, category: category});
	} else {
		console.log(`Trying to overwrite encyclopedia article "${article}".`);
	}
};

/**
 * Renders the specified article.
 *
 * @param {string} article
 * @returns {string|HTMLElement|DocumentFragment}
 */
App.Encyclopedia.renderArticle = function(article) {
	if (App.Encyclopedia.articles.has(article)) {
		return App.Encyclopedia.articles.get(article).render();
	}

	// TODO: turn this back on when completed.
	// return `Encyclopedia article "${article}" not found.`;
	return null;
};

// Categories map
/**
 * @type {Map<string, function():(HTMLElement|DocumentFragment)>}
 */
App.Encyclopedia.categories = new Map();

/**
 * Adds an article if it does not exist yet.
 *
 * @param {string} key
 * @param {function():(HTMLElement|DocumentFragment)} category
 */
App.Encyclopedia.addCategory = function(key, category) {
	if (!App.Encyclopedia.categories.has(key)) {
		App.Encyclopedia.categories.set(key, category);
	} else {
		console.log(`Trying to overwrite encyclopedia category "${category}".`);
	}
};

/**
 * Renders the specified article.
 *
 * @param {string} article
 * @returns {string|HTMLElement|DocumentFragment}
 */
App.Encyclopedia.renderCategory = function(article) {
	if (!App.Encyclopedia.articles.has(article)) {
		// TODO: turn this back on when completed.
		// return `Encyclopedia article "${article}" not found.`;
		return null;
	}

	const category = App.Encyclopedia.articles.get(article).category;
	if (!category) {
		// TODO: turn this back on when completed.
		// return `Encyclopedia article "${article}" has no category.`;
		return null;
	}

	if (!App.Encyclopedia.categories.has(category)) {
		return `Encyclopedia category "${category}" not found.`;
	}

	return App.Encyclopedia.categories.get(category)();
};

// Utility functions
App.Encyclopedia.topic = function(topic) {
	return App.UI.DOM.makeElement("span", topic, ["encyclopedia", "topic"]);
};

/**
 * @param {Node} parent
 * @param {string} source
 * @param {string} [author]
 */
App.Encyclopedia.addArticleSource = function(parent, source, author) {
	const r = [];

	if (author) {
		r.push(`<span class='author'>${author},</span>`);
	}

	r.push(`<span class='article'>${source}</span>`);

	App.Events.addNode(parent, r, "p", "encyclopedia-source");
};


code:
App.Data.FacilityDesc = class {
	constructor() {
		/** Base name for state variables */
		this.baseName = "";
		/** Generic name for UI (Brothel, Club, etc.)
		 * If null, baseName is used instead
		 */
		this.genericName = "";
		/** @type {Object.<string, FC.Data.JobDesc>} */
		this.jobs = {};
		this.defaultJob = "";
		/** @type {FC.Data.ManagerJobDesc} */
		this.manager = null;
		this.decorated = false;
	}
};

App.Data.Facilities = {};
App.Entity.Facilities = {};

App.Entity.Facilities.Job = class {
	constructor() {
		/** @type {FC.Data.JobDesc} */
		this.desc = null;
		/** @type {App.Entity.Facilities.Facility} */
		this.facility = null;
	}

	/**
	 * Does slave meet the requirements for this job
	 * @param {App.Entity.SlaveState} slave
	 * @returns {string[]}
	 */
	checkRequirements(slave) {
		let r = [];
		if (this.desc.publicSexUse &&
			(slave.breedingMark === 1 && V.propOutcome === 1 && V.eugenicsFullControl !== 1 && V.arcologies[0].FSRestart !== "unset")) {
			r.push(`${slave.slaveName} is for private use only.`);
		}
		if (!this.desc.fuckdollAccepted && slave.fuckdoll > 0) {
			r.push(`Fuckdolls can't ${this.desc.assignment} at ${this.facility.name}.`);
		}
		if (!this.desc.broodmotherAccepted && slave.preg > 37 && slave.broodmother === 2) {
			r.push(`Birthing broodmothers can't ${this.desc.assignment}.`);
		}
		return r;
	}

	/**
	 * Can slave be employed at this position
	 * @param {App.Entity.SlaveState} slave
	 * @returns {string[]}
	 */
	canEmploy(slave) {
		if (this.isEmployed(slave)) {
			return [`${slave.slaveName} is already assigned to ${this.desc.assignment} at ${this.facility.name}.`];
		}
		if (!this._facilityHasFreeSpace) {
			return [`Capacity of ${this.facility.name} exceeded.`];
		}
		if (slave.assignment === this.desc.assignment) {
			return [`${slave.slaveName} is already assigned to ${this.desc.assignment}.`];
		}

		return this.checkRequirements(slave);
	}

	/**
	 * Is slave with the given ID already assigned to this job
	 * @param {number} id
	 * @returns {boolean}
	 */
	hasEmployeeWithId(id) {
		return this.employeesIDs().has(id);
	}

	/**
	 * Is slave already assigned to this job
	 * @param {App.Entity.SlaveState} slave
	 * @returns {boolean}
	 */
	isEmployed(slave) {
		return this.hasEmployeeWithId(slave.ID);
	}

	/**
	 * @callback assignmentCallback
	 * @param {App.Entity.SlaveState} slave the slave whose assignment changes
	 * @param {FC.Assignment} assignment new assignment
	 * @returns {void}
	 */

	/**
	 * Returns link text for the penthouse assignment
	 * @param {number} ID slave ID
	 * @param {string} [passage] passage to go to
	 * @param {assignmentCallback} [callback]
	 * @param {string} [linkText]
	 * @returns {HTMLAnchorElement}
	 */
	assignmentLinkElement(ID, passage, callback, linkText) {
		linkText = linkText || this.desc.position;
		return App.UI.DOM.assignmentLink(slaveStateById(ID), this.desc.assignment, passage, callback, linkText);
	}

	/**
	 * all slaves that are employed at this job
	 * @returns {App.Entity.SlaveState[]}
	 */
	employees() {
		return [...this.employeesIDs()].map(id => slaveStateById(id));
	}

	/**
	 * IDs for all slaves that are employed at this job
	 * @returns {Set<number>}
	 */
	employeesIDs() {
		return V.JobIDMap[this.desc.assignment];
	}

	/**
	 * assignment string but the generic facility name replaced with its in-game name
	 */
	get assignment() {
		return this.desc.assignment.replace("the " + this.facility.desc.baseName, this.facility.name);
	}

	/**
	 * Tests if slave is broken enough
	 * @protected
	 * @param {App.Entity.SlaveState} slave
	 * @param {number} [pureDevotion=50] Minimal devotion level to pass test with any trust
	 * @param {number} [devotion=-50] Minimal devotion for slaves with enough fear
	 * @param {number} [trust=-21] Maximal trust (i.e. minimal fear) for the less devotional (see above)
	 * @param {number} [pureFear=-51] Maximal low trust to pass test with any devotion (because of the fear)
	 * @param {number} [pureTrust=101] Minimal high trust level to pass test without devotion
	 * @returns {boolean}
	 */
	static _isBrokenEnough(slave, pureDevotion = 50, devotion = -51, trust = -21, pureFear = -51, pureTrust = 101) {
		return slave.devotion >= pureDevotion ||
			slave.trust >= pureTrust ||
			slave.trust <= pureFear ||
			(
				slave.devotion > devotion &&
				slave.trust < trust
			);
	}

	/**
	 * @protected
	 * Standard message that slave is not broken enough
	 * @param {App.Entity.SlaveState} slave
	 * @returns {string}
	 */
	static _stdBreakageMessage(slave) {
		return `${slave.slaveName} must be either more fearful of you or devoted to you.`;
	}

	/** @protected */
	get _facilityHasFreeSpace() {
		return this.facility.hasFreeSpace;
	}
};

App.Entity.Facilities.ManagingJob = class extends App.Entity.Facilities.Job {
	constructor() {
		super();
		/** @type {FC.Data.ManagerJobDesc} */
		this.desc = null;
	}

	/**
	 * Can slave be employed at this position
	 * @param {App.Entity.SlaveState} slave
	 * @returns {string[]}
	 */
	checkRequirements(slave) {
		let r = super.checkRequirements(slave);
		if (slave.devotion < this.desc.requiredDevotion) {
			r.push(`${slave.slaveName} must be more devoted to you.`);
		}
		if (this.desc.shouldWalk && !canWalk(slave)) {
			r.push(`${slave.slaveName} must be able to walk.`);
		}
		if (this.desc.shouldHold && !canHold(slave)) {
			r.push(`${slave.slaveName} must be able to hold on to objects.`);
		}
		if (this.desc.shouldSee && !canSee(slave)) {
			r.push(`${slave.slaveName} must have working eyes.`);
		}
		if (this.desc.shouldHear && !canHear(slave)) {
			r.push(`${slave.slaveName} must be able to hear.`);
		}
		if (this.desc.shouldTalk && !canTalk(slave)) {
			r.push(`${slave.slaveName} must be able to talk.`);
		}
		if (this.desc.shouldThink && slave.fetish === "mindbroken") {
			r.push(`${slave.slaveName} must possess cognition.`);
		}
		return r;
	}

	/**
	 * Returns true if slave has enough applicable skill or career
	 * @param {App.Entity.SlaveState} slave
	 * @returns {boolean}
	 */
	slaveHasExperience(slave) {
		return (this.desc.skill !== null && slave.skill[this.desc.skill] >= V.masteredXP) ||
			(typeof slave.career === 'string' && this.desc.careers.includes(slave.career));
	}

	/** @returns {App.Entity.SlaveState} */
	get currentEmployee() {
		const employees = this.employees();
		return employees.length > 0 ? employees[0] : null;
	}

	/** @protected */
	get _facilityHasFreeSpace() {
		return true;
	}

	/** @returns {string} */
	get positionAbbreviation() {
		return this.desc.positionAbbreviation || "L";
	}
};

App.Entity.Facilities.Facility = class {
	/**
	 * @param {FC.Data.FacilityDesc} desc defines state variable for this facility
	 * @param {Object.<string, App.Entity.Facilities.Job>} [jobs] job object that are not default
	 * @param {App.Entity.Facilities.ManagingJob} [manager]
	 */
	constructor(desc, jobs, manager) {
		this.desc = desc;
		jobs = jobs || {};
		/** @private @type {Object.<string, App.Entity.Facilities.Job>} */
		this._jobs = {};

		for (const jn in this.desc.jobs) {
			if (jobs[jn] !== undefined) {
				this._jobs[jn] = jobs[jn];
			} else {
				this._jobs[jn] = this._createJob(jn);
			}
			this._jobs[jn].facility = this;
			this._jobs[jn].desc = desc.jobs[jn];
		}

		if (manager === undefined) {
			// default manager job implementation
			manager = (this.desc.manager !== null) ? new App.Entity.Facilities.ManagingJob() : null;
		}
		/** @private */
		this._manager = manager;
		if (this._manager !== null) {
			this._manager.facility = this;
			this._manager.desc = this.desc.manager;
		}
	}

	/** Facility display name
	 * @returns {string} */
	get name() {
		const stateObj = V[this.desc.baseName];
		const res = (stateObj && typeof stateObj === "object") ? stateObj.name : V[this.desc.baseName + "Name"];
		return res !== undefined ? res : 'the ' + this.genericName;
	}

	set name(newName) {
		const stateObj = V[this.desc.baseName];
		if (stateObj && typeof stateObj === "object") {
			stateObj.name = newName;
		} else {
			V[this.desc.baseName + "Name"] = newName;
		}
	}

	/** @returns {string} */
	get nameCaps() {
		return capFirstChar(this.name);
	}

	/** @returns {string} */
	get UIName() {
		return capFirstChar(this.genericName);
	}

	/** Facility generic name ("Brothel", "Schoolroom", etc.)
	 * @returns {string} */
	get genericName() {
		return this.desc.genericName !== null ? this.desc.genericName : capFirstChar(this.desc.baseName);
	}

	/** All jobs at this facility
	 * @returns {string[]}
	 */
	get jobsNames() {
		return Object.keys(this.desc.jobs);
	}

	/**
	 * Returns job description
	 * @param {string} [name] job name; the default job will be used if omitted
	 * @returns {App.Entity.Facilities.Job}
	 */
	job(name) {
		return this._jobs[name || this.desc.defaultJob];
	}

	get jobs() {
		return Object.values(this._jobs);
	}

	get manager() {
		return this._manager;
	}

	/** Facility slave capacity
	 * @returns {number} */
	get capacity() {
		return V[this.desc.baseName] !== null && ((typeof V[this.desc.baseName] === "object") ? V[this.desc.baseName].capacity : V[this.desc.baseName]);
	}

	get established() {
		return this.capacity > 0;
	}

	/** Number of already hosted slaves
	 * @returns {number} */
	get hostedSlaves() {
		return this.jobs.reduce((acc, job) => { return acc + job.employeesIDs().size; }, 0);
	}

	/**
	 * Count of employees plus manager altogether
	 * @returns {number}
	 */
	get totalEmployeesCount() {
		return this.hostedSlaves + ((this.manager && this.manager.currentEmployee) ? 1 : 0);
	}

	/**
	 * @returns {number} count of non slave occupants (e.g. bioreactors in the dairy)
	 */
	get nonEmployeeOccupantsCount() {
		return 0;
	}

	/**
	 * @returns {number} total count of facility places taken by employees and other beings (e.g. bioreactors in the dairy)
	 */
	get totalOccupants() {
		return this.hostedSlaves + this.nonEmployeeOccupantsCount;
	}

	get hasEmployees() {
		return this.jobs.some(j => j.employeesIDs().size > 0);
	}

	get hasFreeSpace() {
		return this.capacity > this.totalOccupants;
	}

	get freeSpace() {
		return this.capacity > 0 ? this.capacity - this.totalOccupants : 0;
	}

	/**
	 * @param {string} name
	 * @returns {number}
	 */
	option(name) {
		return (typeof V[this.desc.baseName] === "object") ? V[this.desc.baseName][name] : V[this.desc.baseName + name];
	}

	/**
	 * @param {string} name
	 * @returns {number}
	 */
	upgrade(name) {
		return (typeof V[this.desc.baseName] === "object") ? V[this.desc.baseName].upgrade[name] : this.option("Upgrade" + name);
	}

	/** Can this facility be decorated? */
	get isDecorated() {
		return this.desc.decorated;
	}

	/** How is this facility currently decorated?
	 * @returns {FC.FutureSocietyDeco}
	 */
	get decoration() {
		if (!this.isDecorated) {
			return "standard";
		}
		return (typeof V[this.desc.baseName] === "object") ? V[this.desc.baseName].decoration : V[this.desc.baseName + "Decoration"];
	}

	/** Configure facility decoration
	 * @param {FC.FutureSocietyDeco} val
	 */
	set decoration(val) {
		if (this.isDecorated) {
			if (typeof V[this.desc.baseName] === "object") {
				V[this.desc.baseName].decoration = val;
			} else {
				V[this.desc.baseName + "Decoration"] = val;
			}
		}
	}

	/** Returns the facility's value, including all purchased upgrades. */
	get value() {
		return 10000;
	}

	/**
	 * Can this facility host the given slave
	 * @param {App.Entity.SlaveState} slave
	 * @param {string} [job]
	 * @returns {string[]} array with rejection reasons. Slave can be hosted if this is empty.
	 */
	canHostSlave(slave, job) {
		job = job || this.desc.defaultJob;
		const j = this.job(job);
		if (j === undefined) {
			if (this.jobs.length === 0) {
				const err = `${this.genericName} facility has no jobs for slaves`;
				console.warn(err);
				return [err];
			} else {
				console.warn(`Can't find job ${job} at ${this.name}.`); // eslint-disable-line no-console
			}
		}
		// if there are more than one job at this facility, test them too
		if (Object.keys(this.desc.jobs).length > 1 && this.isHosted(slave)) {
			return [`${slave.slaveName} is already assigned to ${slave.assignment} at ${this.name}.`];
		}
		return j.canEmploy(slave);
	}

	/**
	 * Does the given slave work at this facility
	 * @param {App.Entity.SlaveState} slave
	 * @returns {boolean}
	 */
	isHosted(slave) {
		return this.jobs.some(job => job.isEmployed(slave));
	}

	/**
	 * Returns link text for the job assignments
	 * @param {number} ID slave ID
	 * @param {string} [job] generate link only for this job
	 * @param {string} [passage]
	 * @param {assignmentCallback} [callback]
	 * @returns {HTMLElement[]}
	 */
	assignmentLinkElements(ID, job, passage, callback) {
		/** @type {App.Entity.SlaveState} */
		const slave = slaveStateById(ID);
		const jobs = job === undefined ? this._jobs : {job: this._jobs[job]};

		let res = [];
		for (const jn in jobs) {
			const j = jobs[jn];
			let rejects = j.canEmploy(slave);
			if (rejects.length === 0) {
				res.push(j.assignmentLinkElement(ID, passage, callback));
			} else {
				res.push(App.UI.DOM.disabledLink(j.desc.position, rejects));
			}
		}
		return res;
	}

	/**
	 * Returns link text for the facility transfer
	 * @param {number} ID slave ID
	 * @param {string} [job] transfer to this job (uses default job if this is undefined)
	 * @param {string} [passage]
	 * @param {assignmentCallback} [callback]
	 * @returns {HTMLElement}
	 */
	transferLinkElement(ID, job, passage, callback) {
		job = job || this.desc.defaultJob;
		return this._jobs[job].assignmentLinkElement(ID, passage, callback, this.genericName);
	}

	/**
	 * all slaves that are employed at this job
	 * @returns {App.Entity.SlaveState[]}
	 */
	employees() {
		const jobArray = this.jobs;
		if (jobArray.length === 1) {
			return jobArray[0].employees();
		}
		return [].concat(...jobArray.map(j => j.employees()));
	}

	/**
	 * IDs for all slaves that are employed at this facility (excluding the manager)
	 * @returns {Set<number>}
	 */
	employeesIDs() {
		const jobArray = this.jobs;
		if (jobArray.length === 1) {
			return jobArray[0].employeesIDs();
		}
		const res = new Set();
		for (const j of jobArray) {
			const ids = j.employeesIDs();
			for (const id of ids) {
				res.add(id);
			}
		}
		return res;
	}

	/**
	 * @param {boolean} long
	 * @returns {string}
	 */
	occupancyReport(long) {
		if (this.jobs.length === 0) {
			return this.manager && this.manager.currentEmployee ? this.manager.positionAbbreviation : "";
		}
		const nonEmployees = this.nonEmployeeOccupantsCount;
		const managerStr = this.manager && this.manager.currentEmployee ? (
			long ? `, ${this.manager.desc.position}` : `, ${this.manager.positionAbbreviation}`) : "";
		const slavesStr = long ? ` ${this.job().desc.position}${this.capacity !== 1 ? 's' : ''}` : "";
		return `${this.hostedSlaves}${nonEmployees ? `+${nonEmployees}` : ""}/${this.capacity}${slavesStr}${managerStr}`;
	}

	/**
	 * @protected
	 * @param {string} jobName
	 * @returns {App.Entity.Facilities.Job}
	 */
	_createJob(jobName) { /* eslint-disable-line no-unused-vars*/
		return new App.Entity.Facilities.Job();
	}
};

/**
 * Job for a facility with a single job option
 */
App.Entity.Facilities.FacilitySingleJob = class extends App.Entity.Facilities.Job {
	/**
	 * @param {number} ID slave ID
	 * @param {string} [targetPassage] passage to go to
	 * @param {assignmentCallback} [callback]
	 * @param {string} [linkText]
	 * @returns {HTMLAnchorElement}
	 */
	assignmentLinkElement(ID, targetPassage, callback, linkText) {
		linkText = linkText || this.desc.position;
		return App.UI.DOM.assignmentLink(slaveStateById(ID), this.desc.assignment, "",
			(slave, assignment) => {
				if (callback) {
					callback(slave, assignment);
				}
				assignmentTransition(slave, assignment, targetPassage || passage());
			}, linkText);
	}
};

App.Entity.Facilities.SingleJobFacility = class extends App.Entity.Facilities.Facility {
	/**
	 * @param {FC.Data.FacilityDesc} desc defines state variable for this facility
	 * @param {Object.<string, App.Entity.Facilities.Job>} [jobs] job object that are not default
	 * @param {App.Entity.Facilities.ManagingJob} [manager]
	 */
	constructor(desc, jobs, manager) {
		super(desc, jobs, manager);
		if (this.jobs.length !== 1) {
			throw Error(`SingleJobFacility accepts only a single job, but ${this.jobs.length} were provided`);
		}
		this._job = this.job(); // cache the only job
	}

	/**
	 * @override
	 * @protected
	 * @returns {App.Entity.Facilities.FacilitySingleJob}
	 */
	_createJob() {
		return new App.Entity.Facilities.FacilitySingleJob();
	}

	get facilityJob() {
		return this._job;
	}
};

/** Instances of all facility objects */
App.Entity.facilities = {};


code:
/**
 * Creates the facility passage for a facility.
 *
 * Not to be confused with `App.Entity.Facilities.Facility`, which handles the logic.
 */
App.Facilities.Facility = class {
	/**
	 * @param {App.Entity.Facilities.Facility} facility The instance form of the facility. Typically found in `App.Entity.facilities`.
	 * @param {function():void} decommissionHandler
	 */
	constructor(facility, decommissionHandler) {
		this.facility = facility;

		/** @private */
		this._div = document.createElement("div");
		/** @private @type {Array<function():HTMLDivElement>} */
		this._sections = [];
		/** @private @type {FC.IUpgrade[]} */
		this._upgrades = [];
		/** @private @type {FC.Facilities.Rule[]} */
		this._rules = [];

		this._addUpgrades(...this.upgrades);
		this._addRules(...this.rules);

		this._addSections(
			() => this._makeIntro(decommissionHandler),
			() => this._makeExpand(),
			() => this._makeUpgrades(),
			() => this._makeRules(),
			() => this._makeCustomNodes(),
			() => this._makeMenials(),
			() => this._makeStats(),
			() => this._makeAds(),
			() => this._makeSlaves(),
			() => this._makeRename(),
		);
	}

	/**
	 * Puts the different sections together into one passage.
	 *
	 * @private
	 * @returns {DocumentFragment}
	 */
	_assemble() {
		const frag = new DocumentFragment();

		this._sections.forEach(section => frag.append(App.UI.DOM.makeElement("div", section(), ['margin-bottom'])));

		return frag;
	}

	/**
	 * Renders the facility onscreen.
	 *
	 * @returns {HTMLDivElement}
	 */
	render() {
		this._div.append(this._assemble());

		return this._div;
	}

	/**
	 * Refreshes the facility onscreen.
	 *
	 * @returns {void}
	 */
	refresh() {
		App.UI.DOM.replace(this._div, this._assemble());
	}

	/**
	 * Adds new sections to the facility passage.
	 *
	 * @param {...function():HTMLDivElement} args
	 *
	 * @private
	 * @returns {void}
	 */
	_addSections(...args) {
		this._sections.push(...args);
	}

	/**
	 * Adds new purchaseable upgrades.
	 * @param {...FC.IUpgrade} upgrades
	 *
	 * @private
	 * @returns {void}
	 */
	_addUpgrades(...upgrades) {
		this._upgrades.push(...upgrades);
	}

	/**
	 * Adds new rules able to be set by the player.
	 * @param {...FC.Facilities.Rule} rules
	 *
	 * @private
	 * @returns {void}
	 */
	_addRules(...rules) {
		this._rules.push(...rules);
	}

	/**
	 * Sets up the intro scene.
	 *
	 * @param {function():void} decommissionHandler
	 *
	 * @private
	 * @returns {HTMLDivElement}
	 */
	_makeIntro(decommissionHandler) {
		const div = document.createElement("div");

		App.UI.DOM.appendNewElement("h1", div, this.facility.nameCaps);
		App.UI.DOM.appendNewElement("div", div, this.intro, ['scene-intro']);

		if (this.facility.totalEmployeesCount === 0) {
			div.append(App.UI.DOM.makeElement("div", App.UI.DOM.passageLink(`Decommission ${this.facility.name}`, "Main", decommissionHandler), ['indent']));
		}

		return div;
	}

	/**
	 * Allows the facility to be expanded.
	 *
	 * @private
	 * @returns {HTMLDivElement}
	 */
	_makeExpand() {
		const div = document.createElement("div");

		const capacity = typeof V[this.facility.desc.baseName] === 'object' ?
			V[this.facility.desc.baseName].capacity :
			V[this.facility.desc.baseName];
		const maximum = this.expand.maximum || Number.MAX_SAFE_INTEGER;
		const amount = this.expand.amount || 5;
		const cost = this.expand.cost || capacity * 1000 * V.upgradeMultiplierArcology;
		const desc = this.expand.desc ||
			`${this.facility.nameCaps} can support ${this.facility.capacity} slaves. There ${this.facility.hostedSlaves === 1 ? `is` : `are`} currently ${numberWithPluralOne(this.facility.hostedSlaves, "slave")} here.`;

		div.append(desc);

		if (!this.expand.unexpandable) {
			if (capacity < maximum) {
				App.UI.DOM.appendNewElement("div", div, App.UI.DOM.link(`Expand ${this.facility.name}`, () => {
					cashX(forceNeg(cost), "capEx");
					if (typeof V[this.facility.desc.baseName] === 'object') {		// TODO: change this once all facilities are objects
						V[this.facility.desc.baseName].capacity += amount;
					} else {
						V[this.facility.desc.baseName] += amount;
					}
					V.PC.skill.engineering += .1;

					this.refresh();
				}, [], '', `Costs ${cashFormat(cost)} and increases the capacity of ${this.facility.name} by ${amount}.`), ['indent']);
			} else {
				App.UI.DOM.appendNewElement("div", div, App.UI.DOM.disabledLink(`Expand ${this.facility.name}`, [
					`${this.facility.nameCaps} can support a maximum of ${maximum} slaves.`,
				]), ['indent']);
			}
		}

		if (this.facility.totalEmployeesCount > 0) {
			App.UI.DOM.appendNewElement("div", div, this.expand.removeAll || removeFacilityWorkers(this.facility, this.expand.removeManager, this.expand.removeSlave), ['indent']);
		}

		return div;
	}

	/**
	 * Allows the facility to be upgraded.
	 *
	 * @private
	 * @returns {HTMLDivElement}
	 */
	_makeUpgrades() {
		const div = document.createElement("div");

		if (this.upgrades.length > 0) {
			App.UI.DOM.appendNewElement("h2", div, `Upgrades`);

			this._upgrades.forEach(u => {
				const upgrade = new App.Upgrade(u.property, u.tiers, u.object);

				div.append(upgrade.render());
			});
		}

		return div;
	}

	/**
	 * Allows rules to be set up in the facility.
	 *
	 * @private
	 * @returns {HTMLDivElement}
	 */
	_makeRules() {
		const div = document.createElement("div");

		if (this.rules.length > 0 && this.rules.some(rule => rule.prereqs.every(prereq => prereq()))) {
			App.UI.DOM.appendNewElement("h2", div, `Rules`);

			this._rules.forEach(rule => {
				if (rule.prereqs.every(prereq => prereq())) {
					const options = new App.UI.OptionsGroup();
					const option = options.addOption(null, rule.property, rule.object || V);

					rule.options.forEach(o => {
						if (!o.prereqs || o.prereqs.every(prereq => prereq())) {
							option.addValue(o.link, o.value);

							if (o.handler) {
								option.addCallback(o.handler);
							}

							if (o.note) {
								option.addComment(o.note);
							}

							if ((rule.object && _.isEqual(rule.object[rule.property], o.value)) ||
							_.isEqual(V[rule.property], o.value)) {
								App.UI.DOM.appendNewElement("div", div, o.text);
							}
						}
					});

					App.UI.DOM.appendNewElement("div", div, options.render(), ['indent', 'margin-bottom']);
				}

				if (rule.nodes) {
					App.Events.addNode(div, rule.nodes);
				}
			});
		}

		return div;
	}

	/**
	 * Creates any custom nodes and adds them to the facility.
	 *
	 * Custom nodes are always placed between the rules section and the menial slaves section (or where they would be).
	 *
	 * @private
	 * @returns {HTMLDivElement}
	 */
	_makeCustomNodes() {
		const div = document.createElement("div");

		if (this.customNodes) {
			this.customNodes.forEach(node => div.append(node));
		}

		return div;
	}

	/**
	 * Allows menial slaves to be bought, sold, and assigned to the facility.
	 *
	 * @private
	 * @returns {HTMLDivElement}
	 */
	_makeMenials() {
		const div = document.createElement("div");

		if (this.menials) {
			App.UI.DOM.appendNewElement("h2", div, `Menials`);
			App.UI.DOM.appendNewElement("div", div, this.menials, ['margin-bottom']);
		}

		return div;
	}

	/**
	 * Displays a table with statistics relating to the facility.
	 *
	 * @private
	 * @returns {HTMLDivElement}
	 */
	_makeStats() {
		const div = document.createElement("div");

		if (this.stats) {
			App.UI.DOM.appendNewElement("h2", div, `Statistics`);
			App.UI.DOM.appendNewElement("div", div, this.stats, ['margin-bottom']);
		}

		return div;
	}

	/**
	 * Adds a section for managing advertisements to the facility.
	 *
	 * @private
	 * @returns {HTMLDivElement}
	 */
	_makeAds() {
		const div = document.createElement("div");

		if (this.ads) {
			App.UI.DOM.appendNewElement("h2", div, `Advertising`);
			App.UI.DOM.appendNewElement("div", div, this.ads, ['margin-bottom']);
		}

		return div;
	}

	/**
	 * Displays a list of slaves that can be assigned and removed.
	 *
	 * @private
	 * @returns {HTMLDivElement}
	 */
	_makeSlaves() {
		const div = document.createElement("div");

		App.UI.DOM.appendNewElement("h2", div, `Slaves`);
		App.UI.DOM.appendNewElement("div", div, this.slaves || App.UI.SlaveList.stdFacilityPage(this.facility, true), ['margin-bottom']);

		return div;
	}

	/**
	 * Adds a textbox allowing the facility to be renamed.
	 *
	 * @private
	 * @returns {HTMLDivElement}
	 */
	_makeRename() {
		const div = document.createElement("div");

		App.UI.DOM.appendNewElement("h2", div, `Rename`);
		App.UI.DOM.appendNewElement("div", div, App.Facilities.rename(this.facility, () => this.refresh()));

		return div;
	}

	// Getters and Setters

	// Getters that returns a nullish type are used for type checking and autocompletion.

	/**
	 * The text displayed in the intro scene.
	 *
	 * @returns {string}
	 */
	 get intro() {
		return null;
	}

	/**
	 * The facility description in the intro scene.
	 *
	 * @returns {string}
	 */
	 get decorations() {
		return null;
	}

	/**
	 * Adds an option to expand the facility.
	 *
	 * Describes how many slaves are in the facility out of how many are supported by default.
	 *
	 * @returns {FC.Facilities.Expand}
	 */
	get expand() {
		return {};
	}

	/**
	 * Any upgrades available for purchase.
	 *
	 * @returns {FC.IUpgrade[]}
	 */
	get upgrades() {
		return [];
	}

	/**
	 * Any rules able to be set.
	 *
	 * @returns {FC.Facilities.Rule[]}
	 */
	get rules() {
		return [];
	}

	/**
	 * Any menials that are able to be assigned.
	 *
	 * @returns {HTMLDivElement}
	 */
	get menials() {
		return null;
	}

	/**
	 * Any statistics table to display.
	 *
	 * @returns {HTMLDivElement}
	 */
	get stats() {
		return null;
	}

	/**
	 * Any advertisements to display.
	 *
	 * @returns {HTMLDivElement}
	 */
	get ads() {
		return null;
	}

	/**
	 * Any slave selection menu to display.
	 *
	 * Uses the standard list if not defined.
	 *
	 * @returns {HTMLDivElement}
	 */
	get slaves() {
		return null;
	}

	/**
	 * Allows for the addition of any custom nodes to the facility.
	 *
	 * @returns {HTMLDivElement[]}
	 */
	 get customNodes() {
		return [];
	}
};


code:
App.SpecialSlavesProxy = class SpecialSlavesProxy {
	constructor() {
	}

	get Attendant() {
		return slaveStateById(V.AttendantID);
	}
	get Bodyguard() {
		return slaveStateById(V.BodyguardID);
	}
	get Concubine() {
		return slaveStateById(V.ConcubineID);
	}
	get DJ() {
		return slaveStateById(V.djID);
	}
	get Farmer() {
		return slaveStateById(V.FarmerID);
	}
	get HeadGirl() {
		return slaveStateById(V.HeadGirlID);
	}
	get Lurcher() {
		return slaveStateById(V.LurcherID);
	}
	get Madam() {
		return slaveStateById(V.MadamID);
	}
	get Matron() {
		return slaveStateById(V.MatronID);
	}
	get Milkmaid() {
		return slaveStateById(V.MilkmaidID);
	}
	get Nurse() {
		return slaveStateById(V.NurseID);
	}
	get Recruiter() {
		return slaveStateById(V.RecruiterID);
	}
	get Schoolteacher() {
		return slaveStateById(V.SchoolteacherID);
	}
	get Stewardess() {
		return slaveStateById(V.StewardessID);
	}
	get Stud() {
		return slaveStateById(V.StudID);
	}
	get Wardeness() {
		return slaveStateById(V.WardenessID);
	}
	get activeSlave() {
		return V.activeSlave;
	}
};

globalThis.S = new App.SpecialSlavesProxy();


code: new App.DomPassage("StoryCaption", () => { return App.UI.storyCaption(); });


code:
new App.DomPassage("Backwards Compatibility",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Main";
		V.returnTo = "Main";

		App.Update.setNonexistentProperties(V, App.Data.defaultGameStateVariables);

		// resetOnNGPlus contains half of the variables we need, but we use it politely here instead of forcing it so it
		// fills in holes instead of overwriting data.
		App.Update.setNonexistentProperties(V, App.Data.resetOnNGPlus);

		return App.Update.backwardsCompatibility();
	}
);


code:
new App.DomPassage("BirthStorm",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Slave Interact";

		return birth(getSlave(V.AS), {birthStorm: true});
	}
);

new App.DomPassage("csec",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Slave Interact";

		return birth(getSlave(V.AS), {cSection: true});
	}
);

new App.DomPassage("Surrogacy",
	() => {
		V.nextButton = "Continue";

		return App.UI.surrogacy();
	}
);


code:
new App.DomPassage("Costs Budget",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";
		return App.Budget.costs();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Rep Budget",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";
		return App.Budget.rep();
	}, ["jump-to-safe", "jump-from-safe"]
);


code:
new App.DomPassage("Slave Assignments Report",
	() => {
		V.nextLink = "Economics";
		V.nextButton = "Continue";

		const f = document.createDocumentFragment();

		try {
			App.UI.DOM.appendNewElement("h1", f, `${V.arcologies[0].name} Weekly Slave Report - Week ${V.week}`);
			f.append(App.EndWeek.slaveAssignmentReport());
		} finally {
			// whatever happens, stop the end week animation so we at least know there is an error.
			App.UI.EndWeekAnim.end();
		}

		return f;
	}
);

new App.DomPassage("Next Week",
	() => {
		App.EndWeek.nextWeek();

		// simulate <<goto "Main">> macro behaviour
		// @ts-ignore
		setTimeout(() => Engine.play("Main"), Engine.minDomActionDelay);

		return document.createDocumentFragment();
	}
);

new App.DomPassage("Economics",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Scheduled Event";

		return App.EndWeek.economics();
	}
);

new App.DomPassage("Security Force Naming-Colonel",
	() => {
		V.nextButton = " ";
		return App.UI.securityForceNamingColonel();
	}
);


code:
/* ### Non Random Events ### */

new App.DomPassage("conflictReport",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Scheduled Event";
		V.encyclopedia = "Battles";
		return App.Events.conflictReport();
	}
);
new App.DomPassage("conflictHandler",
	() => {
		return App.Events.conflictHandler();
	}
);

/* ### Random Events ### */

new App.DomPassage("Random Individual Event",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Next Week";

		return App.Events.playRandomIndividualEvent();
	}
);

new App.DomPassage("Random Nonindividual Event",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Random Individual Event";

		return App.Events.playRandomNonindividualEvent();
	}
);

/* ### Scheduled Events ### */

new App.DomPassage("Scheduled Event",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Scheduled Event"; // return to self; playNonrandomEvent will forward automatically when necessary

		return App.Events.playNonrandomEvent();
	}
);

/* ### Player Events ### */

new App.DomPassage("Gameover",
	() => {
		return App.Events.Gameover();
	}
);


code:
/* ### Standard Facilities ### */
new App.DomPassage("Arcade", () => { return new App.Facilities.Arcade.arcade().render(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Brothel", () => { return new App.Facilities.Brothel.brothel().render(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Cellblock", () => { return new App.Facilities.Cellblock.cellblock().render(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Clinic", () => { return new App.Facilities.Clinic.clinic().render(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Club", () => { return new App.Facilities.Club.club().render(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Farmyard", () => { return new App.Facilities.Farmyard.farmyard().render(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Head Girl Suite", () => { return new App.Facilities.HGSuite.headGirlSuite().render(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Incubator", () => { return App.UI.incubator(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Master Suite", () => { return new App.Facilities.MasterSuite.masterSuite().render(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Nursery", () => { return new App.Facilities.Nursery.nursery().render(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Pit", () => { return new App.Facilities.Pit.pit().render(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Schoolroom", () => { return new App.Facilities.Schoolroom.schoolroom().render(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Servants' Quarters", () => { return new App.Facilities.ServantsQuarters.servantsQuarters().render(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Spa", () => { return new App.Facilities.Spa.spa().render(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Transport Hub", () => { return App.SecExp.transportHub.GUI(); });
new App.DomPassage("Weapons Manufacturing", () => { return App.SecExp.weapManu.GUI(); }, ["jump-to-safe", "jump-from-safe"]);
new App.DomPassage("securityHQ", () => { return App.SecExp.secHub.GUI(); }, ["jump-to-safe", "jump-from-safe"]);
new App.DomPassage("secBarracks", () => { return App.SecExp.barracks.GUI(); }, ["jump-to-safe", "jump-from-safe"]);
new App.DomPassage("riotControlCenter", () => { return App.SecExp.riotCenter.GUI(); }, ["jump-to-safe", "jump-from-safe"]);
new App.DomPassage("propagandaHub", () => { return App.SecExp.propHub.GUI(); }, ["jump-to-safe", "jump-from-safe"]);

/* ### Slave Interact Facilities ### */
new App.DomPassage("Wardrobe",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Manage Penthouse";

		return App.UI.WardrobeShopping();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Salon",
	() => {
		V.nextButton = "Confirm changes";
		V.nextLink = "Slave Interact";
		V.encyclopedia = "The Auto Salon";

		return App.UI.salon(getSlave(V.AS));
	}, ["jump-from-safe"]
);

new App.DomPassage("Body Modification",
	() => {
		V.nextButton = "Confirm changes";
		V.nextLink = "Slave Interact";
		V.encyclopedia = "The Studio";

		return App.UI.bodyModification(getSlave(V.AS));
	}, ["jump-from-safe"]
);

/* ### Special Facilities ### */
new App.DomPassage("Agent Select",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Neighbor Interact";
		V.encyclopedia = "Agents";

		return App.Facilities.AgentSelect();
	}, ["jump-from-safe"]
);

new App.DomPassage("Attendant Select",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Spa";
		V.encyclopedia = "Attendant";

		return App.Facilities.AttendantSelect();
	}, ["jump-to-safe", "jump-hidden", "jump-from-safe"]
);

new App.DomPassage("BG Select",
	() => {
		V.nextButton = "Back to Main";
		V.nextLink = "Main";
		V.encyclopedia = "Bodyguard";

		return App.Facilities.BGSelect();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Concubine Select",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Master Suite";
		V.encyclopedia = "Concubine";

		return App.Facilities.ConcubineSelect();
	}, ["jump-to-safe", "jump-hidden", "jump-from-safe"]
);

new App.DomPassage("DJ Select",
	() => {
		return App.Facilities.DJSelect();
	}, ["jump-to-safe", "jump-hidden", "jump-from-safe"]
);

new App.DomPassage("Head Girl Select",
	() => {
		V.nextButton = "Back to Main";
		V.nextLink = "Main";
		V.encyclopedia = "Head Girl";

		return App.Facilities.HGSelect();
	}, ["jump-to-safe", "jump-hidden", "jump-from-safe"]
);

new App.DomPassage("Madam Select",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Brothel";
		V.encyclopedia = "Madam";

		return App.Facilities.MadamSelect();
	}, ["jump-to-safe", "jump-hidden", "jump-from-safe"]
);

new App.DomPassage("Milkmaid Select",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Dairy";
		V.encyclopedia = "Milkmaid";

		return App.Facilities.MilkmaidSelect();
	}, ["jump-to-safe", "jump-hidden", "jump-from-safe"]
);

new App.DomPassage("Nurse Select",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Clinic";
		V.encyclopedia = "Nurse";

		return App.Facilities.NurseSelect();
	}, ["jump-to-safe", "jump-hidden", "jump-from-safe"]
);

new App.DomPassage("Recruiter Select",
	() => {
		V.nextButton = "Back to Main";
		V.nextLink = "Main";
		V.encyclopedia = "Recruiter";

		return App.Facilities.RecruiterSelect();
	}, ["jump-to-safe", "jump-hidden", "jump-from-safe"]
);

new App.DomPassage("Schoolteacher Select",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Schoolroom";
		V.encyclopedia = "Schoolteacher";

		return App.Facilities.SchoolTeacherSelect();
	}, ["jump-to-safe", "jump-hidden", "jump-from-safe"]
);

new App.DomPassage("Stewardess Select",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Servants' Quarters";
		V.encyclopedia = "Stewardess";

		return App.Facilities.StewardessSelect();
	}, ["jump-to-safe", "jump-hidden", "jump-from-safe"]
);

new App.DomPassage("Wardeness Select",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Cellblock";
		V.encyclopedia = "Wardeness";

		return App.Facilities.WardenessSelect();
	}, ["jump-to-safe", "jump-hidden", "jump-from-safe"]
);

new App.DomPassage("Farmer Select",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Farmyard";
		V.encyclopedia = "Farmer";

		return App.Facilities.FarmerSelect();
	}, ["jump-to-safe", "jump-hidden", "jump-from-safe"]
);

new App.DomPassage("Matron Select",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Nursery";
		V.encyclopedia = "Matron";

		return App.Facilities.MatronSelect();
	}, ["jump-to-safe", "jump-hidden", "jump-from-safe"]
);

new App.DomPassage("Rules Assistant",
	() => {
		const div = document.createElement("div");
		App.RA.options(div);
		return div;
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Rules Assistant Summary",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Rules Assistant";
		V.returnTo = "Rules Assistant";
		return App.RA.summary();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Toy Shop",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";

		return App.UI.toyShop();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Dispensary",
	() => {
		V.nextButton = "Back"; V.nextLink = "Manage Penthouse"; V.encyclopedia = "The Pharmaceutical Fab.";

		return App.UI.dispensary();
	}, ["jump-to-safe", "jump-from-safe"]
);


code:
/* ### Central Slave Interact ### */
new App.DomPassage("Slave Interact",
	() => {
		V.nextButton = "Confirm changes";
		V.nextLink = "Main";

		return App.UI.SlaveInteract.mainPage(getSlave(V.AS));
	}, ["jump-from-safe"]
);

/* ### Single Interaction ### */
new App.DomPassage("SlaveOnSlaveFeeding",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Slave Interact";

		return App.UI.SlaveInteract.slaveOnSlaveFeedingSelection(getSlave(V.AS));
	}
);

new App.DomPassage("KillSlave", () => App.UI.SlaveInteract.killSlave(getSlave(V.AS)));

new App.DomPassage("Fat Grafting",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Remote Surgery";

		return App.UI.SlaveInteract.fatGraft(getSlave(V.AS));
	}
);

new App.DomPassage(
	"Slave Slave Swap Workaround",
	() => {
		V.nextButton = "Abort Operation";
		V.nextLink = "Main";
		return bodySwapSelection(getSlave(V.AS));
	}
);

new App.DomPassage(
	"Husk Slave Swap Workaround",
	() => {
		V.nextButton = "Abort Operation";
		if (V.activeSlave.tankBaby !== 3) {
			V.nextLink = "Scheduled Event";
			V.returnTo = "Scheduled Event";
		} else {
			V.nextLink = "Main";
			V.returnTo = "Incubator";
		}
		return huskSwapSelection(V.activeSlave);
	}
);

new App.DomPassage(
	"Incubator Retrieval Workaround",
	() => {
		V.returnTo = "Main";
		V.nextLink = "Incubator";
		V.nextButton = "Continue";
		return App.UI.incubatorRetrievalWorkaround();
	}
);

new App.DomPassage(
	"Nursery Retrieval Workaround",
	() => {
		V.returnTo = "Main";
		V.nextLink = "Nursery";
		V.nextButton = "Continue";
		return App.Facilities.Nursery.retrievalWorkaround();
	}
);

new App.DomPassage(
	"Agent Company",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Neighbor Interact";
		return App.UI.SlaveInteract.agentCompany(getSlave(V.AS));
	}, ["jump-from-safe"]
);

new App.DomPassage(
	"Surgery Degradation",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Remote Surgery";
		return App.UI.SlaveInteract.surgeryDegradation(getSlave(V.AS));
	}, ["jump-from-safe"]
);

new App.DomPassage(
	"Remote Surgery",
	() => {
		V.nextButton = "Confirm changes";
		V.nextLink = "Slave Interact";
		return App.UI.SlaveInteract.remoteSurgery(getSlave(V.AS));
	}, ["jump-from-safe"]
);

new App.DomPassage(
	"Import Slave",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Slave Interact";
		return App.UI.SlaveInteract.importSlave();
	}, ["jump-from-safe"]
);

new App.DomPassage(
	"Export Slave",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Slave Interact";
		return App.UI.SlaveInteract.exportSlave(getSlave(V.AS));
	}, ["jump-from-safe"]
);

new App.DomPassage(
	"Cheat Edit JS",
	() => {
		V.nextButton = " ";
		return App.UI.SlaveInteract.cheatEditSlave(getSlave(V.AS));
	}
);

new App.DomPassage(
	"Cheat Edit JS Apply",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Slave Interact";
		const el = new DocumentFragment();
		App.UI.DOM.appendNewElement("p", el, `You perform the dark rituals, pray to the dark gods, and sell your soul for the power to change and mold slaves to your will.`);
		App.UI.DOM.appendNewElement("p", el, `This slave has been changed forever and you have lost a bit of your soul, YOU CHEATER!`);
		return el;
	}, ["jump-from-safe"]
);

new App.DomPassage("MpregSelf",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";

		return MpregSelf();
	}
);

new App.DomPassage("BreedingTest",
	() => {
		V.nextButton = "Confirm changes";
		V.nextLink = "Slave Interact";
		return App.UI.DOM.makeElement("div", eliteBreedingExam(getSlave(V.AS)));
	}
);

new App.DomPassage("Aztec Slave Sacrifice Penance",
	() => {
		V.nextButton = "Back to Main";
		V.nextLink = "Main";
		V.encyclopedia = "Aztec Revivalism";
		return App.UI.SlaveInteract.aztecSlaveSacrificePenance(getSlave(V.AS));
	}
);

new App.DomPassage("Aztec Slave Sacrifice Life",
	() => {
		V.nextButton = "Back to Main";
		V.nextLink = "Main";
		V.encyclopedia = "Aztec Revivalism";
		return App.UI.SlaveInteract.aztecSlaveSacrificeLife(getSlave(V.AS));
	}
);

new App.DomPassage("Abort",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Slave Interact";
		return App.Interact.abort(getSlave(V.AS));
	}
);

new App.DomPassage("FAnimalImpreg",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Slave Interact";
		return App.Interact.fAnimalImpreg(getSlave(V.AS));
	}
);

new App.DomPassage("FSlaveSlaveDick",
	() => {
		V.nextLink = "Slave Interact";
		V.nextButton = "Back";
		return App.Interact.fSlaveSlaveDick(getSlave(V.AS));
	}
);

new App.DomPassage("FSlaveSlaveVag",
	() => {
		V.nextLink = "Slave Interact";
		V.nextButton = "Back";
		return App.Interact.fSlaveSlaveVag(getSlave(V.AS));
	}
);

new App.DomPassage("FSlaveSlaveAss",
	() => {
		V.nextLink = "Slave Interact";
		V.nextButton = "Back";
		return App.Interact.fSlaveSlaveAss(getSlave(V.AS));
	}
);

new App.DomPassage("Matchmaking",
	() => {
		if (lastVisited("Child Interact") === 1) {
			V.nextLink = "Incubator";
		} else {
			V.nextLink = "Slave Interact";
		}
		V.nextButton = "Continue";
		return App.Interact.matchmaking(getSlave(V.AS));
	}
);

new App.DomPassage("FSelf",
	() => {
		V.nextLink = "Main";
		V.nextButton = "Back";
		return App.Interact.fSelf();
	}
);

new App.DomPassage("FMarry",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Slave Interact";
		return App.Interact.fMarry(getSlave(V.AS));
	}
);

new App.DomPassage("Discard Confirm",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Slave Interact";
		return App.Interact.discard(getSlave(V.AS));
	}
);

new App.DomPassage("Sell Slave",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Slave Interact";
		return App.Interact.sellSlave(getSlave(V.AS));
	}
);

new App.DomPassage("Brothel Assignment Scene",
	() => {
		return App.Facilities.Brothel.assignmentScene(getSlave(V.AS));
	}
);

new App.DomPassage("Industrial Dairy Assignment Scene",
	() => {
		return App.Facilities.Dairy.industrialAssignmentScene(getSlave(V.AS));
	}
);

new App.DomPassage("Free Range Dairy Assignment Scene",
	() => {
		return App.Facilities.Dairy.freeRangeAssignmentScene(getSlave(V.AS));
	}
);

new App.DomPassage("Personal Attention Select",
	() => {
		return App.UI.Player.personalAttention();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Agent Assignment Scene",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Neighbor Interact";
		return App.UI.SlaveInteract.agentAssignmentScene();
	}
);

new App.DomPassage("Artificial Insemination",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Remote Surgery";
		return App.UI.SlaveInteract.artificialInsemination();
	}
);

new App.DomPassage("Neighbor Interact",
	() => {
		V.nextLink = "Main";
		V.nextButton = "Back";
		return App.Neighbor.Interact();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Slave Slave Swap",
	() => {
		V.nextButton = "Continue";
		return App.UI.SlaveInteract.slaveSlaveSwap();
	}
);

new App.DomPassage("Husk Slave Swap",
	() => {
		V.nextButton = "Continue";
		return App.UI.SlaveInteract.huskSlaveSwap();
	}
);

new App.DomPassage("Prosthetics Configuration",
	() => {
		return App.UI.prostheticsConfigPassage();
	}
);

new App.DomPassage("Multiple Organ Implant",
	() => {
		return App.UI.multipleOrganImplant();
	}
);

new App.DomPassage("Surgery Death",
	() => {
		V.nextLink = "Main";

		const slave = getSlave(V.AS);
		removeSlave(slave);

		const f = new DocumentFragment();
		App.Events.addParagraph(f, [`${slave.slaveName} <span class="health dec">has died from complications of surgery.</span>`]);
		return f;
	}, ["jump-from-safe"]
);

new App.DomPassage("Walk Past List",
	() => {
		V.nextButton = "Back to Main";
		V.nextLink = "Main";
		return App.UI.walkPastAll();
	}
);
new App.DomPassage("Find Slave",
	() => {
		V.nextButton = "Back to Main";
		V.nextLink = "Main";
		return App.UI.findSlave();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Analyze Pregnancy",
	() => {
		V.nextButton = "Continue";
		return App.UI.analyzePregnancy();
	}, ["jump-from-safe"]
);

new App.DomPassage("Analyze PC Pregnancy",
	() => {
		V.nextButton = "Continue";
		return App.UI.analyzePCPregnancy();
	}, ["jump-from-safe"]
);

new App.DomPassage("Surrogacy Workaround",
	() => {
		V.nextButton = "Cancel";
		return App.UI.surrogacyWorkaround();
	}
);

new App.DomPassage("Pit Workaround",
	() => {
		V.nextButton = V.pit.slavesFighting.length > 1 ? "Finish" : "Cancel";
		V.nextLink = "Pit";
		return App.Facilities.Pit.workaround();
	}
);

new App.DomPassage("Cloning Workaround",
	() => {
		V.nextButton = "Cancel";
		V.nextLink = "Gene Lab";
		return App.UI.cloningWorkaround();
	}
);

new App.DomPassage("Ova Transplant Workaround",
	() => {
		V.nextButton = "Cancel";
		return App.UI.ovaTransplantWorkaround();
	}
);

new App.DomPassage("Subordinate Targeting",
	() => {
		V.nextButton = "Back";
		V.nextLink = V.returnTo;
		return App.UI.subordinateTargeting();
	}
);


code:
new App.DomPassage("Alpha disclaimer", () => { return App.Intro.alphaDisclaimer(); });

new App.DomPassage("New Game Plus", () => { return App.Intro.newGamePlus(); });

new App.DomPassage("PC Body Intro", () => { return App.Intro.PCBodyIntro(); });

new App.DomPassage("Intro Summary", () => { return App.Intro.summary(); });

new App.DomPassage("Acquisition",
	() => {
		V.encyclopedia = "How to Play";
		return App.Intro.acquisition();
	}
);

new App.DomPassage("Customize Slave Trade", () => { return App.Intro.CustomSlaveTrade(); });

new App.DomPassage("Starting Girls", () => { return App.StartingGirls.passage(); });

new App.DomPassage("Gender Intro", () => { return App.Intro.genderIntro(); });

new App.DomPassage("PC Experience Intro", () => { return App.Intro.PCExperienceIntro(); });

new App.DomPassage("Economy Intro", () => { return App.Intro.economyIntro(); });

new App.DomPassage("Trade Intro", () => { return App.Intro.tradeIntro(); });

new App.DomPassage("Extreme Intro", () => { return App.Intro.extremeIntro(); });

new App.DomPassage("Slave Age Intro", () => { return App.Intro.slaveAgeIntro(); });

new App.DomPassage("PC Rumor Intro", () => { return App.Intro.PCRumorIntro(); });

new App.DomPassage("Terrain Intro", () => { return App.Intro.terrainIntro(); });

new App.DomPassage("Takeover Target", () => { return App.Intro.takeoverTarget(); });

new App.DomPassage("PC Preg Intro", () => { return App.Intro.PCPregIntro(); });

new App.DomPassage("PC Appearance Intro", () => { return App.Intro.PCAppearanceIntro(); });

new App.DomPassage("Location Intro", () => { return App.Intro.locationIntro(); });


code:
new App.DomPassage("Main",
	() => {
		V.nextButton = "END WEEK";
		V.nextLink = "End Week";
		V.encyclopedia = "How to Play";

		return App.MainView.full();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Future Society", () => { return App.UI.fsPassage(); }, ["jump-to-safe", "jump-from-safe"]);

new App.DomPassage("Manage Penthouse",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";
		V.encyclopedia = "What the Upgrades Do";
		return App.UI.managePenthouse();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Manage Personal Affairs",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";
		V.encyclopedia = "Being in Charge";
		return App.UI.managePersonalAffairs();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("retire",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";
		return retireScene(getSlave(V.AS));
	}
);

new App.DomPassage("Change Language",
	() => {
		V.nextButton = "Confirm changes";
		V.nextLink = "Main";
		return App.Arcology.changeLanguage();
	}, ["jump-from-safe"]
);

new App.DomPassage("Neighbor Arcology Cheat",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "MOD_Edit Neighbor Arcology Cheat Datatype Cleanup";
		return App.UI.Cheat.neighbors();
	}
);

new App.DomPassage("Manage Corporation",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";
		V.encyclopedia = "The Corporation";
		return App.Corporate.manage();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Manage Arcology",
	() => {
		return App.UI.manageArcology();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Policies",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";
		V.encyclopedia = "Future Societies";
		return App.UI.policies();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Universal Rules",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Manage Penthouse";
		return App.UI.universalRules();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Farmyard Animals",
	() => {
		return App.Facilities.Farmyard.animals();
	}
);

new App.DomPassage("Elective Surgery",
	() => {
		return App.UI.electiveSurgery();
	}
);

new App.DomPassage("Brothel Advertisement",
	() => {
		return App.Facilities.Brothel.ads();
	}, ["jump-from-safe"]
);

new App.DomPassage("Club Advertisement",
	() => {
		return App.Facilities.Club.ads();
	}, ["jump-from-safe"]
);

new App.DomPassage("Coursing Association",
	() => {
		V.nextButton = "Back to Main";
		V.nextLink = "Main";
		return App.UI.coursingAssociation();
	}, ["jump-from-safe"]
);

new App.DomPassage("Personal assistant options",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";
		return App.UI.personalAssistantOptions();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Assistant Appearance Pack Two",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Personal assistant options";
		return App.UI.assistantAppearancePackTwo();
	}
);

new App.DomPassage("The Black Market",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";
		V.returnTo = "Main";
		V.encyclopedia = "The Black Market";
		return App.UI.theBlackMarket();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("FirebaseTour",
	() => {
		V.nextButton = "Return to Operations";
		V.nextLink = "Firebase";
		return App.UI.firebaseTour();
	}
);

new App.DomPassage("Prosthetic Lab",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Manage Penthouse";
		return App.UI.prostheticLab();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Gene Lab",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Manage Penthouse";
		V.encyclopedia = "The Pharmaceutical Fab.";
		return App.UI.geneLab();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Breeder Proposal",
	() => {
		V.nextButton = " ";
		V.nextLink = "Main";
		return App.UI.breederProposal();
	}
);

new App.DomPassage("Implant Manufactory",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Manage Penthouse";
		V.encyclopedia = "The Pharmaceutical Fab.";
		return App.UI.implantManufactory();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Organ Farm",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Manage Penthouse";
		V.encyclopedia = "The Pharmaceutical Fab.";
		return App.UI.organFarm();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Barracks",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";
		return App.UI.barracks();
	}
);

new App.DomPassage("editSF",
	() => {
		V.nextButton = `Back to ${V.SF.Lower}'s Firebase`;
		V.nextLink = "Firebase";
		return App.UI.editSF();
	}
);

new App.DomPassage("Firebase",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";
		return App.UI.FireBase();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("edicts",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";
		return App.SecExp.edicts();
	}, ["jump-to-safe", "jump-from-safe"]
);


code:
new App.DomPassage("Options",
	() => {
		if (lastVisited("Slave Interact") === 1) {
			V.storedLink = "Slave Interact";
		} else {
			V.storedLink = "Main";
		}

		V.nextButton = "Back";
		V.nextLink = V.storedLink;
		V.encyclopedia = "How to Play";

		return App.UI.optionsPassage();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Description Options",
	() => {
		V.nextButton = "Back";
		if (V.storedLink !== "Slave Interact") {
			if (lastVisited("Slave Interact") === 1) {
				V.storedLink = "Slave Interact";
			} else {
				V.storedLink = "Options";
			}
		}
		V.nextLink = V.storedLink;

		return App.UI.descriptionOptions();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Summary Options",
	() => {
		V.nextButton = "Back";
		if (V.storedLink !== "Slave Interact" && V.storedLink !== "Main") {
			if (lastVisited("Main") === 1) {
				V.storedLink = "Main";
			} else {
				V.storedLink = "Options";
			}
		}
		V.nextLink = V.storedLink;
		V.passageSwitchHandler = App.EventHandlers.optionsChanged;

		return App.UI.summaryOptions();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Hotkey Settings",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";

		return App.UI.Hotkeys.settings();
	}, ["jump-to-safe", "jump-from-safe"]
);

new App.DomPassage("Edit Genetics",
	() => {
		return App.UI.editGenetics();
	}, ["jump-from-safe"]
);

new App.DomPassage("Variable Difference",
	() => {
		V.nextButton = " "; /* disable the nextButton. Forces the user to use the "Go Back" option contained in the function */
		return App.UI.variableDifference();
	}
);

new App.DomPassage("MOD_Edit Arcology Cheat",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "MOD_Edit Arcology Cheat Datatype Cleanup";
		return App.UI.Cheat.arcologyPassage();
	}
);

new App.DomPassage("MOD_Edit Arcology Cheat Datatype Cleanup",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Main";
		return App.UI.Cheat.arcologyCheatDatatypeCleanup();
	}
);

new App.DomPassage("MOD_Edit Neighbor Arcology Cheat Datatype Cleanup",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Main";
		return App.UI.Cheat.neighborArcologyCheatDatatypeCleanup();
	}
);

new App.DomPassage("PCCheatMenu",
	() => {
		V.nextButton = "Apply";
		V.nextLink = "PCCheatMenuCheatDatatypeCleanup";
		V.encyclopedia = "Design Your Master";
		return App.UI.Cheat.PCCheatMenu();
	}
);

new App.DomPassage("PCCheatMenuCheatDatatypeCleanup",
	() => {
		V.nextButton = "Continue";
		V.nextLink = "Manage Personal Affairs";
		return App.UI.Cheat.PCCheatMenuCheatDatatypeCleanup();
	}
);


code:
new App.DomPassage("Market",
	() => {
		const span = App.UI.DOM.makeElement("span", App.Markets[V.market.slaveMarket]());
		span.id = "slave-markets";
		return span;
	}
);

new App.DomPassage("Buy Slaves",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Main";
		V.encyclopedia = "Obtaining Slaves";

		return App.UI.market();
	}, ["jump-from-safe", "jump-to-safe"]
);

new App.DomPassage("Bulk Slave Intro", () => App.Markets.bulkSlaveIntro());

new App.DomPassage("Underperforming Slaves",
	() => {
		V.nextButton = "Back";
		V.nextLink = "Buy Slaves";
		return App.Underperformers.passage();
	}, ["jump-from-safe", "jump-to-safe"]
);


code:
App.Corporate.InitConstants = function() {
	App.Corporate.payoutMinimumCash = 1000000;
	App.Corporate.payoutCorpValueMultiplier = 0.05;
	App.Corporate.dividendOptions = [0.75, 0.5, 0.25, 0.1, 0.05];
	App.Corporate.stockSplits = [
		{
			'newStocks': 3,
			'oldStocks': 2,
			'weeks': 1,
			'cost': 7.5
		},
		{
			'newStocks': 2,
			'weeks': 1,
			'cost': 10
		},
		{
			'newStocks': 3,
			'weeks': 2,
			'cost': 12.5
		},
		{
			'newStocks': 4,
			'weeks': 3,
			'cost': 15
		},
		{
			'oldStocks': 4,
			'weeks': 1,
			'cost': 15
		},
		{
			'oldStocks': 10,
			'weeks': 2,
			'cost': 25
		},
	];
	/*
	center/range object: The center value is the average or center value of the property, while range is the value range (centered on center). So if center is 1 and range is 0.5, the value can be anything from 0.75 to 1.25.

	== Base ==
	id: The variable ID for identifying the department.
	name: The display name of the department.
	focusDescription: The descriptive phrase of what the department does:
	The department focuses on {breaking slaves}.

	sizeCost: How much a single development of the department costs in thousands.
	maintenance: (object) Defines the amount paid each week.
		linear: How much a single development costs to maintain each week in thousands.
		quadratic: Development maintenance increases quadratically; this is the multiplier on the squared development component.
		category: Which division maintenance category this division belongs to. Multiple divisions in a category increase overhead costs exponentially.
	founding: (object) Defines information regarding the founding of the division.
	corporateCash: Defines how much it costs the player to *found the corporation* with this department with a 2-to-1 split, in thousands. This will be the corporation's starting cash, from which the development and slaves will be purchased.
	startingSize: How developed the department is upon founding, including how many active slaves the department starts with (Note that acquiring divisions don't start with slaves)
	merger: A list of companies that can be purchased during the merger event. The event will collect all of these company objects associated with owned divisions and pick some at random.
		name: The generic name of the company that is being purchased. This will have "the" put in front of it, so it must fit with the sentence "The {name} will be bought."
		cost: How much, in thousands, it will cost to purchase this company. Default: 50.
		text: An object with the flavor text of the event.
			trouble: A description of the company and its troubles. Will be in the sentence "The first company is {trouble}"
			acquire: The text that will be shown when the player chooses to acquire the company. Will be in the sentence "You acquire the {name}{acquire}".
		result: The functional result of acquiring this company.
			development: The number of developments that will be added to this division.
			slaves: the number of activeSlaves that will be added to this division. If the division doesn't have enough room to store them you will have to purchase more development by the end of the week or they will be sold off. *WARNING* Do not provide this property if the division cannot have activeSlave set (Acquiring divisions, for example)

	== Acquiring ==
	An acquiring division generates new slaves. It can always sell to the market but cannot buy from the market.
	slaveValue: How much value is added to a slave when it becomes a Held slave.
	acquiring: (center/range) The percentage of development that can acquire a slave each week.
	mercenaryHelp: (object) If present, defines how and when your mercenaries (not SF) assist this division in acquiring slaves.
	level: What level the mercenaries must be above in order to assist.
	cost: How much each level of mercenary above level costs the department for this assistance, in thousands.
	nextDivision: (list) The ids of the divisions that acquired slaves can be sent directly to. Note that if any of these divisions can send slaves to a listed division, that division will block this division from sending to it. For example, if A->[B,C] and B->C, then A->C can only happen if B *has not* been founded. Once B is founded, slaves from A must proceed A->B->C and cannot skip to C.

	==Processing==
	A processing division enhances the value of slaves. It can always buy from the market and sell to the market.
	addedValue: How much value is added to a slave when it becomes a Held slave.
	processing: (center/range) The percentage of active slaves that become held slaves each week.
	slaveProcessType: (text object) The verb used for the completion of the processing done on slaves. It will be used to explain when a slave moves from processed to held.
	present: The department can {break} 5 slaves.
	past:The department {broke} 5 slaves.
	slaveProcessDescription: The descriptive phrase for what sort of processing is being done to the division's slaves.
	present: the department is {breaking} 5 slaves.
	future: the department will {break} 5 slaves.
	past: Over the past week, the department {broke} 5 slaves.
	market: The department bought {fresh slaves} from the market.
	nextDivision: (list) The ids of the divisions that improved slaves can be sent directly to. Note that if any of these divisions can send slaves to a listed division, that division will block this division from sending to it. For example, if A->[B,C] and B->C, then A->C can only happen if B *has not* been founded. Once B is founded, slaves from A must proceed A->B->C and cannot skip to C.

	==Working==
	A working division produces revenue by working slaves. It can always buy from the market but cannot sell to the market.
	attrition: (center/range) The percentage of active slaves that become no longer workable
	revenue: (center/range) The amount of money generated by a slave.
	slaveProcessDescription: The descriptive phrase for what sort of work is being done by the division's slaves and how they are removed from the workforce.
	present: the department is {exploiting} 5 slaves.
	future: the department will {exploit} 5 slaves.
	past: Over the past week, the department {wore out} 5 slaves.
	market: The department bought {menials} from the market.

	*/
	App.Corporate.divisionList = [
		new App.Corporate.Division.Acquiring( {
			'id': 'Extra',
			'name': 'Extralegal Enslavement',
			'focusDescription': 'capturing and enslaving prisoners',
			'sizeCost': 20,
			'slaveValue': 10,
			'acquiring': {
				'center': 1,
				'range': 0.8,
			},
			'maintenance': {
				'linear': 7.5,
				'quadratic': 5,
				'category': 'acquisition',
			},
			'founding': {
				'corporateCash': 100,
			},
			'mercenaryHelp': {
				'level': 2,
				'cost': 0.05
			},
			'nextDivision': [
				"Break",
				"Arcade"
			],
			'merger': [
				{
					'name': 'mercenary company',
					'cost': 50,
					'text': {
						'trouble': 'a troubled mercenary company. After a spate of failed operations, the rank and file deposed their management and are now looking to merge with a better-run organization. The old leadership did not engage in enslavement activities, but the mercenaries have decided to turn their talents towards slaving for profit in conflict zones.',
						'acquire': ' and ease their transition to conflict zone slaving.'
					},
					'result': {
						'development': 5
					}
				}
			]
		}),
		new App.Corporate.Division.Acquiring( {
			'id': 'Legal',
			'name': 'Legal Enslavement',
			'focusDescription': 'legal enslavement',
			'sizeCost': 25,
			'slaveValue': 15,
			'acquiring': {
				'center': 1,
				'range': 0.5,
			},
			'maintenance': {
				'linear': 12,
				'quadratic': 6,
				'category': 'acquisition',
			},
			'founding': {
				'corporateCash': 125,
			},
			'nextDivision': [
				"Train",
				"Menial",
				"Surgery"
			],
			'merger': [
				{
					'name': 'telemarketing firm',
					'cost': 50,
					'text': {
						'trouble': 'an old world telemarketing firm. In an attempt to survive in the changing economy, it turned its focus towards convincing desperate, ignorant, or stupid people from the old world to accept voluntary enslavement, through a variety of entrapment procedures. It failed to make the transition successfully, but you could certainly continue its operations under your aegis.',
						'acquire': ' and clean out the deadwood, getting the business in Free Cities shape.'
					},
					'result': {
						'development': 5
					}
				}
			]
		}),
		new App.Corporate.Division.Processing( {
			'id': 'Break',
			'name': 'Slave Breaking',
			'focusDescription': 'breaking slaves',
			'sizeCost': 10,
			'addedValue': 5,
			'processing': {
				'center': 0.3,
				'range': 0.3,
			},
			'maintenance': {
				'linear': 0.45,
				'quadratic': 0.6,
				'category': 'processing',
			},
			'founding': {
				'corporateCash': 25,
			},
			'slaveProcessType': {
				'present': 'break',
				'past': 'broke'
			},
			'slaveProcessDescription': {
				'present': 'attempting to break',
				'future': 'break',
				'past': 'broken',
				'market': {
					single: 'slave that needs breaking',
					plural: 'slaves that need breaking'
				}
			},
			'nextDivision': [
				"Train",
				"Menial",
				"Surgery"
			],
			'merger': [
				{
					'name': 'slave breaking facility',
					'cost': 50,
					'text': {
						'trouble': 'a small slave breaking firm. Despite a great need for obedient slaves the owner of this little establishment was unable to turn a profit. The facility has everything a respecting slave breaking could ever need, it seems the owner simply lacked the right character to apply effective breaking techniques. Your corporation knows perfectly well what it takes to achieve obedience and the assets can be put to great use.',
						'acquire': ' and staff it with qualified personnel to make use of the new assets.'
					},
					'result': {
						'development': 5,
						'slaves': 5
					}
				}
			]
		}),
		new App.Corporate.Division.Processing( {
			'id': 'Surgery',
			'name': 'Slave Modifications',
			'focusDescription': 'physical slave modifications',
			'sizeCost': 20,
			'addedValue': 8,
			'processing': {
				'center': 0.5,
				'range': 0.2,
			},
			'maintenance': {
				'linear': 2.4,
				'quadratic': 1,
				'category': 'processing',
			},
			'founding': {
				'corporateCash': 55,
			},
			'slaveProcessType': {
				'present': 'modify',
				'past': 'finished working on'
			},
			'slaveProcessDescription': {
				'present': 'working on',
				'future': 'work on',
				'past': 'improved',
				'market': {
					single: 'slave that could use some work done to their body',
					plural: 'slaves that could use some work done to their bodies'
				}
			},
			'nextDivision': [
				"Train",
				"Dairy"
			],
			'merger': [
				{
					'name': 'medical clinic',
					'cost': 50,
					'text': {
						'trouble': "a small medical clinic. Medical malpractice continues to be a major source of trouble for surgeons in the old world. A plastic surgeon has just suffered defeat in a major legal case over implant ruptures. He's looking to make the transition to a more enlightened area, along with his inventory and staff.",
						'acquire': " and the surgeon decides to retire on the proceeds of the sale, wishing your corporation well. It was a smart decision; he can retire to the Free Cities on that sum of money, and remote surgery is killing surgeons' wages."
					},
					'result': {
						'development': 3,
						'slaves': 3
					}
				}
			]
		}),
		new App.Corporate.Division.Processing( {
			'id': 'Train',
			'name': 'Slave Training',
			'focusDescription': 'training slaves',
			'sizeCost': 25,
			'addedValue': 11,
			'processing': {
				'center': 0.2,
				'range': 0.3,
			},
			'maintenance': {
				'linear': 0.7,
				'quadratic': 1,
				'category': 'processing',
			},
			'founding': {
				'corporateCash': 50,
			},
			'slaveProcessType': {
				'present': 'train',
				'past': 'trained'
			},
			'slaveProcessDescription': {
				'present': 'training',
				'future': 'train',
				'past': 'trained',
				'market': {
					single: 'slave that needs slave training',
					plural: 'slaves that need slave training'
				}
			},
			'nextDivision': [
				"Whore"
			],
			'merger': [
				{
					'name': 'slave training firm',
					'cost': 50,
					'text': {
						'trouble': "a small slave training firm. Slave training is a notoriously difficult business, since the maturation times on the merchandise can be extremely long, and the improvement in price can be marginal if the training does not prove highly successful. There's nothing inherently wrong with the business, they've simply hit a cash flow bottleneck and need to merge with a cash-rich organization like yours.",
						'acquire': ". The previous owners are happy they'll be able to continue operations under the aegis of a better-run, richer corporation."
					},
					'result': {
						'development': 3,
						'slaves': 3
					}
				}
			]
		}),
		new App.Corporate.Division.Working( {
			'id': 'Arcade',
			'name': 'Sex Arcades',
			'focusDescription': 'operating sex arcades',
			'sizeCost': 5,
			'attrition': {
				'center': 0.05,
				'range': 0.2,
			},
			'maintenance': {
				'linear': 0.2,
				'quadratic': 0.125,
				'category': 'working',
			},
			'revenue': {
				'center': 1000,
				'range': 0.1
			},
			'founding': {
				'corporateCash': 15,
				'startingSize': 20,
			},
			'slaveWorkDescription': {
				'present': 'exploiting',
				'future': 'exploit',
				'past': 'wore out',
				'market': 'fresh slave'
			},
			'merger': [
				{
					'name': 'café',
					'cost': 50,
					'text': {
						'trouble': "a quaint maid café. The prime location together with a popular concept should make for a bustling business and yet the owner is looking to get out of the business hoping to salvage some of his savings. Sadly there was a lack of capital to employ attractive servant slave maids. While someone might very well be more than willing to come in and invest in some better slaves, the place is so cheap you could easily replace the furniture and use the current assets to create a new arcade location for your corporation instead.",
						'acquire': ". The old owner wishes his slaves luck under the new management, not knowing your plans for the place. A new sex arcade under your corporate umbrella will open shortly."
					},
					'result': {
						'development': 5,
						'slaves': 5
					}
				}
			]
		}),
		new App.Corporate.Division.Working( {
			'id': 'Menial',
			'name': 'Menial Services',
			'focusDescription': 'offering menial services',
			'sizeCost': 6.5,
			'attrition': {
				'center': 0.05,
				'range': 0.2,
			},
			'maintenance': {
				'linear': 0.2,
				'quadratic': 0.1,
				'category': 'working',
			},
			'revenue': {
				'center': 1250,
				'range': 0.2
			},
			'founding': {
				'corporateCash': 20,
				'startingSize': 20,
			},
			'slaveWorkDescription': {
				'present': 'exploiting',
				'future': 'exploit',
				'past': 'wore out',
				'market': 'menial slave'
			},
			'merger': [
				{
					'name': 'slaveholding firm',
					'cost': 50,
					'text': {
						'trouble': "a minor slaveholding company. A major deal with a troubled government just fell through, and they had depended on it going through to an unwise degree. They need to merge with a larger slaving concern immediately, but there's nothing at all wrong with their significant inventory of slaves.",
						'acquire': ", cash out its hapless staff, and absorb its stock into your corporation."
					},
					'result': {
						'development': 5,
						'slaves': 5
					}
				}
			]
		}),
		new App.Corporate.Division.Working( {
			'id': 'Dairy',
			'name': 'Dairy',
			'focusDescription': 'milking slaves',
			'sizeCost': 15,
			'attrition': {
				'center': 0.05,
				'range': 0.2,
			},
			'maintenance': {
				'linear': 0.85,
				'quadratic': 0.4,
				'category': 'working',
			},
			'revenue': {
				'center': 3000,
				'range': 0.2
			},
			'founding': {
				'corporateCash': 25,
			},
			'slaveWorkDescription': {
				'present': 'milking',
				'future': 'milk',
				'past': 'ran dry',
				'market': 'cow'
			},
			'merger': [
				{
					'name': 'farm',
					'cost': 50,
					'text': {
						'trouble': "a dairy farm. Why a conventional dairy farm popped up as target confused you for a moment, but it quickly became clear the entire family was so deep in debt it would be a no brainer to buy the farm and start milking the farmer's large-uddered daughters as a great addition to your corporate dairy.",
						'acquire': " and your new slaves for the corporation. The family is not happy with your plans, but their approval is not required, you care only for their fluids."
					},
					'result': {
						'development': 3,
						'slaves': 3
					}
				}
			]
		}),
		new App.Corporate.Division.Working( {
			'id': 'Whore',
			'name': 'Escort Service',
			'focusDescription': 'whoring out slaves',
			'sizeCost': 20,
			'attrition': {
				'center': 0.05,
				'range': 0.2,
			},
			'maintenance': {
				'linear': 0.7,
				'quadratic': 0.25,
				'category': 'working',
			},
			'revenue': {
				'center': 3000,
				'range': 0.1
			},
			'founding': {
				'corporateCash': 50,
			},
			'slaveWorkDescription': {
				'present': 'whoring out',
				'future': 'whore out',
				'past': 'lost their appeal',
				'market': 'trained whore'
			},
			'merger': [
				{
					'name': 'brothel',
					'cost': 50,
					'text': {
						'trouble': "a little brothel. With slave whores becoming the dominant force in sexual services the current madam lost her passion for the business. She's getting up there in age and has run a tight ship for many years so she deemed it the right time to bow out. All you need to do to add a new brothel location for your corporation is sign at the dotted line before anyone else has a chance to bite.",
						'acquire': " before anyone else can make an offer. The madam is surprised by your speed, but happily signs over the brothel."
					},
					'result': {
						'development': 3,
						'slaves': 3
					}
				}
			]
		})
	];
	// divisionCategoriesList: Division maintenance categories are used to calculate the overhead that occurs from having multiple divisions within the same category.
	// id: The identifier that will be used in division.maintenance.category
	// freeDivisions: How many divisions must appear in this category before the overhead cost comes into play. For example, if this is 1, then the second division will cost divisionCost, and the third will bring the overhead up to four times divisionCost.
	// divisionCost: How much to multiply the overhead by in cash. The first division past the free divisions will cost this much, but the value will increase exponentially.
	// freeDevelopment: How many developments, totaled across all divisions in the category, are ignored by this category before overhead comes into play. Note: freeDivisions does *not* affect this; if freeDevelopment is 100 and freeDivisions is 1, if you only have a single division with 110 developments, you will be charged for the 10 developments over.
	// developmentCost: How much to multiply the squared developments by, in cash. The first development past the ignored value will cost this much, but the cost increases exponentially.
	// corporate: The corporate-wide overhead for divisions, beyond the individual categories.
	// freeDivisions: How many total divisions, across all categories, are ignored before charging corporate level overhead.
	// divisionCost: How much to multiply the square of counted divisions in cash. The first counted division will cost this much, but the value increases exponentially.
	// Equations:
	// divisionBaseOverhead = (divisions - freeDivisions) ^ 2 * divisionCost
	// divisionOperationOverhead = (sum(development) - freeDevelopment) ^ 2 * developmentCost
	// corpOverhead = (divisions - 2) ^ 2 * 10000
	App.Corporate.maintenance = {
		divisionCategoriesList: [
			{
				id: 'acquisition',
				freeDivisions: 0,
				divisionCost: 25000,
				freeDevelopment: 100,
				developmentCost: 2
			},
			{
				id: 'processing',
				freeDivisions: 0,
				divisionCost: 25000,
				freeDevelopment: 200,
				developmentCost: 1
			},
			{
				id: 'working',
				freeDivisions: 1,
				divisionCost: 15000,
				freeDevelopment: 800,
				developmentCost: 0.1
			},
		],
		corporate: {
			freeDivisions: 2,
			divisionCost: 10000
		}
	};
};