code:
/** Buy and sell menial slaves
 * @param {boolean} embedded - if true, this element is embedded in a page other than the market and should omit fluff and results
 */
App.UI.tradeMenials = function(embedded) {
	const menialSpan = document.createElement("span");
	const menialPrice = menialSlaveCost();
	const popCap = menialPopCap();
	const bulkMax = popCap.value - V.menials - V.fuckdolls - V.menialBioreactors;
	let linkArray;

	if (!embedded) {
		App.UI.DOM.appendNewElement("h2", menialSpan, "Menial Slaves");

		menialSpan.append(slaveDemand());
		menialSpan.append(slaveSupply());

		App.UI.DOM.appendNewElement("div", menialSpan, popCap.text);
		App.UI.DOM.appendNewElement("div", menialSpan, `The parts of your arcology you own can house a total of ${num(popCap.value)} menial slaves.`);
	}

	menialSpan.append(menialTrading());

	if (!embedded) {
		if (!FutureSocieties.isActive('FSPaternalist')) {
			menialSpan.append(fuckDollConversion());
			menialSpan.append(fuckDollTrading());
		}

		menialSpan.append(bioreactorTrading());
	}

	return menialSpan;

	function slaveDemand() {
		const el = new DocumentFragment();
		const div = document.createElement("div");
		div.append(`Slave demand is `);
		if (V.menialDemandFactor <= -35000) {
			App.UI.DOM.appendNewElement("span", div, `extremely low`, ["red", "bold"]);
		} else if (V.menialDemandFactor <= -20000) {
			App.UI.DOM.appendNewElement("span", div, `very low`, ["red", "bold"]);
		} else if (V.menialDemandFactor <= 0) {
			App.UI.DOM.appendNewElement("span", div, `weak`, ["yellow", "bold"]);
		} else if (V.menialDemandFactor >= 35000) {
			App.UI.DOM.appendNewElement("span", div, `extremely high`, ["green", "bold"]);
		} else if (V.menialDemandFactor >= 20000) {
			App.UI.DOM.appendNewElement("span", div, `very high`, ["green", "bold"]);
		} else {
			App.UI.DOM.appendNewElement("span", div, `strong`, ["yellow", "bold"]);
		}
		if (V.deltaDemand > 0) {
			div.append(` and `);
			App.UI.DOM.appendNewElement("span", div, `improving`, ["green", "bold"]);
		} else if (V.deltaDemand < 0) {
			div.append(` and `);
			App.UI.DOM.appendNewElement("span", div, `decreasing`, ["red", "bold"]);
		}
		if (V.deltaDemand !== 0) {
			if (V.demandTimer - V.elapsedDemandTimer < 3) {
				App.UI.DOM.appendNewElement("span", div, ` but it might change soon.`);
			}
		}
		if (V.cheatMode && V.cheatModeM) {
			const menDemand = function() {
				const el = new DocumentFragment();
				el.append(" ");
				App.UI.DOM.appendNewElement("span", el, `Slave Demand`, ["yellowgreen"]);
				el.append(` | ${V.menialDemandFactor}`);
				return el;
			};

			const span = document.createElement("span");
			span.append(menDemand());

			App.UI.DOM.makeTextBox(
				V.menialDemandFactor,
				v => {
					V.menialDemandFactor = Math.clamp(Math.trunc(Number(v)), -50000, 50000) || V.menialDemandFactor;
					V.cheater = 1;
					$(span).empty().append(menDemand());
				}
			);
			div.append(span);
		}
		el.append(div);
		return el;
	}

	function slaveSupply() {
		const el = new DocumentFragment();
		const div = document.createElement("div");
		div.append(`Slave supply is `);

		if (V.menialSupplyFactor <= -35000) {
			App.UI.DOM.appendNewElement("span", div, "extremely low", ["green", "bold"]);
		} else if (V.menialSupplyFactor <= -20000) {
			App.UI.DOM.appendNewElement("span", div, "very low", ["green", "bold"]);
		} else if (V.menialSupplyFactor <= 0) {
			App.UI.DOM.appendNewElement("span", div, "weak", ["yellow", "bold"]);
		} else if (V.menialSupplyFactor >= 35000) {
			App.UI.DOM.appendNewElement("span", div, "extremely high", ["red", "bold"]);
		} else if (V.menialSupplyFactor >= 20000) {
			App.UI.DOM.appendNewElement("span", div, "very high", ["red", "bold"]);
		} else {
			App.UI.DOM.appendNewElement("span", div, "strong", ["yellow", "bold"]);
		}
		if (V.deltaSupply > 0) {
			div.append(` and `);
			App.UI.DOM.appendNewElement("span", div, "improving", ["red", "bold"]);
		} else if (V.deltaSupply < 0) {
			div.append(` and `);
			App.UI.DOM.appendNewElement("span", div, "decreasing", ["green", "bold"]);
		}
		if (V.deltaSupply !== 0) {
			if (V.supplyTimer - V.elapsedDemandTimer < 3) {
				App.UI.DOM.appendNewElement("span", div, ` but it might change soon.`);
			}
		}

		if (V.cheatMode && V.cheatModeM) {
			const menSupply = function() {
				const el = new DocumentFragment();
				el.append(" ");
				App.UI.DOM.appendNewElement("span", el, `Slave Supply`, `yellowgreen`);
				el.append(` | ${V.menialSupplyFactor}`);
				return el;
			};

			const span = document.createElement("span");
			span.append(menSupply());

			App.UI.DOM.makeTextBox(
				V.menialSupplyFactor,
				v => {
					V.menialSupplyFactor = Math.clamp(Math.trunc(Number(v)), -50000, 50000) || V.menialSupplyFactor;
					V.cheater = 1;
					$(span).empty().append(menSupply());
				}
			);
			div.append(span);
		}
		el.append(div);

		return el;
	}

	function menialTrading() {
		const el = document.createElement("div");
		if (!embedded) {
			const r = [];
			if (V.menials > 1) {
				r.push(`You own ${num(Math.trunc(V.menials))} menial slaves.`);
			} else if (V.menials > 0) {
				r.push(`You own one menial slave.`);
			} else {
				r.push(`You do not own any menial slaves.`);
			}
			r.push(`The market price of menials is <span class='cash'>${cashFormat(menialPrice)}.</span>`);
			App.Events.addNode(el, r);
		}

		if (bulkMax > 0 && V.cash > menialPrice) {
			linkArray = [];
			const buySomeMenials = function(number = 1) {
				const value = forceNeg(menialSlaveCost(number) * number);
				V.menials += number;
				V.menialSupplyFactor -= number;
				cashX(value, "menialTransfer");
				if (embedded) {
					Engine.play(passage());
				} else {
					jQuery("#menial-transaction-result").empty().append(App.UI.DOM.cashFormat(value));
				}
			};

			linkArray.push(
				App.UI.DOM.link(
					"Buy",
					() => {
						buySomeMenials(1);
						jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
					}
				)
			);
			if (V.cash > (menialSlaveCost(5)) * 5) {
				makeLink(5, buySomeMenials);
			}
			if (V.cash > (menialSlaveCost(10)) * 10) {
				makeLink(10, buySomeMenials);
			}
			if (V.cash > (menialSlaveCost(100)) * 100) {
				makeLink(100, buySomeMenials);
			}
			if (V.cash > (menialPrice + 1) * 2) {
				const menialBulkPremium = Math.trunc(1 + Math.clamp(V.cash / menialPrice, 0, bulkMax) / 400);
				linkArray.push(
					App.UI.DOM.link(
						"max",
						() => {
							buySomeMenials(
								Math.trunc(Math.clamp(V.cash / (menialPrice + menialBulkPremium), 0, bulkMax))
							);
							jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
						},
					)
				);
			}
			const div = document.createElement("div");
			div.classList.add("indent");
			div.append(App.UI.DOM.generateLinksStrip(linkArray));
			App.UI.DOM.appendNewElement("span", div, " Bulk transactions may require offering a premium.", "note");
			el.append(div);
		}

		if (!embedded && V.menials > 0) {
			linkArray = [];
			const sellSomeMenials = function(number = 1) {
				const value = menialSlaveCost(number) * number;
				V.menials -= number;
				V.menialDemandFactor -= number;
				cashX(value, "menialTransfer");
				jQuery("#menial-transaction-result").empty().append(App.UI.DOM.cashFormat(value));
			};
			linkArray.push(
				App.UI.DOM.link(
					"Sell",
					() => {
						sellSomeMenials(1);
						jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
					}
				)
			);

			if (V.menials >= 5) {
				makeLink(5, sellSomeMenials);
			}
			if (V.menials >= 10) {
				makeLink(10, sellSomeMenials);
			}
			if (V.menials >= 100) {
				makeLink(100, sellSomeMenials);
			}
			linkArray.push(
				App.UI.DOM.link(
					"all",
					() => {
						sellSomeMenials(V.menials);
						V.menials = 0; // Be quite sure.
						jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
					},
				)
			);
			App.UI.DOM.appendNewElement("div", el, App.UI.DOM.generateLinksStrip(linkArray), ["indent"]);
		}
		return el;
	}

	function fuckDollConversion() {
		const el = document.createElement("div");
		const div = document.createElement("div");
		if (V.menials > 0) {
			if (V.arcadeUpgradeFuckdolls > 0) {
				const convertCost = 100;
				const convertSomeMenialsToFuckdolls = function(number) {
					const value = forceNeg(number * convertCost);
					V.menials -= number;
					V.fuckdolls += number;
					cashX(value, "menialTransfer");
					jQuery("#menial-transaction-result").empty().append(App.UI.DOM.cashFormat(value));
				};
				linkArray = [];
				linkArray.push(
					App.UI.DOM.link(
						"Convert to Fuckdoll",
						() => {
							convertSomeMenialsToFuckdolls(1);
							jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
						}
					)
				);

				if (V.menials >= 10) {
					makeLink(10, convertSomeMenialsToFuckdolls);
					if (V.menials >= 100) {
						makeLink(100, convertSomeMenialsToFuckdolls);
					}
				}
				linkArray.push(
					App.UI.DOM.link(
						"all",
						() => {
							convertSomeMenialsToFuckdolls(V.menials);
							V.menials = 0; // Be really sure
							jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
						},
					)
				);

				div.append(App.UI.DOM.generateLinksStrip(linkArray));
				App.Events.addNode(div, [`Conversion costs ${cashFormatColor(convertCost)} each`], "span", "note");
				el.append(div);
			}
			if (V.dairyFeedersUpgrade > 0) {
				const convertCost = 500;
				const convertSomeMenialsToBioreactors = function(number) {
					const value = forceNeg(number * convertCost);
					V.menials -= number;
					V.menialBioreactors += number;
					cashX(value, "menialTransfer");
					jQuery("#menial-transaction-result").empty().append(App.UI.DOM.cashFormat(value));
				};
				linkArray = [];
				linkArray.push(
					App.UI.DOM.link(
						"Convert to Bioreactor",
						() => {
							convertSomeMenialsToBioreactors(1);
							jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
						}
					)
				);

				if (V.menials >= 10) {
					makeLink(10, convertSomeMenialsToBioreactors);
					if (V.menials >= 100) {
						makeLink(100, convertSomeMenialsToBioreactors);
					}
				}
				linkArray.push(
					App.UI.DOM.link(
						"all",
						() => {
							convertSomeMenialsToBioreactors(V.menials);
							V.menials = 0; // Be really sure
							jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
						},
					)
				);
				const div = document.createElement("div");
				div.append(App.UI.DOM.generateLinksStrip(linkArray));
				App.Events.addNode(div, [`Conversion costs ${cashFormatColor(convertCost)} each`], "span", "note");
				el.append(div);
			}
		}
		return el;
	}

	function fuckDollTrading() {
		const el = document.createElement("div");
		if (V.fuckdolls > 1) {
			el.append(`You own ${num(Math.trunc(V.fuckdolls))} standard Fuckdolls. `);
		} else if (V.fuckdolls > 0) {
			el.append(`You own one standard Fuckdoll. `);
		} else if (!FutureSocieties.isActive('FSPaternalist')) {
			el.append(`You do not own any standard Fuckdolls. `);
		}
		if ((V.fuckdolls > 0) || !FutureSocieties.isActive('FSPaternalist')) {
			App.Events.addNode(el, [`The market price of standard Fuckdolls is <span class="cash">${cashFormat(menialPrice)}.</span>`]);
			if (bulkMax > 0) {
				if (!FutureSocieties.isActive('FSPaternalist') && V.cash > menialPrice) {
					linkArray = [];
					const buySomeFuckdolls = function(number = 1) {
						const value = forceNeg(menialSlaveCost(number) * number);
						V.fuckdolls += number;
						V.menialSupplyFactor -= number;
						cashX(value, "fuckdollsTransfer");
						jQuery("#menial-transaction-result").empty().append(App.UI.DOM.cashFormat(value));
					};

					linkArray.push(
						App.UI.DOM.link(
							"Buy",
							() => {
								buySomeFuckdolls(1);
								jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
							}
						)
					);
					if (V.cash > (menialSlaveCost(10)) * 10) {
						makeLink(10, buySomeFuckdolls);
					}
					if (V.cash > (menialSlaveCost(100)) * 100) {
						makeLink(100, buySomeFuckdolls);
					}
					if (V.cash > (menialPrice + 1) * 2) {
						const menialBulkPremium = Math.trunc(1 + Math.clamp(V.cash / menialPrice, 0, bulkMax) / 400);
						linkArray.push(
							App.UI.DOM.link(
								"max",
								() => {
									buySomeFuckdolls(
										Math.trunc(Math.clamp(V.cash / (menialPrice + menialBulkPremium), 0, bulkMax))
									);
									jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
								},
							)
						);
					}
					const div = document.createElement("div");
					div.classList.add("indent");
					div.append(App.UI.DOM.generateLinksStrip(linkArray));
					App.UI.DOM.appendNewElement("span", div, " Bulk transactions may require offering a premium.", "note");
					el.append(div);
				}
			}
			if (V.fuckdolls >= 1) {
				linkArray = [];
				const sellSomeFuckdolls = function(number = 1) {
					const value = menialSlaveCost(number) * number;
					V.fuckdolls -= number;
					V.menialDemandFactor -= number;
					cashX(value, "fuckdollsTransfer");
					jQuery("#menial-transaction-result").empty().append(App.UI.DOM.cashFormat(value));
				};
				linkArray.push(
					App.UI.DOM.link(
						"Sell",
						() => {
							sellSomeFuckdolls(1);
							jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
						}
					)
				);

				if (V.fuckdolls >= 10) {
					makeLink(10, sellSomeFuckdolls);
					if (V.fuckdolls >= 100) {
						makeLink(100, sellSomeFuckdolls);
					}
				}
				linkArray.push(
					App.UI.DOM.link(
						"all",
						() => {
							sellSomeFuckdolls(V.fuckdolls);
							V.fuckdolls = 0; // Be quite sure.
							jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
						},
					)
				);
				App.UI.DOM.appendNewElement("div", el, App.UI.DOM.generateLinksStrip(linkArray), ["indent"]);
			}
		}

		return el;
	}

	function bioreactorTrading() {
		const el = document.createElement("div");
		if (V.menialBioreactors > 1) {
			el.append(`You own ${num(Math.trunc(V.menialBioreactors))} standard bioreactors. `);
		} else if (V.menialBioreactors > 0) {
			el.append(`You own one standard bioreactor. `);
		} else if (!FutureSocieties.isActive('FSPaternalist')) {
			el.append(`You do not own any standard bioreactors. `);
		}
		if ((V.menialBioreactors > 0) || !FutureSocieties.isActive('FSPaternalist')) {
			App.Events.addNode(el, [`The market price of standard bioreactors is <span class='cash'>${cashFormat(menialPrice - 100)}.</span>`]);
			if (bulkMax > 0) {
				if (!FutureSocieties.isActive('FSPaternalist') && V.cash > menialPrice) {
					linkArray = [];
					const buySomeBioreactors = function(number = 1) {
						const value = forceNeg(menialSlaveCost(number - 100) * number);
						V.menialBioreactors += number;
						V.menialSupplyFactor -= number;
						cashX(value, "menialBioreactorsTransfer");
						jQuery("#menial-transaction-result").empty().append(App.UI.DOM.cashFormat(value));
					};

					linkArray.push(
						App.UI.DOM.link(
							"Buy",
							() => {
								buySomeBioreactors(1);
								jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
							}
						)
					);
					if (V.cash > (menialSlaveCost(10)) * 10) {
						makeLink(10, buySomeBioreactors);
					}
					if (V.cash > (menialSlaveCost(100)) * 100) {
						makeLink(100, buySomeBioreactors);
					}
					if (V.cash > (menialPrice - 99) * 2) {
						const bioreactorBulkPremium = Math.trunc(1 + Math.clamp(V.cash / (menialPrice - 99), 0, bulkMax) / 400);
						linkArray.push(
							App.UI.DOM.link(
								"max",
								() => {
									buySomeBioreactors(
										Math.trunc(Math.clamp(V.cash / (menialPrice - 99 + bioreactorBulkPremium), 0, bulkMax))
									);
									jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
								},
							)
						);
					}
					const div = document.createElement("div");
					div.classList.add("indent");
					div.append(App.UI.DOM.generateLinksStrip(linkArray));
					App.UI.DOM.appendNewElement("span", div, " Bulk transactions may require offering a premium.", "note");
					el.append(div);
				}
			}
			if (V.menialBioreactors >= 1) {
				linkArray = [];
				const sellSomeBioreactors = function(number = 1) {
					const value = menialSlaveCost(number - 100) * number;
					V.menialBioreactors -= number;
					V.menialDemandFactor -= number;
					jQuery("#menial-transaction-result").empty().append(App.UI.DOM.cashFormat(value));
					cashX(value, "menialBioreactorsTransfer");
				};
				linkArray.push(
					App.UI.DOM.link(
						"Sell",
						() => {
							sellSomeBioreactors(1);
							jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
						}
					)
				);

				if (V.menialBioreactors >= 10) {
					makeLink(10, sellSomeBioreactors);
					if (V.menialBioreactors >= 100) {
						makeLink(100, sellSomeBioreactors);
					}
				}
				linkArray.push(
					App.UI.DOM.link(
						"all",
						() => {
							sellSomeBioreactors(V.menialBioreactors);
							V.menialBioreactors = 0; // Be quite sure.
							jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
						},
					)
				);
				const div = document.createElement("div");
				div.classList.add("indent");
				div.append(App.UI.DOM.generateLinksStrip(linkArray));
				el.append(div);
			}
		}
		return el;
	}

	/**
	 * @param {number} number
	 * @param {Function} runMe
	 */
	function makeLink(number, runMe) {
		linkArray.push(
			App.UI.DOM.link(
				`x${number}`,
				() => {
					runMe(number);
					jQuery(menialSpan).empty().append(App.UI.tradeMenials(embedded));
				}
			)
		);
	}
};


code:
/** get one arcology's opinion of another
 * @param {FC.ArcologyState} activeArcology index
 * @param {FC.ArcologyState} targetArcology index
 * @returns {number} opinion
 */
App.Neighbor.opinion = function(activeArcology, targetArcology) {
	const {shared, conflicting} = FutureSocieties.diplomaticFSes(activeArcology, targetArcology);

	let opinion = 0;

	if (activeArcology.direction === targetArcology.direction) {
		// ignore narcissism
		// this will be a problem in the future and as such, arcologies should realistically be given IDs
		return opinion;
	}

	for (const fs of shared) {
		opinion += activeArcology[fs];
		opinion += targetArcology[fs];
	}

	for (const [activeFS, targetFS] of conflicting) {
		opinion -= activeArcology[activeFS];
		opinion -= targetArcology[targetFS];
	}

	// unshared but uncontested multiculturalism gets a relationship bonus
	if (!shared.includes("FSNull") && !conflicting.some((pair) => pair.includes("FSNull"))) {
		if (FutureSocieties.isActive('FSNull', activeArcology)) {
			opinion += activeArcology.FSNull;
		} else if (FutureSocieties.isActive('FSNull', targetArcology)) {
			opinion += targetArcology.FSNull;
		}
	}

	return opinion;
};

/** set a new influence target for a given arcology
 * @param {number} arcID
 */
App.Neighbor.selectInfluenceTarget = function(arcID) {
	const notMulticulturalism = (f) => f !== "FSNull"; // multiculturalism can neither influence nor be influenced
	const influenceSources = FutureSocieties.influenceSources(arcID);
	const arcology = V.arcologies[arcID];
	if (influenceSources.length > 0) {
		let eligibleTargets = [];
		const obedient = (arcology.government === "your trustees" || arcology.government === "your agent");

		for (const target of V.arcologies) {
			if (arcology.direction !== target.direction) {
				if (!obedient || target.direction !== 0) {
					const {shared, conflicting} = FutureSocieties.diplomaticFSes(arcology, target);
					let count = 0;
					count += shared.filter(notMulticulturalism).length;
					count += conflicting.filter((pair) => pair.every(notMulticulturalism)).length;
					eligibleTargets.push(...Array(count).fill(target.direction));
				}
			}
		}

		if (eligibleTargets.length > 0) {
			arcology.influenceTarget = eligibleTargets.random();
		}
	}
};

App.Neighbor.PassiveFSInfluence = class {
	/** pick up social hints from an arcology's neighbors
	 * @param {number} arcID
	 */
	constructor(arcID) {
		this._arcID = arcID;
		/** @type {Map<number, {shared: FC.FutureSociety[], conflicting: FC.FutureSociety[][]}>} */
		this._relationships = new Map();

		const arcology = V.arcologies[this._arcID];
		for (let i = 0; i < V.arcologies.length; ++i) {
			if (i !== arcID) {
				this._relationships.set(i, FutureSocieties.diplomaticFSes(arcology, V.arcologies[i]));
			}
		}

		this._thresh = 5;
		if (arcology.direction === 0) {
			this._thresh -= V.policies.culturalOpenness * 5;
		}
		if (arcology.ownership >= 100) {
			this._thresh += 5;
		}
	}

	/** output the neighbors that have passively influenced a particular FS in this arcology
	 * @param {FC.FutureSociety} fs
	 */
	output(fs) {
		/** @type {number[]} */
		let shared = [];
		/** @type {Map<FC.FutureSociety, number[]>} */
		let conflicting = new Map();
		const arcology = V.arcologies[this._arcID];

		if (arcology.direction === 0 && fs === "FSNull") {
			return ``; // Multiculturalism in the player's arcology is not affected by passive influence
		}

		for (const [i, rel] of this._relationships) {
			if (rel.shared.some((s) => s === fs)) {
				if (V.arcologies[i][fs] > arcology[fs] + this._thresh) {
					shared.push(i);
				}
			} else {
				const conflict = rel.conflicting.find((p) => p[0] === fs);
				if (conflict) {
					const conflictFS = conflict[1];
					if (conflictFS === "FSNull") {
						continue; // no passive slowing from Multiculturalism
					}
					if (V.arcologies[i][conflictFS] > arcology[fs] + this._thresh) {
						const oldVal = conflicting.get(conflictFS);
						if (oldVal) {
							oldVal.push(i);
						} else {
							conflicting.set(conflictFS, [i]);
						}
					}
				}
			}
		}

		let t = [];
		// passive growth influence
		arcology[fs] += shared.length;
		if (V.showNeighborDetails && shared.length > 0) {
			t.push(FutureSocieties.displayName(fs));
			if (fs === "FSSubjugationist") {
				t.push(`of ${arcology.FSSubjugationistRace} people`);
			} else if (fs === "FSSupremacist") {
				t.push(`for ${arcology.FSSupremacistRace} people`);
			}
			t.push(`in ${arcology.name} is influenced by`);
			const sharedNames = shared.map((i) => V.arcologies[i].name + "'s");
			t.push(`${toSentence(sharedNames)} more advanced ${shared.length === 1 ? "society" : "societies"}.`);
		}

		// passive slowing influence
		arcology[fs] -= conflicting.size;
		if (V.showNeighborDetails && conflicting.size > 0) {
			/** @type {string} */
			let actualDisplayName = FutureSocieties.displayName(fs);
			if (fs === "FSSubjugationist") {
				actualDisplayName = `${arcology.FSSubjugationistRace} Subjugationism`;
			} else if (fs === "FSSupremacist") {
				actualDisplayName = `${arcology.FSSupremacistRace} Supremacy`;
			}
			t.push(`Development of ${actualDisplayName} in ${arcology.name} is slowed by contact with`);
			let conflictOutput = [];
			for (const [conflictFS, arcs] of conflicting) {
				const conflictNames = arcs.map((i) => V.arcologies[i].name + "'s");
				conflictOutput.push(`${toSentence(conflictNames)} more advanced ${FutureSocieties.displayAdj(conflictFS)} ${arcs.length === 1 ? "society" : "societies"}`);
			}
			t.push(toSentence(conflictOutput) + '.');
		}

		return t.join(" ");
	}
};

/** Get an arcology name from a given set, preferring unused ones.
 * @param {string[]} nameSet
 * @returns {string}
 */
App.Neighbor.getUnusedName = function(nameSet) {
	const filteredSet = nameSet.filter(n => !V.arcologies.some(a => a.name === n));
	return filteredSet.length > 0 ? filteredSet.random() : nameSet.random();
};

repo_name: Nithhogg/fc

license: mit


code:
App.UI.neighborDescription = function(i) {
	const el = new DocumentFragment();
	const averageProsperity = _.mean(V.arcologies.map((a) => a.prosperity));
	let r = [];
	r.push(App.UI.DOM.makeElement("span", V.arcologies[i].name, "bold"));
	if (V.arcologies[i].direction !== 0) {
		r.push(`is located to the ${V.arcologies[i].direction} of your arcology. It is governed by`);
		switch (V.arcologies[i].government) {
			case "elected officials":
				r.push(`elected officials, periodically paralyzing its development.`);
				break;
			case "a committee":
				r.push(`a committee, hindering its development.`);
				break;
			case "an oligarchy":
				r.push(`a small group of leading citizens, making its development very unpredictable.`);
				break;
			case "your trustees":
				r.push(`a small group of leading citizens who are serving as <span class="mediumseagreen">your trustees.</span>`);
				break;
			case "an individual":
				r.push(`<span class="cyan">an individual,</span> making its development vibrant but unpredictable.`);
				break;
			case "your agent":
				r.push(`<span class="deeppink">your agent,</span> who is directing the arcology in your stead.`);
				break;
			case "a corporation":
				r.push(`a corporation, making its development steady and unspectacular.`);
				break;
			default:
				r.push(`direct democracy, making its development dangerously unstable.`);
		}
	} else {
		r.push(`is your arcology.`);
	}
	let economicUncertainty = App.Utils.economicUncertainty(i);
	if (V.arcologies[i].direction === 0) {
		r.push(`You control <span class="lime">${V.arcologies[i].ownership}%</span> of the arcology${V.arcologies[0].ownership !== 100 ? `, and the largest minority holder controls ${V.arcologies[i].minority}%.` : `.`}`);
	} else if ((V.arcologies[i].government !== "your trustees") && (V.arcologies[i].government !== "your agent")) {
		r.push(`Its leadership has control of approximately ${Math.trunc(V.arcologies[i].ownership*economicUncertainty)}% of the arcology${(V.arcologies[i].minority > V.arcologies[i].ownership-10) ? `, a dangerously narrow margin over competition with a <span class="tan">${Math.trunc(V.arcologies[i].minority*economicUncertainty)}%</span> share` : ``}.`);
	}
	if (V.arcologies[i].PCminority > 0) {
		r.push(`You own ${V.arcologies[i].PCminority}% of this arcology${(((V.arcologies[i].government === "your trustees") || (V.arcologies[i].government === "your agent")) && V.arcologies[i].minority > V.arcologies[i].PCminority-10) ? `, a dangerously narrow margin over competition with a <span class="red">${Math.trunc(V.arcologies[i].minority*economicUncertainty)}%</span> share` : ``}.`);
	}
	r.push(`The arcology has an estimated GSP of <span class="yellowgreen">${cashFormat(Math.trunc(0.1*V.arcologies[i].prosperity*economicUncertainty))}m,</span>`);
	if (Math.abs(V.arcologies[i].prosperity - averageProsperity) < 5) {
		r.push(`average among`);
	} else if (V.arcologies[i].prosperity > averageProsperity) {
		r.push(`ahead of`);
	} else {
		r.push(`behind`);
	}
	r.push(`its neighbors.`);

	let desc = "";
	const neighborDescription = [];
	/** @type {Record<FC.FutureSociety, Record<95 | 40 | 0, string>>} */
	const FSThresholds = {
		FSSubjugationist: {
			95: `the home of an advanced project to create a subservient race of ${V.arcologies[i].FSSubjugationistRace} slaves`,
			40: `working to refine ${V.arcologies[i].FSSubjugationistRace} slavery`,
			0: `an excellent dumping ground for low quality ${V.arcologies[i].FSSubjugationistRace} slaves.`
		},
		FSSupremacist: {
			95: `a global magnet for ${V.arcologies[i].FSSupremacistRace} nationalists`,
			40: `becoming increasingly free of ${V.arcologies[i].FSSupremacistRace} slaves`,
			0: `the site of a furious debate over existing ${V.arcologies[i].FSSupremacistRace} slaves`
		},
		FSRepopulationFocus: {
			95: `notorious for the size and number of pregnancies among its population`,
			40: `known to be a good place to find slavegirls heavy with children`,
			0: `actively importing fertile slave girls`
		},
		FSRestart: {
			95: `notorious for the number of powerful civilians inhabiting it`,
			40: `known to be a good place to make connections`,
			0: `actively importing sterilization supplies`
		},
		FSGenderRadicalist: {
			95: `notorious for the openness with which its citizens fuck its slavegirls in the ass until they cum`,
			40: `known to be a good place to find slavegirls who cum when buttfucked`,
			0: `actively importing a wider variety of slave girls`
		},
		FSGenderFundamentalist: {
			95: `famous for its slave schools, crowded with a future generation of world class slaves`,
			40: `remarkable for its crowds of hugely pregnant slave women`,
			0: `importing increasing numbers of fertile slaves`
		},
		FSPaternalist: {
			95: `the home of an educated, enlightened caste of slaves more productive than some arcologies' citizens`,
			40: `becoming known for its unusually cheerful atmosphere`,
			0: `starting to demand abused slaves whose lives can be turned around`
		},
		FSDegradationist: {
			95: `renowned and feared by slaves worldwide, as a place of blood and steel from which few ever leave`,
			40: `becoming dreaded by slaves, since the few it exports are full of unbelievably awful stories`,
			0: `importing a rapidly increasing number of slaves, for some reason`
		},
		FSIntellectualDependency: {
			95: `both terrifying and alluring to slaves worldwide, as a place where none leave with any semblance of intelligence but always flaunt the enjoyment they've had`,
			40: `becoming known for its unusually simple-minded chattel`,
			0: `importing large quantities of aphrodisiacs and psychosuppressants`
		},
		FSSlaveProfessionalism: {
			95: `renowned as a source of some of the world's finest courtesans`,
			40: `becoming revered as an intellectual paradise`,
			0: `seeking out high-class slave trainers`
		},
		FSBodyPurist: {
			95: `a world leader in the drug industry due to its pharmaceutical research breakthroughs`,
			40: `pouring an ever-increasing amount of money into drug research`,
			0: `setting up research programs to develop better slave drugs`,
		},
		FSTransformationFetishist: {
			95: `renowned as the source of some of the world's most unbelievable surgical transformations`,
			40: `rapidly moving from mere breast expansion to more esoteric surgical fetishism`,
			0: `receiving daily shipments of silicone and surgical necessities`,
		},
		FSYouthPreferentialist: {
			95: `famous for the intense celebratory attention slaves receive there once reaching their majorities`,
			40: `moving virginity and the taking of virginity ever higher in the public estimation`,
			0: `starting to get a reputation as an excellent place to get a good price for a virgin`,
		},
		FSMaturityPreferentialist: {
			95: `world famous among mature slaves, who see it as a paradise in which MILFs are the most valuable girls around`,
			40: `striking for the variety of well-preserved beauties that can be seen there`,
			0: `displaying an increasing demand for enslaved housewives and professional women`,
		},
		FSPetiteAdmiration: {
			95: `known as a place where even the shortest person can feel tall`,
			40: `investing large sums of money into petite clothing lines`,
			0: `starting to accumulate an unusually large number of short slaves`
		},
		FSStatuesqueGlorification: {
			95: `world famous among tall slaves, who see it as a paradise in which they are valued above all`,
			40: `going out of its way to import only the tallest of slaves`,
			0: `starting to drive out its shorter citizenry`,
		},
		FSSlimnessEnthusiast: {
			95: `very well known for the wonderful variety of nubile bodies that can be seen there`,
			40: `becoming known as an arcology that slims slaves down rather than turning them into piles of tits and ass`,
			0: `starting to display unusual fashions regarding breasts and butts`,
		},
		FSAssetExpansionist: {
			95: `widely considered an interior design masterpiece for its adaptations to slaves with fifty kilos of tits`,
			40: `a popular tourist destination just for the view, which features some truly spectacular bare boobs`,
			0: `demanding fatter slaves, since its citizens are learning they absorb growth hormones better`,
		},
		FSPastoralist: {
			95: `a world-renowned producer of cowgirl dairy products of all kinds`,
			40: `devoting more and more of its slaves to work as cowgirls, or to the service and upkeep of cowgirls`,
			0: `displaying an increasing public appetite for dairy, and yet imports almost no true cow's milk`,
		},
		FSCummunism: {
			95: `a world-renowned producer of cum-based products of all kinds`,
			40: `devoting more and more of its slaves to work as cumtanks, or to the service, upkeep and milking of ballgirls`,
			0: `displaying an increasing public appetite for cum, and has begun importing more and more specialized milkers`,
		},
		FSPhysicalIdealist: {
			95: `a constant standout at international athletic competitions, where both its citizens and slaves do very well`,
			40: `quite a sight, since its citizens and slaves all lift constantly`,
			0: `the site of a musclegirl fetish community`,
		},
		FSHedonisticDecadence: {
			95: `very well known as a place where every imaginable desire and fantasy can be fulfilled`,
			40: `importing huge amounts of food and alcohol`,
			0: `the site of a large number of lazy individuals`,
		},
		FSIncestFetishist: {
			95: `highly recommended as a place to stop by if you like threesomes with twins or familial gangbangs`,
			40: `attracting a substantial number of families`,
			0: `devoting more and more of its resources into genealogy`,
		},
		FSChattelReligionist: {
			95: `a significant force in the global development of Chattel Religionist dogma`,
			40: `a popular destination for devout old world citizens engaging in sex tourism`,
			0: `in the throes of public dissension over its religious laws`,
		},
		FSNull: {
			95: `the source of several new and interesting cultural fusion trends in art, fashion and cuisine`,
			40: `a popular place to visit to broaden one's horizons`,
			0: `struggling with the conflict inherent in becoming a cultural melting pot`
		},
		FSRomanRevivalist: {
			95: `hugely famous for its broadcasts of gladiatorial combat, popular even in the old world`,
			40: `almost obnoxiously aspirational, with citizens competing to serve the state best`,
			0: `working its way through sword and sandals fashion towards proper historicity`,
		},
		FSNeoImperialist: {
			95: `utterly hierarchical, with techno-nobles holding near complete control over the very lives of the masses and powerful Knights in heavy powered plate keeping them firmly in line`,
			40: `increasingly syncretic, integrating the highest technology with total, unrepentant serfdom under tight-fisted techno-nobles and a rising class of elite Knights serving as the sword of the nobility`,
			0: `creating an extremely strict hierarchical system, with an elite caste of techno-nobility cementing itself through a series of complicated, emergent feudal dynamics`,
		},
		FSAztecRevivalist: {
			95: `world famous for its incredible architecture and highly qualified leading caste and military`,
			40: `constructing great pyramids and statues with equally weighty costs`,
			0: `struggling to embrace the amount of blood sacrifice and prayer involved`,
		},
		FSEgyptianRevivalist: {
			95: `a world famous tourist destination for the traditional festival in its plaza, which never stops`,
			40: `very much under renovation, as vast blocks of stone are imported around the clock`,
			0: `struggling with the fashion implications of so much white linen everywhere`,
		},
		FSEdoRevivalist: {
			95: `visibly trailing cherry blossoms, blown off its balconies by the wind`,
			40: `becoming a notable cultural center, even in the old world`,
			0: `the object of considerable debate among socially awkward teenagers worldwide`,
		},
		FSArabianRevivalist: {
			95: `a famous center of Arabian romanticism, since it has the wisdom not to be historically perfect`,
			40: `working through which parts of Arabian romanticism to adopt and which to discard`,
			0: `eagerly reveling in the most romantic parts of superficial Arabian romanticism`,
		},
		FSChineseRevivalist: {
			95: `now confidently mature about its possession of the Mandate of Heaven`,
			40: `often aggressive about its status as the middle kingdom to the point that it betrays uncertainty`,
			0: `moving towards a stage of refinement that will allow it to present itself properly`,
		},
		FSAntebellumRevivalist	: {
			95: `famed for it's Greek-revival style architecture, well-mannered citizenry, and chivalrous aristocrats.`,
			40: `a superficial and sappy reflection of the old American South, too busy indulging in the extravagant architecture of Antebellum plantation houses`,
			0: `struggling to center its society around slave-owning aristocrats and facing resistance from certain ethnic groups who fear they may become a class of chattel slaves.`
		},
	};

	for (const fs of App.Data.FutureSociety.fsNames) {
		const fsScore = V.arcologies[i][fs];
		if (fsScore > 0) {
			if (fsScore > 80) {
				desc = "committed to ";
			} else if (fsScore > 20) {
				desc = "pursuing ";
			} else {
				desc = "just establishing ";
			}
			desc += `<span class="gold">${FutureSocieties.displayName(fs)},</span> and is `;
			for (const threshold of [95, 40, 0]) {
				if (fsScore >= threshold) {
					desc += FSThresholds[fs][threshold] + `.`;
					break;
				}
			}
			neighborDescription.push(desc);
		}
	}

	if (neighborDescription.length > 0) {
		if (neighborDescription.length > 2) {
			r.push(`Its culture is complex. First, it is ${neighborDescription[0]}`);
			for (let j = 1; j < neighborDescription.length; j++) {
				if (j < neighborDescription.length-1) {
					r.push(`It is`);
				} else {
					r.push(`Finally, the arcology is`);
				}
				r.push(neighborDescription[j]);
			}
		} else if (neighborDescription.length === 2) {
			r.push(`Its culture is developing along two lines. First, it is ${neighborDescription[0]} Second, it is ${neighborDescription[1]}`);
		} else {
			r.push(`Its culture is diverging from the old world: it is ${neighborDescription[0]}`);
		}
	}
	App.Events.addNode(el, r, "div");
	return el;
};


code:
App.Neighbor.Display = class {
	/** Create a neighbor display controller
	 * @param {function(number):void} onSelection function to be called when selection changes
	 */
	constructor(onSelection) {
		this.containerID = "neighbor-display";
		this._onSelection = onSelection;
	}

	/** Render the neighbor display to DOM
	 * @returns {Element}
	 */
	render() {
		const container = App.UI.DOM.makeElement("div", null, ["margin-top"]);
		container.id = this.containerID;

		function makeSortLink(/** @type {string} */ title, /** @type {string} */ mode) {
			if (V.neighborDisplay === mode) {
				return document.createTextNode(title);
			} else {
				return App.UI.DOM.link(title, () => { V.neighborDisplay = mode; this.rerender(); } );
			}
		}

		container.append("Neighbor display mode: ");
		container.appendChild(App.UI.DOM.generateLinksStrip([
			makeSortLink.call(this, "List by ID", "list"),
			makeSortLink.call(this, "List by Name", "list-name"),
			makeSortLink.call(this, "City Grid", "grid")
		]));

		// pick a mode
		if (V.neighborDisplay === "grid") {
			container.appendChild(this._renderGrid());
		} else if (V.neighborDisplay === "list-name") {
			container.appendChild(this._renderListName());
		} else { // default if V.neighborDisplay is unset for some reason; canonically "list"
			container.appendChild(this._renderListID());
		}
		return container;
	}

	/** Refresh an existing neighbor display list */
	rerender() {
		$(`#${this.containerID}`).replaceWith(this.render());
		this.select(V.temp.activeArcologyIdx);
	}

	/** Render the display as a list, sorted by ID
	 * @returns {Element}
	 */
	_renderListID() {
		const container = App.UI.DOM.makeElement("div", null, ["neighbor-container", "neighbor-container-list"]);

		for (let i = 0; i < V.arcologies.length; ++i) {
			this._renderCell(container, i);
		}

		return container;
	}

	/** Render the display as a list, sorted by name
	 * @returns {Element}
	 */
	_renderListName() {
		const container = App.UI.DOM.makeElement("div", null, ["neighbor-container", "neighbor-container-list"]);

		const arcologyNames = V.arcologies.map((a) => a.name).sort((a, b) => a.localeCompare(b));
		for (const name of arcologyNames) {
			this._renderCell(container, V.arcologies.findIndex((a) => a.name === name));
		}

		return container;
	}

	/** Render the display as a grid
	 * @returns {Element}
	 */
	_renderGrid() {
		const container = App.UI.DOM.makeElement("div", null, ["neighbor-container", "neighbor-container-grid"]);

		const directionToIndex = (dir) => V.arcologies.findIndex((a) => a.direction === dir);
		for (const dir of [ "northwest", "north", "northeast", "west", 0, "east", "southwest", "south", "southeast" ]) {
			this._renderCell(container, directionToIndex(dir));
		}

		return container;
	}

	/** Render a single cell to the display
	 * @param {Element} container
	 * @param {number} arcID
	 */
	_renderCell(container, arcID) {
		const arcology = V.arcologies[arcID];

		/** render styled text with a tooltip
		 * @param {string} text
		 * @param {string} tooltipText
		 * @param {string} className
		 * @returns {HTMLSpanElement}
		 */
		function withTooltip(text, tooltipText, className) {
			let res = App.UI.DOM.makeElement("span", text, ["has-tooltip", className]);
			tippy(res, {content: tooltipText});
			return res;
		}

		/** @param {function(): void} selectLambda */
		function nameFrag(selectLambda) {
			return App.UI.DOM.makeElement("div", App.UI.DOM.link(arcology.name, selectLambda), "name");
		}

		function govGSPFrag() {
			let frag = document.createDocumentFragment();
			let gov;
			if (arcID === 0) {
				gov = App.UI.DOM.appendNewElement("div", frag, "Your arcology; ");
			} else {
				gov = App.UI.DOM.appendNewElement("div", frag, capFirstChar(arcology.government) + '; ');
			}
			const estimatedGSP = Math.trunc(0.1 * arcology.prosperity * App.Utils.economicUncertainty(arcID));
			App.UI.DOM.appendNewElement("span", gov, cashFormat(estimatedGSP) + "m GSP", "cash");
			return frag;
		}

		function fsFrag() {
			let frag = document.createDocumentFragment();
			frag.appendChild(document.createTextNode("FS: "));
			const FSes = FutureSocieties.activeFSes(arcology);
			if (arcID === 0) {
				for (const fs of FSes) {
					frag.appendChild(withTooltip("⯁", FutureSocieties.displayName(fs), "steelblue"));
				}
			} else {
				const diplomatic = FutureSocieties.diplomaticFSes(arcology, V.arcologies[0]);
				for (const fs of FSes) {
					let style = "white";
					if (diplomatic.shared.includes(fs)) {
						style = "steelblue";
					} else {
						const conflict = diplomatic.conflicting.find((f) => f[0] === fs);
						if (conflict) {
							style = "red";
						}
					}
					frag.appendChild(withTooltip("⯁", FutureSocieties.displayName(fs), style));
				}
			}
			return App.UI.DOM.makeElement("div", frag);
		}

		function ownershipFrag() {
			let frag = document.createDocumentFragment();
			const owned = arcID === 0 || arcology.government === "your trustees" || arcology.government === "your agent";
			const yourPercent = arcID === 0 ? `${arcology.ownership}%` : `${arcology.PCminority}%`;
			const hostilePercent = owned ? `${arcology.minority}%` : `${arcology.ownership}%`;
			const challengerPercent = owned ? null : `${arcology.minority}%`;
			const publicPercent = `${100 - (arcology.ownership + arcology.minority + arcology.PCminority)}%`;
			App.UI.DOM.appendNewElement("li", frag, withTooltip(yourPercent, "Your ownership", "steelblue"));
			if (arcID !== 0) {
				App.UI.DOM.appendNewElement("li", frag, withTooltip(publicPercent, "Public ownership", "yellow"));
			}
			if (challengerPercent) {
				App.UI.DOM.appendNewElement("li", frag, withTooltip(challengerPercent, "Minority challenger ownership", "orange"));
			}
			App.UI.DOM.appendNewElement("li", frag, withTooltip(hostilePercent, "Hostile ownership", "red"));
			return App.UI.DOM.makeElement("ul", frag, "choices-strip");
		}

		let frag = document.createDocumentFragment();
		let classNames = ["neighbor-item"];
		if (!arcology) {
			// empty block
			classNames.push("neighbor-item-empty");
		} else {
			frag.appendChild(nameFrag(() => this.select(arcID)));
			frag.appendChild(govGSPFrag());
			frag.appendChild(fsFrag());
			frag.appendChild(ownershipFrag());
			if (arcID === 0) {
				classNames.push("neighbor-item-self");
			} else if (arcology.government === "your trustees" || arcology.government === "your agent") {
				classNames.push("neighbor-item-owned");
			} else if (arcology.rival) {
				classNames.push("neighbor-item-rival");
			} else {
				classNames.push("neighbor-item-unowned");
			}
		}

		const element = App.UI.DOM.appendNewElement("div", container, frag, classNames);
		element.id = `neighbor-cell-${arcID}`;
	}

	/** Set the selection to a particular arcology
	 * @param {number} arcID
	 */
	select(arcID) {
		const elementID = `#neighbor-cell-${arcID}`;

		$("div[id^='neighbor-cell']").each((i, e) => { $(e).removeClass("neighbor-item-selected"); });
		$(elementID).addClass("neighbor-item-selected");

		this._onSelection(arcID);
	}
};


code:
App.Neighbor.Interact = function() {
	const node = new DocumentFragment();

	App.UI.DOM.appendNewElement("h1", node, `Diplomacy`);

	if (V.cheatMode === 1) {
		App.UI.DOM.appendNewElement("div", node,
			App.UI.DOM.passageLink("Cheat Edit Neighboring Arcologies", "Neighbor Arcology Cheat"),
			["cheat-menu"]);
	}
	App.UI.DOM.appendNewElement("p", node, `You have ${V.arcologies.length - 1} neighbors.`);

	/** Return how much resources are being put towards the current choice.
	 * @param {string} option
	 */
	function affect(option) {
		if (V.arcologies[0][option] === 3) {
			return App.UI.DOM.makeElement("span", "widespread", "bold");
		} else if (V.arcologies[0][option] === 2) {
			return App.UI.DOM.makeElement("span", "limited", "bold");
		} else {
			return App.UI.DOM.makeElement("span", "nominal", "bold");
		}
	}

	/** Return a list of links based off how much resources are being put towards the current choice.
	 * @param {string} option
	 */
	function makeLinkList(option) {
		let linkArray = [];
		if (V.arcologies[0][option] < 3) {
			linkArray.push(App.UI.DOM.link(
				"Intensify",
				() => {
					V.arcologies[0][option]++;
					App.UI.reload();
				}
			));
		}
		if (V.arcologies[0][option] > 1) {
			linkArray.push(App.UI.DOM.link(
				"Moderate",
				() => {
					V.arcologies[0][option]--;
					App.UI.reload();
				}
			));
		}
		linkArray.push(App.UI.DOM.link(
			"Cancel",
			() => {
				V.arcologies[0][option + "Target"] = -1;
				App.UI.reload();
			}
		));
		return App.UI.DOM.generateLinksStrip(linkArray);
	}

	let nei = V.arcologies.find(function(s) { return s.direction === V.arcologies[0].embargoTarget; });
	if (!nei) {
		App.UI.DOM.appendNewElement("div", node, `You are not engaged in economic warfare against a neighboring arcology.`);
	} else {
		App.Events.addNode(node, [
			`You have targeted`,
			App.UI.DOM.makeElement("span", nei.name, "bold"),
			`for`,
			affect("embargo"),
			`economic warfare.`
		], "div");
		App.UI.DOM.appendNewElement("div", node, makeLinkList("embargo"), "indent");
	}

	nei = V.arcologies.find(function(s) { return s.direction === V.arcologies[0].influenceTarget; });
	if (!nei) {
		App.UI.DOM.appendNewElement("div", node, `You are not using your arcology's culture to attempt to influence neighboring arcologies' development.`);
	} else {
		App.Events.addNode(node, [
			`You have targeted`,
			App.UI.DOM.makeElement("span", nei.name, "bold"),
			`for cultural influence.`,
			App.UI.DOM.link("Stop",
				() => {
					V.arcologies[0].influenceTarget = -1;
					App.UI.reload();
				}
			)
		], "div");
	}

	if (V.PC.skill.hacking > 0) {
		nei = V.arcologies.find(function(s) { return s.direction === V.arcologies[0].CyberEconomicTarget; });
		if (!nei) {
			App.UI.DOM.appendNewElement("div", node, `You are not engaged in cyber warfare against a neighboring arcology.`);
		} else {
			App.Events.addNode(node, [
				`You have targeted`,
				App.UI.DOM.makeElement("span", nei.name, "bold"),
				`for`,
				affect("CyberEconomic"),
				`cyber economic warfare.`
			], "div");
			App.UI.DOM.appendNewElement("div", node, makeLinkList("CyberEconomic"), "indent");
		}

		nei = V.arcologies.find(function(s) { return s.direction === V.arcologies[0].CyberReputationTarget; });
		if (!nei) {
			App.UI.DOM.appendNewElement("div", node, `You are not engaged in character assassination against a neighboring arcology.`);
		} else {
			App.Events.addNode(node, [
				`You have targeted the leadership of`,
				App.UI.DOM.makeElement("span", nei.name, "bold"),
				`for`,
				affect("CyberReputation"),
				`character assassination.`
			], "div");
			App.UI.DOM.appendNewElement("div", node, makeLinkList("CyberReputation"), "indent");
		}
	}

	const interact = (function() {
		let nd = new App.Neighbor.Display((id) => replaceDetails(id));
		const DEMAND_PER_PERCENT = 2;

		return list;

		/** Output the arcology list
		 * @returns {DocumentFragment}
		 */
		function list() {
			for (let i = 1; i < V.arcologies.length; i++) {
				V.arcologies[i].prosperity = Math.clamp(V.arcologies[i].prosperity, 1, 400);
			}
			if (!V.temp.activeArcologyIdx) {
				V.temp.activeArcologyIdx = 0;
			}

			let frag = document.createDocumentFragment();

			// set up the neighbor display list itself
			frag.append(nd.render());
			$(document).one(':passagedisplay', () => nd.select(V.temp.activeArcologyIdx));

			// empty container for details
			const detailSpan = App.UI.DOM.appendNewElement("span", frag, "", "margin-top");
			detailSpan.id = "neighbor-details";

			return frag;
		}

		/** Replace the details block with an updated one for the given arcology
		 * Used both as a refresh and as a selection handler
		 * @param {number} arcID
		 */
		function replaceDetails(arcID) {
			V.temp.activeArcologyIdx = arcID;
			const container = $("#neighbor-details").empty();
			container.append(App.UI.neighborDescription(arcID));
			container.append(details(arcID));
		}

		/** Re-render most of the page because some important arcology property (FS, government, ownership) may have changed
		 * @param {number} arcID for reselection
		 */
		function arcChanged(arcID) {
			nd.rerender();
			nd.select(arcID);
		}

		/** Create a fragment containing all the details for a given arcology
		 * @param {number} arcID
		 * @returns {DocumentFragment}
		 */
		function details(arcID) {
			let frag = document.createDocumentFragment();
			if (arcID === 0) {
				const desc = FutureSocieties.activeFSes(V.arcologies[0]).map((f) => FutureSocieties.displayName(f));
				if (desc.length === 0) {
					App.UI.DOM.appendNewElement("p", frag, `Your arcology's culture has not developed to the point where it can meaningfully influence other arcologies.`);
				} else if (desc.length > 2) {
					App.UI.DOM.appendNewElement("p", frag, `Your arcology's mature culture is capable of exerting great cultural sway over other arcologies. It can readily project ${toSentence(desc)}.`);
				} else if (desc.length === 2) {
					App.UI.DOM.appendNewElement("p", frag, `Your arcology's culture is capable of exerting some cultural sway over other arcologies. It can effectively project ${desc[0]} and ${desc[1]}.`);
				} else {
					App.UI.DOM.appendNewElement("p", frag, `Your arcology's culture is capable of starting to exert cultural sway over other arcologies. It can project ${desc[0]}.`);
				}
			} else {
				frag.append(`A 1% interest in this arcology is worth ${cashFormat(buyValue(V.arcologies[arcID], 1))}. `);
				if (V.arcologies[arcID].ownership + V.arcologies[arcID].PCminority + V.arcologies[arcID].minority < 100) {
					frag.append(`The transaction fee is ${cashFormat(10000)}.`);
				}

				if (V.arcologies[arcID].ownership + V.arcologies[arcID].PCminority + V.arcologies[arcID].minority < 100) {
					let links = [];
					links.push(App.UI.DOM.link("Buy", () => {
						cashX(-(buyValue(V.arcologies[arcID], 1) + 10000), "war");
						V.arcologies[arcID].PCminority += 1;
						V.arcologies[arcID].demandFactor += DEMAND_PER_PERCENT;
					}, [], "Neighbor Interact"));
					if (V.arcologies[arcID].ownership + V.arcologies[arcID].PCminority + V.arcologies[arcID].minority <= 90) {
						if (V.cash > buyValue(V.arcologies[arcID], 10) + 10000) {
							const link = App.UI.DOM.link("10%", () => {
								cashX(-(buyValue(V.arcologies[arcID], 10) + 10000), "war");
								V.arcologies[arcID].PCminority += 10;
								V.arcologies[arcID].demandFactor += DEMAND_PER_PERCENT * 10;
							}, [], "Neighbor Interact");
							links.push(link);
						}
					}
					const div = App.UI.DOM.appendNewElement("div", frag, App.UI.DOM.generateLinksStrip(links));
					if (links.length > 1) {
						App.UI.DOM.appendNewElement("span", div, " Transaction costs will only be paid once.", ["detail"]);
					}
				}

				if (V.arcologies[arcID].PCminority > 0) {
					let links = [];
					links.push(App.UI.DOM.link("Sell", () => {
						cashX(sellValue(V.arcologies[arcID], 1), "war");
						V.arcologies[arcID].PCminority -= 1;
						V.arcologies[arcID].demandFactor -= DEMAND_PER_PERCENT;
						if (V.arcologies[arcID].government !== "your agent" && V.arcologies[arcID].government !== "your trustees" && V.arcologies[arcID].rival !== 1) {
							if (V.arcologies[arcID].ownership + V.arcologies[arcID].PCminority + V.arcologies[arcID].minority < 10) {
								V.arcologies[arcID].ownership += 10;
							}
						}
						arcChanged(arcID);
					}, [], "Neighbor Interact"));
					if (V.arcologies[arcID].PCminority >= 10) {
						links.push(App.UI.DOM.link("10%", () => {
							cashX(sellValue(V.arcologies[arcID], 10), "war");
							V.arcologies[arcID].PCminority -= 10;
							V.arcologies[arcID].demandFactor -= DEMAND_PER_PERCENT * 10;
							if (V.arcologies[arcID].government !== "your agent" && V.arcologies[arcID].government !== "your trustees" && V.arcologies[arcID].rival !== 1) {
								if (V.arcologies[arcID].ownership + V.arcologies[arcID].PCminority + V.arcologies[arcID].minority < 10) {
									V.arcologies[arcID].ownership += 10;
								}
							}
						}, [], "Neighbor Interact"));
					}
					App.UI.DOM.appendNewElement("div", frag, App.UI.DOM.generateLinksStrip(links));
				}

				if (V.arcologies[arcID].direction !== V.arcologies[0].embargoTarget) {
					frag.append(document.createElement("br"));
					frag.append(App.UI.DOM.passageLink("Target them for economic warfare", "Neighbor Interact", () => V.arcologies[0].embargoTarget = V.arcologies[arcID].direction));
				}

				if (V.PC.skill.hacking > 0) {
					if (V.arcologies[arcID].direction !== V.arcologies[0].CyberEconomicTarget) {
						frag.append(document.createElement("br"));
						frag.append(App.UI.DOM.passageLink("Target them for cyber economic warfare", "Neighbor Interact", () => V.arcologies[0].CyberEconomicTarget = V.arcologies[arcID].direction));
					}
					if (V.arcologies[arcID].direction !== V.arcologies[0].CyberReputationTarget) {
						frag.append(document.createElement("br"));
						frag.append(App.UI.DOM.passageLink("Target their leadership for character assassination", "Neighbor Interact", () => V.arcologies[0].CyberReputationTarget = V.arcologies[arcID].direction));
					}
				}
				if (FutureSocieties.influenceSources(0).length > 0) {
					if (V.arcologies[arcID].direction !== V.arcologies[0].influenceTarget) {
						frag.append(document.createElement("br"));
						frag.append(App.UI.DOM.passageLink("Set as influence target", "Neighbor Interact", () => V.arcologies[0].influenceTarget = V.arcologies[arcID].direction));
					}
				}

				if (V.arcologies[arcID].government === "your trustees" || V.arcologies[arcID].government === "your agent") {
					frag.append(controlActions(arcID));
				}

				frag.append(fsGoods(arcID));
			}
			return frag;
		}

		/**
		 * How much does it cost to buy x percent of a given arcology
		 * @param {FC.ArcologyState} arc
		 * @param {number} percent
		 * @returns {number}
		 */
		function buyValue(arc, percent) {
			const baseValue = 500 * arc.prosperity;
			// scale the demand in such a way that buying twice in a row costs the same as buying double once.
			const scaledDemand = arc.demandFactor + (DEMAND_PER_PERCENT * (percent / 2));
			const scalingFactor = 1 + (scaledDemand / 100);
			return Math.round(baseValue * scalingFactor * percent);
		}

		/**
		 * How much does the player get when selling x percent of a given arcology
		 * @param {FC.ArcologyState} arc
		 * @param {number} percent
		 * @returns {number}
		 */
		function sellValue(arc, percent) {
			const baseValue = 500 * arc.prosperity;
			// scale the demand in such a way that selling twice in a row gives the same as selling double once.
			const scaledDemand = arc.demandFactor - (DEMAND_PER_PERCENT * (percent / 2));
			const scalingFactor = 1 + (scaledDemand / 100);
			return Math.round(baseValue * scalingFactor * percent);
		}

		/** Create a div containing actions specific to arcologies that are under the player's control
		 * @param {number} arcID
		 * @returns {Element}
		 */
		function controlActions(arcID) {
			const container = document.createElement("div");
			const agent = App.currentAgent(arcID);
			const him = agent ? getPronouns(agent).him : "them";
			container.append(document.createElement("br"));
			if (V.arcologies[arcID].government === "your trustees") {
				container.append(App.UI.DOM.passageLink("Appoint an agent", "Agent Select"));
			} else {
				let linkText = `Recall and reenslave ${him}`;
				const residentList = [agent.ID];
				const agentPartner = getSlaves().find((s) => s.assignment === Job.AGENTPARTNER && s.relationshipTarget === agent.ID)?.value;
				if (agentPartner) {
					linkText = `Recall them and reenslave your agent`;
					residentList.push(agentPartner.ID);
				}
				container.append(App.UI.SlaveList.render(residentList, [], App.UI.SlaveList.SlaveInteract.stdInteract));
				container.append(App.UI.DOM.link(linkText, () => {
					removeJob(agent, "be your agent");
					arcChanged(arcID);
				}));
			}
			container.append(" | ");
			const rename = App.UI.DOM.appendNewElement("span", container, '');
			rename.id = "rename";
			rename.append(App.UI.DOM.link(`Instruct ${him} to rename the arcology`, () => $("#rename").replaceWith([
				App.UI.DOM.makeTextBox(V.arcologies[arcID].name, (s) => {
					V.arcologies[arcID].name = s;
				}, false),
				App.UI.DOM.link("Confirm name", arcChanged, [arcID])
			])));

			if (V.arcologies[arcID].government === "your agent") {
				const {His, his, he, himself /* him is already set */} = getPronouns(agent);
				let r = [];
				r.push(`${His} ${agent.intelligence > 95 ? `brilliance` : `intelligence`} and education are the most important qualities for ${him}.`);
				if (agent.actualAge > 35) {
					r.push(`As with the Head Girl position, ${his} age and experience lend ${him} leadership weight.`);
				}
				if (agent.career === "an arcology owner") {
					r.push(`${His} career as an arcology owner ${himself} is, obviously, useful to ${him}.`);
				} else if (App.Data.Careers.Leader.HG.includes(agent.career)) {
					r.push(`${His} career in leadership helps ${him}.`);
				}
				if (agent.fetishStrength > 95) {
					if ((agent.fetish === "dom") || (agent.fetish === "sadist")) {
						r.push(`${His} sexually dominant fetish helps ${him} fill a leadership role.`);
					} else if ((agent.fetish === Fetish.SUBMISSIVE) || (agent.fetish === "masochist")) {
						r.push(`Unfortunately, ${he} has an inappropriate fetish for a leader.`);
					} else {
						r.push(`${His} sexual fetishes will influence how ${he} leads the arcology.`);
					}
				}
				if (agent.energy > 95) {
					r.push(`Finally, ${his} sexual depravity lets ${him} fit into arcology society naturally.`);
				}
				App.UI.DOM.appendNewElement("div", container, r.join(' '));
			}

			container.append(document.createElement("br"));
			const forceAbandonment = (fs) => {
				V.arcologies[arcID][fs] = null;
				arcChanged(arcID);
			};
			for (const fs of FutureSocieties.activeFSes(V.arcologies[arcID])) {
				App.UI.DOM.appendNewElement("div", container, App.UI.DOM.link(`Force abandonment of ${FutureSocieties.displayName(fs)}`, forceAbandonment, [fs]));
			}

			return container;
		}

		/** Create an element containing all the society-dependent stuff you can buy from this arcology.
		 * @param {number} arcID
		 * @returns {Element}
		 */
		function fsGoods(arcID) {
			const container = document.createElement("div");
			const arcology = V.arcologies[arcID];
			let r = [];
			r.push(`If ${arcology.name} has developed enough to begin exporting worthwhile goods, it may be of interest to acquire some.`);
			const opinionDiscount = App.Neighbor.opinion(arcology, V.arcologies[0]) * 10;
			const basePrice = Math.trunc((7500 - opinionDiscount) * V.upgradeMultiplierTrade);
			const controlled = (arcology.government === "your trustees") || (arcology.government === "your agent");
			if (controlled) {
				r.push(`Since it is under your control, it is no problem at all to request the transfer of goods to ${V.arcologies[0].name}.`);
			} else if (V.PC.skill.hacking >= 50) {
				r.push(`It is within your skills to redirect an outgoing shipment to ${V.arcologies[0].name} for your retrieval.`);
			} else if (arcology.direction === V.arcologies[0].embargoTarget) {
				r.push(`However, due to your active embargo, trade with ${arcology.name} is not possible.`);
			}
			App.UI.DOM.appendNewElement("p", container, r.join(' '));

			let exports = 0;

			/** Build a link or text block describing how to acquire a specific good from this arcology
			 * @param {string} fsRequired - the FS that the arcology has to have for this block to appear
			 * @param {string} itemName - the item name to check to see if the player already has this item
			 * @param {string} category - the category to check to see if the player already has this item
			 * @param {string} itemDisplay - a display name for a group of the item; as in, "a shipment of XXX" or "enough XXX"
			 * @param {function(): void} property - adjusts the global property controlling whether this item has been acquired
			 * @param {number} [itemPrice] - the price the player should pay for the item; by default, basePrice (computed above)
			 */
			function addAcquisitionBlock(fsRequired, itemName, category, itemDisplay, property, itemPrice = basePrice) {
				if (arcology[fsRequired] > 95) {
					if (!isItemAccessible.entry(itemName, category)) {
						if (controlled) {
							const link = App.UI.DOM.link(`Request a shipment of ${itemDisplay}`, () => {
								property();
								replaceDetails(arcID);
							});
							App.UI.DOM.appendNewElement("div", container, link);
						} else if (V.PC.skill.hacking >= 50) {
							const link = App.UI.DOM.link(`Divert an outgoing shipment of ${itemDisplay}`, () => {
								property();
								replaceDetails(arcID);
							});
							App.UI.DOM.appendNewElement("div", container, link);
						} else if (arcology.direction !== V.arcologies[0].embargoTarget) {
							const link = App.UI.DOM.link(`Divert an outgoing shipment of ${itemDisplay}`, () => {
								property();
								cashX(forceNeg(itemPrice), "capEx");
								replaceDetails(arcID);
							});
							const div = App.UI.DOM.appendNewElement("div", container, link);
							App.UI.DOM.appendNewElement("span", div, `Will cost ${cashFormat(itemPrice)}`, ["detail"]);
						}
					} else {
						App.UI.DOM.appendNewElement("div", container, `You already have enough ${itemDisplay}.`);
					}
					exports = 1;
				}
			}

			addAcquisitionBlock("FSRomanRevivalist", "a toga", "clothes", "togas", () => { V.boughtItem.clothing.toga = 1; });
			addAcquisitionBlock("FSEdoRevivalist", "a kimono", "clothes", "kimonos", () => { V.boughtItem.clothing.kimono = 1; });
			addAcquisitionBlock("FSArabianRevivalist", "harem gauze", "clothes", "silken harem garb", () => { V.boughtItem.clothing.harem = 1; });
			addAcquisitionBlock("FSAztecRevivalist", "a huipil", "clothes", "huipils", () => { V.boughtItem.clothing.huipil = 1; });
			addAcquisitionBlock("FSChineseRevivalist", "a slutty qipao", "clothes", "skimpy qipaos", () => { V.boughtItem.clothing.qipao = 1; });
			addAcquisitionBlock("FSEgyptianRevivalist", "ancient Egyptian", "collar", "Egyptian necklace replicas", () => { V.boughtItem.clothing.egypt = 1; });
			addAcquisitionBlock("FSPaternalist", "conservative clothing", "clothes", "conservative clothing", () => { V.boughtItem.clothing.conservative = 1; });
			addAcquisitionBlock("FSDegradationist", "chains", "clothes", "binding chains", () => { V.boughtItem.clothing.chains = 1; });
			addAcquisitionBlock("FSGenderFundamentalist", "a bunny outfit", "clothes", "bunny suits", () => { V.boughtItem.clothing.bunny = 1; });
			addAcquisitionBlock("FSIntellectualDependency", "a bimbo outfit", "clothes", "bimbo attire", () => { V.boughtItem.clothing.bimbo = 1; });
			addAcquisitionBlock("FSSlaveProfessionalism", "a courtesan dress", "clothes", "courtesan dresses", () => { V.boughtItem.clothing.courtesan = 1; });
			// addAcquisitionBlock("FSPetiteAdmiration", "petite dress", "clothes", "petite-sized dresses", () => { V.boughtItem.clothing.petite = 1; });
			addAcquisitionBlock("FSPhysicalIdealist", "body oil", "clothes", "body oil", () => { V.boughtItem.clothing.oil = 1; });
			addAcquisitionBlock("FSHedonisticDecadence", "stretch pants and a crop-top", "clothes", "stretch pants and crop-tops", () => { V.boughtItem.clothing.lazyClothes = 1; });
			addAcquisitionBlock("FSChattelReligionist", "a chattel habit", "clothes", "chattel religionist habits", () => { V.boughtItem.clothing.habit = 1; });
			addAcquisitionBlock("FSPastoralist", "Western clothing", "clothes", "Western clothing", () => { V.boughtItem.clothing.western = 1; });
			addAcquisitionBlock("FSRepopulationFocus", "a maternity dress", "clothes", "maternity dresses", () => { V.boughtItem.clothing.maternityDress = 1; });
			addAcquisitionBlock("FSRepopulationFocus", "attractive lingerie for a pregnant woman", "clothes", "maternity lingerie", () => { V.boughtItem.clothing.maternityLingerie = 1; });
			addAcquisitionBlock("FSRepopulationFocus", "a small empathy belly", "bellyAccessory", "empathy bellies", () => { V.boughtItem.clothing.belly = 1; });
			addAcquisitionBlock("FSStatuesqueGlorification", "platform heels", "shoes", "platform shoes", () => { V.boughtItem.shoes.heels = 1; });

			if (exports !== 1) {
				const luck = (arcology.direction === V.arcologies[0].embargoTarget) ? `Fortunately` : `Unfortunately`;
				App.UI.DOM.appendNewElement("p", container, `${luck}, they have nothing of value.`);
			}

			return container;
		}
	})();

	node.append(interact());

	return node;
};


code:
App.UI.SlaveInteract.agentAssignmentScene = function() {
	const node = new DocumentFragment();
	const slave = getSlave(V.temp.AS);
	let r = [];
	assignJob(slave, "be your agent");

	if (slave.rivalry > 0) {
		const rival = getSlave(slave.rivalryTarget);
		if (rival) {
			rival.rivalry = 0;
			rival.rivalryTarget = 0;
		} else {
			r.push(`<span class="red">Error, rival not found.</span>`);
		}
		slave.rivalry = 0;
		slave.rivalryTarget = 0;
	}

	if (slave.relationship.isBetween(0, 4)) {
		const relation = getSlave(slave.relationshipTarget);
		if (relation) {
			relation.relationship = 0;
			relation.relationshipTarget = 0;
		} else {
			r.push(`<span class="red">Error, relationshipTarget not found.</span>`);
		}
		slave.relationship = 0;
		slave.relationshipTarget = 0;
	}

	const {
		He,
		he, his, him, woman
	} = getPronouns(slave);
	const {title: Master} = getEnunciation(slave);

	App.Events.drawEventArt(node, slave);

	r.push(`You order ${slave.slaveName} to come into your office for orders. The devoted ${SlaveTitle(slave)} has no prior warning that this is anything unusual, and from the`);
	if (slave.energy > 95) {
		r.push(`horny glint`);
	} else {
		r.push(`glow of sexual availability`);
	}
	r.push(`in ${his} ${App.Desc.eyesColor(slave)} as ${he}`);
	if (canWalk(slave)) {
		r.push(`enters,`);
	} else {
		r.push(`is helped in,`);
	}
	r.push(he);
	if (slave.energy > 95) {
		r.push(`seems to be hoping`);
	} else {
		r.push(`wouldn't mind hearing`);
	}
	r.push(`that you've called ${him} in for a quick fuck. In fact, the matter at hand is about as far as it could possibly be from that, on the spectrum of interactions between ${properMaster()} and slave.`);

	App.Events.addParagraph(node, r);
	r = [];

	App.Events.addParagraph(node, [`You offer ${him}, without preamble, agency on your behalf in ${App.activeArcology().name}. As a ${woman} of intelligence, ${he} knows the basics of such an arrangement, but ${he}'s stunned into silence by the offer, so you outline it briefly. Having a slave lead an arcology would be too much, even for the most open minded of Free Cities societies. So, ${he} would be freed, albeit subject to a set of private, restrictive contracts that would allow you to recall ${him} if you wish, and require ${him} to follow your directions, if you give ${him} any. But other than that, ${he} would be the effective leader of an entire arcology. It's an unusual arrangement, but one that's becoming more common as multiple arcologies come under united control.`]);

	r.push(`${He}'s intelligent and confident of ${his} place within society as a valued slave, and knows that with your resources behind ${him}, this is likely something ${he}'s capable of succeeding at. Nevertheless, ${his} feelings are obviously mixed. It will be a challenge,`);
	switch (slave.fetish) {
		case "submissive":
			r.push(`especially for a sexual submissive like ${him}.`);
			break;
		case "cumslut":
			r.push(`and ${he}'ll have to find dominant ways to sate ${his} oral appetites.`);
			break;
		case "humiliation":
			r.push(`especially for a humiliation addict like ${him}.`);
			break;
		case "buttslut":
			r.push(`though ${he}'ll probably be able to live ${his} anal life vicariously by buttfucking others.`);
			break;
		case "boobs":
			r.push(`but ${he}'ll have a practically infinite procession of breasts to judge and fondle.`);
			break;
		case "pregnancy":
			r.push(`but the potential for impregnation of hundreds of slaves excites ${him}.`);
			break;
		case "dom":
			r.push(`but the prospect of having more slaves at ${his} whim than even ${he} could possibly use is incredibly exciting to ${him}.`);
			break;
		case "sadist":
			r.push(`but the prospect of having more slaves at ${his} whim than even ${he} could possibly break is incredibly exciting to ${him}.`);
			break;
		case "masochist":
			r.push(`especially for a masochist like ${him}.`);
			break;
		default:
			r.push(`though one ${he}'s willing to take on.`);
	}
	r.push(`It will mean that ${he}'ll see much less of you, something such a devoted slave may struggle with. Oppressed by the confusion and strength of ${his} feelings, ${he} begins to cry.`);
	App.Events.addParagraph(node, r);

	App.Events.addParagraph(node, [
		Spoken(slave, `"Thank you, ${Master},"`),
		`${he} gasps out. "I'll do my best."`
	]);

	return node;
};


code:
/** Reassign your agent's lover to the arcology run by your agent
 * @param {FC.SlaveState} lover
 * @returns {DocumentFragment}
 */
App.UI.SlaveInteract.agentCompany = function(lover) {
	const frag = document.createDocumentFragment();

	const agent = getSlave(lover.relationshipTarget);
	const neighborArcology = V.arcologies.find((a) => agent.ID === a.leaderID);
	const {he, him, his, himself} = getPronouns(lover);
	const {He2, His2, he2, him2, his2, wife2} = getPronouns(agent).appendSuffix('2');
	const {say} = getEnunciation(lover);
	const {say: say2} = getEnunciation(agent);

	// make the assignment
	assignJob(lover, Job.AGENTPARTNER);

	// clear rivalry if applicable
	const rival = getSlave(lover.rivalryTarget);
	if (rival) {
		rival.rivalry = 0;
		rival.rivalryTarget = 0;
	}
	lover.rivalry = 0;
	lover.rivalryTarget = 0;

	// output text
	let r = [];
	r.push(`Working in ${neighborArcology.name}'s penthouse office, ${agent.slaveName} gets a video call from you. Naturally, ${his2} personal assistant program has been instructed to give these the highest priority, and your face appears in front of ${him2} immediately. ${He2} greets you properly, and asks you what ${he2} can do to serve you. You tell ${him2} you're planning to send one of your slaves over to ${his2} arcology to assist ${him2}. Exchanges of this kind are neither unusual nor particularly interesting, so a slight look of perplexity clouds ${agent.slaveName}'s face as ${he2} wonders why this merits a personal call. Normally, ${S.HeadGirl ? S.HeadGirl.slaveName : V.assistant.name} would handle it with ${him2} directly.`);
	App.Events.addParagraph(frag, r);

	r = [];
	r.push(`${His2} question is answered when you bring ${contextualIntro(agent, lover)} in close, putting ${him} in ${agent.slaveName}'s view. ${agent.slaveName}'s ${App.Desc.eyesColor(agent)} fly open with pleased surprise, but before ${he2} can speak, ${lover.slaveName} goes first.`);

	if (!canTalk(lover)) {
		r.push(`Unable to speak ${himself}, ${he} simply makes a heart shape with ${his} ${hasBothArms(lover) ? "hands" : "hand"} in view of the video call, and blows ${his} ${lover.relationship === 4 ? "lover" : wife2} a kiss.`);
	} else {
		r.push(Spoken(lover, `"Hi ${lover.relationship === 4 ? 'lover' : 'sweetheart'},"`));
		r.push(`${he} ${say}s in a syrupy tone.`);
		r.push(Spoken(lover, `"Miss me?"`));
	}
	App.Events.addParagraph(frag, r);

	r = [];
	r.push(`${agent.slaveName} smiles back at ${him}, glowing with pleasure, and responds with exaggerated arousal.`);
	switch (agent.fetish) {
		case "submissive":
			r.push(Spoken(agent, `"Looking forward to sleeping ${hasBothArms(agent) ? "in your arms" : "with you"}, babe,"`), `the submissive`);
			break;
		case "cumslut":
			r.push(Spoken(agent, `"Can't wait to kiss you, babe,"`), `the orally fixated`);
			break;
		case "humiliation":
			r.push(Spoken(agent, `"Can't wait to take you right in the middle of the plaza here,"`), `the exhibitionist`);
			break;
		case "buttslut":
			r.push(Spoken(agent, `"I can't wait to fuck you in your hot little ass,"`), `the anally fixated`);
			break;
		case "boobs":
			r.push(Spoken(agent, `"Looking forward to feeling your breasts again,"`), `the boob-loving`);
			break;
		case "pregnancy":
			r.push(Spoken(agent, `"Can't wait to share all the pregnant girls here with you,"`), `the impregnation fetishist`);
			break;
		case "dom":
			r.push(Spoken(agent, `"Looking forward to shoving you facedown, bitch,"`), `the dominant`);
			break;
		case "sadist":
			r.push(Spoken(agent, `"Looking forward to making you bite the pillow again, bitch,"`), `the sadistic`);
			break;
		case "masochist":
			r.push(Spoken(agent, `"Can't wait to feel you hurt me again, babe,"`), `the masochistic`);
			break;
		default:
			r.push(Spoken(agent, `"Babe, I can't wait to ${hasAnyArms(agent) ? "give you a hug and" : ""} tell you about ${neighborArcology.name},"`), `the loving`);
	}
	r.push(`leader of an entire arcology ${say2}s.`);
	App.Events.addParagraph(frag, r);

	return frag;
};


code:
App.Data.Facilities.arcologyAgent = {
	baseName: "arcology",
	genericName: null,
	jobs: {
		agentsSlave: {
			position: "agent's toy",
			assignment: Job.AGENTPARTNER,
			publicSexUse: true,
			fuckdollAccepted: false
		}
	},
	defaultJob: "agentsSlave",
	manager: {
		position: "agent",
		assignment: Job.AGENT,
		careers: ["an arcology owner"],
		skill: "headGirl",
		publicSexUse: true,
		fuckdollAccepted: false,
		broodmotherAccepted: false,
		shouldWalk: true,
		shouldHold: true,
		shouldSee: true,
		shouldHear: true,
		shouldTalk: true,
		shouldThink: true,
		requiredDevotion: 21
	},
	decorated: false
};

App.Entity.Facilities.AgentJob = class extends App.Entity.Facilities.ManagingJob {
	/**
	 * @param {FC.SlaveState} slave
	 * @returns {string[]}
	 */
	checkRequirements(slave) {
		let r = super.checkRequirements(slave);
		if (slave.intelligence + slave.intelligenceImplant <= 15) {
			r.push(`${slave.slaveName} is not intelligent enough to be your agent.`);
		}
		return r;
	}
};

App.Entity.facilities.arcologyAgent = new App.Entity.Facilities.Facility(
	App.Data.Facilities.arcologyAgent,
	{
		// agent's partner, eventually
	},
	new App.Entity.Facilities.AgentJob()
);


code:
// cSpell:ignore Austeyr, Bofors, BREN, CETME, CZUB, Cugir, Daewoo, DGIM, estoc, FACSA, FAMAE, FAMAS, Ferfrans, FMAP,
// cSpell:ignore Glauberyt, Glock, GPMG, IMBEL, Indumil, Izhmash, jambiya, Khaybar, khopesh, kightly, kilij, KRISS
// cSpell:ignore Lithgow, macuahuitl, MCIWS, Minebea, MKEK, Modelo, Molot, MSBS, MSMC, MTAR, Nexter, Norinco
// cSpell:ignore Pecheneg, Pernach, Pistolul, Produkt, Rachot, Rheinmetall, SAKO, Sarsilmaz, shamshir, shotel, Stechkin
// cSpell:ignore Steyr, Vektor, wakizashi, WITU, Xiuhcoatl, yatagan, Zastava

/**
 * @param {FC.SlaveState} slave
 * @returns {string}
 */
App.Desc.bodyguard = function(slave) {
	const r = [];
	const {
		his, He, he
	} = getPronouns(slave);
	if (slave.fuckdoll !== 0 || slave.ID !== V.BodyguardID) {
		return "";
	}
	r.push(`${He} is your bodyguard.`);
	r.push(bgWeapon());

	if (slave.skill.combat > 30) {
		r.push(`${He} is <span class="skill">skilled at combat:</span> ${he} is comfortable with the use of modern firearms and edged weapons, and ${his} hands`);
		if (!hasBothArms(slave)) {
			r.push(`would be deadly weapons if ${he} had`);
			if (!hasAnyArms(slave)) {
				r.push(`any.`);
			} else {
				r.push(`more than one.`);
			}
		} else {
			if ((getArmCount(slave, 5) + getArmCount(slave, 6) > 1)) {
				r.push(`would be deadly weapons even if they weren't full of deadly weapons already.`);
			} else {
				r.push(`are deadly weapons.`);
			}
		}
	}

	return r.join(" ");

	function bgWeapon() {
		const w = [];
		const arcInfo = new App.Utils.Arcology(V.arcologies[0]);
		if ((slave.muscles + slave.height - 100) / 25 > 5) {
			w.push(`${He} has a long ceramic`);
			switch (arcInfo.revivalSociety()) {
				case RevivalSociety.ROMAN: w.push('spatha'); break;
				case RevivalSociety.NEO_IMPERIAL: w.push(`claymore`); break;
				case RevivalSociety.EGYPTIAN: w.push(`shotel`); break;
				case RevivalSociety.EDO: w.push(`odachi`); break;
				case RevivalSociety.ARABIAN: w.push(`kilij`); break;
				case RevivalSociety.CHINESE: w.push(`changdao`); break;
				case RevivalSociety.AZTEC: w.push(`macuahuitl`); break;
				case RevivalSociety.ANTEBELLUM: w.push(`saber`); break;
				case null: w.push(``); break;
			}
			w.push(`sword strapped to ${his} back,`);
			switch (slave.nationality) {
				case "American":
					w.push(`and, in keeping with ${his} heritage, a US Ordnance Mk43 Mod.1 GPMG`);
					break;
				case "Armenian":
				case "Belarusian":
				case "Cuban":
				case "Hungarian":
				case "Kazakh":
				case "Uzbek":
					w.push(`and, in keeping with ${his} heritage, a PKM GPMG`);
					break;
				case "Australian":
					w.push(`and, in keeping with ${his} heritage, a Lithgow F89 Maximi GPMG`);
					break;
				case "Austrian":
					w.push(`and, in keeping with ${his} heritage, a Steyr MG 74 GPMG`);
					break;
				case "Belgian":
					w.push(`and, in keeping with ${his} heritage, a FN Minimi M3 LMG`);
					break;
				case "British":
				case "Scottish":
					w.push(`and, in keeping with ${his} heritage, an L7A2 GPMG`);
					break;
				case "Canadian":
					w.push(`and, in keeping with ${his} heritage, a Colt Canada 5.56-LSW LMG`);
					break;
				case "Chilean":
				case "German":
					w.push(`and, in keeping with ${his} heritage, a Rheinmetall MG3 GPMG`);
					break;
				case "Chinese":
					w.push(`and, in keeping with ${his} heritage, a Norinco QJY-88 LMG`);
					break;
				case "Czech":
				case "Slovak":
					w.push(`and, in keeping with ${his} heritage, a ZVI Uk vz.59 Rachot GPMG`);
					break;
				case "Filipina":
					w.push(`and, in keeping with ${his} heritage, a Government Arsenal M60E4 GPMG`);
					break;
				case "Finnish":
					w.push(`and, in keeping with ${his} heritage, a Valmet KvKK 62 LMG`);
					break;
				case "French":
					w.push(`and, in keeping with ${his} heritage, a MAS AAT-F1 GPMG`);
					break;
				case "Indian":
					w.push(`and, in keeping with ${his} heritage, an OFB MG 2A1 GPMG`);
					break;
				case "Indonesian":
					w.push(`and, in keeping with ${his} heritage, a Pindad SM2 LMG`);
					break;
				case "Iranian":
					w.push(`and, in keeping with ${his} heritage, a DIO MGA3 GPMG`);
					break;
				case "Israeli":
					w.push(`and, in keeping with ${his} heritage, an IWI Negev NG7 GPMG`);
					break;
				case "Japanese":
					w.push(`and, in keeping with ${his} heritage, a Sumitomo Type 62 GPMG`);
					break;
				case "Korean":
					w.push(`and, in keeping with ${his} heritage, a S&T K12 GPMG`);
					break;
				case "Mexican":
					w.push(`and, in keeping with ${his} heritage, a SEDENA MG-21 GPMG`);
					break;
				case "Polish":
					w.push(`and, in keeping with ${his} heritage, a ZMT UKM-2000 GPMG`);
					break;
				case "Romanian":
					w.push(`and, in keeping with ${his} heritage, a FACSA PM md.93 LMG`);
					break;
				case "Russian":
					w.push(`and, in keeping with ${his} heritage, a PKP Pecheneg GPMG`);
					break;
				case "Serbian":
					w.push(`and, in keeping with ${his} heritage, a Zastava M09 LMG`);
					break;
				case "South African":
					w.push(`and, in keeping with ${his} heritage, a Vektor SS-77 GPMG`);
					break;
				case "Spanish":
					w.push(`and, in keeping with ${his} heritage, a CETME MG 82 Ameli LMG`);
					break;
				case "Swiss":
					w.push(`and, in keeping with ${his} heritage, a W+F-Bern MG51 GPMG`);
					break;
				case "Taiwanese":
					w.push(`and, in keeping with ${his} heritage, a 205th Arsenal T75 LMG`);
					break;
				case "Turkish":
					w.push(`and, in keeping with ${his} heritage, a MKEK MG3 GPMG`);
					break;
				case "Ukrainian":
					w.push(`and, in keeping with ${his} heritage, a Fort-401 LMG`);
					break;
				case "Vietnamese":
					w.push(`and, in keeping with ${his} heritage, a Z111 RPK LMG`);
					break;
				case "Zimbabwean":
					if (slave.race === "white") {
						w.push(`and, in keeping with ${his} heritage, a FN MAG-58 GPMG`);
					} else {
						w.push(`and a FN MAG 60.20 GPMG`);
					}
					break;
				default:
					w.push(`and a FN MAG 60.20 GPMG`);
			}
			w.push(`slung crosswise on ${his} chest.`);
		} else if ((slave.muscles + slave.height - 100) / 25 > 4) {
			w.push(`${He} has a ceramic`);
			switch (arcInfo.revivalSociety()) {
				case RevivalSociety.ROMAN: w.push('gladius'); break;
				case RevivalSociety.NEO_IMPERIAL: w.push(`knightly`); break;
				case RevivalSociety.EGYPTIAN: w.push(`khopesh`); break;
				case RevivalSociety.EDO: w.push(`katana`); break;
				case RevivalSociety.ARABIAN: w.push(`shamshir`); break;
				case RevivalSociety.CHINESE: w.push(`jian`); break;
				case RevivalSociety.AZTEC: w.push(`macuahuitl`); break;
				case RevivalSociety.ANTEBELLUM: w.push(`saber`); break;
				case null: w.push(``); break;
			}
			w.push(`sword strapped to ${his} back`);
			switch (slave.nationality) {
				case "American":
					w.push(`and, in keeping with ${his} heritage, a Colt M16A4 assault rifle`);
					break;
				case "Argentinian":
					w.push(`and, in keeping with ${his} heritage, a FMAP FARA-83 assault rifle`);
					break;
				case "Armenian":
				case "Belarusian":
				case "Kazakh":
				case "Uzbek":
					w.push(`and, in keeping with ${his} heritage, a Kalashnikov AK-74M assault rifle`);
					break;
				case "Australian":
					w.push(`and, in keeping with ${his} heritage, a Lithgow EF88 Austeyr bullpup assault rifle`);
					break;
				case "Austrian":
					w.push(`and, in keeping with ${his} heritage, a Steyr AUG A3 bullpup assault rifle`);
					break;
				case "Belgian":
					w.push(`and, in keeping with ${his} heritage, a FN FNC M2 assault rifle`);
					break;
				case "Brazilian":
					w.push(`and, in keeping with ${his} heritage, an IMBEL IA2 assault rifle`);
					break;
				case "British":
				case "Scottish":
					w.push(`and, in keeping with ${his} heritage, an L85A3 bullpup assault rifle`);
					break;
				case "Canadian":
					w.push(`and, in keeping with ${his} heritage, a Colt Canada MRR assault rifle`);
					break;
				case "Chilean":
					w.push(`and, in keeping with ${his} heritage, a FAMAE ACE 22NC assault rifle`);
					break;
				case "Chinese":
					w.push(`and, in keeping with ${his} heritage, a Norinco QBZ-95-1 bullpup assault rifle`);
					break;
				case "Colombian":
					w.push(`and, in keeping with ${his} heritage, an Indumil ACE 22 assault rifle`);
					break;
				case "Croatian":
					w.push(`and, in keeping with ${his} heritage, an HS Produkt VHS-2 bullpup assault rifle`);
					break;
				case "Cuban":
					w.push(`and, in keeping with ${his} heritage, an AKM assault rifle`);
					break;
				case "Czech":
				case "Slovak":
					w.push(`and, in keeping with ${his} heritage, a CZUB CZ-806 BREN-2 assault rifle`);
					break;
				case "Filipina":
					w.push(`and, in keeping with ${his} heritage, an Elitool M653P carbine`);
					break;
				case "Finnish":
					w.push(`and, in keeping with ${his} heritage, a SAKO Rk 95 TP assault rifle`);
					break;
				case "French":
					w.push(`and, in keeping with ${his} heritage, a Nexter FAMAS G2 bullpup assault rifle`);
					break;
				case "German":
					w.push(`and, in keeping with ${his} heritage, a H&K G36A2 assault rifle`);
					break;
				case "Hungarian":
					w.push(`and, in keeping with ${his} heritage, a FEG AMD-65M assault rifle`);
					break;
				case "Indian":
					w.push(`and, in keeping with ${his} heritage, an OFB MCIWS assault rifle`);
					break;
				case "Indonesian":
					w.push(`and, in keeping with ${his} heritage, a Pindad SS2 assault rifle`);
					break;
				case "Iranian":
					w.push(`and, in keeping with ${his} heritage, a DIO KH-2002 Khaybar bullpup assault rifle`);
					break;
				case "Israeli":
					w.push(`and, in keeping with ${his} heritage, an IWI MTAR-21 bullpup assault rifle`);
					break;
				case "Italian":
					w.push(`and, in keeping with ${his} heritage, a Beretta ARX-160 assault rifle`);
					break;
				case "Japanese":
					w.push(`and, in keeping with ${his} heritage, a Howa Type 89 assault rifle`);
					break;
				case "Korean":
					w.push(`and, in keeping with ${his} heritage, a Daewoo K2C1 assault rifle`);
					break;
				case "Mexican":
					w.push(`and, in keeping with ${his} heritage, a DGIM FX-05 Xiuhcoatl assault rifle`);
					break;
				case "Peruvian":
					w.push(`and, in keeping with ${his} heritage, a SIMA FAD bullpup assault rifle`);
					break;
				case "Polish":
					w.push(`and, in keeping with ${his} heritage, a FB MSBS assault rifle`);
					break;
				case "Romanian":
					w.push(`and, in keeping with ${his} heritage, a ROMARM PA md.86 assault rifle`);
					break;
				case "Russian":
					w.push(`and, in keeping with ${his} heritage, an Izhmash AN-94 Abakan assault rifle`);
					break;
				case "Serbian":
					w.push(`and, in keeping with ${his} heritage, a Zastava M21A assault rifle`);
					break;
				case "South African":
					w.push(`and, in keeping with ${his} heritage, a Vektor R4 assault rifle`);
					break;
				case "Spanish":
					w.push(`and, in keeping with ${his} heritage, a CETME Model C battle rifle`);
					break;
				case "Swedish":
					w.push(`and, in keeping with ${his} heritage, a Bofors Ak-5C assault rifle`);
					break;
				case "Swiss":
					w.push(`and, in keeping with ${his} heritage, a SIG SG 550 assault rifle`);
					break;
				case "Thai":
					w.push(`and, in keeping with ${his} heritage, a MND Type 11 assault rifle`);
					break;
				case "Taiwanese":
					w.push(`and, in keeping with ${his} heritage, a 205th Arsenal T91 assault rifle`);
					break;
				case "Turkish":
					w.push(`and, in keeping with ${his} heritage, a MKEK MPT-76 assault rifle`);
					break;
				case "Ukrainian":
					w.push(`and, in keeping with ${his} heritage, a Fort-227 assault rifle`);
					break;
				case "Vietnamese":
					w.push(`and, in keeping with ${his} heritage, a Z111 AKM assault rifle`);
					break;
				case "Zimbabwean":
					if (slave.race === "white") {
						w.push(`and, in keeping with ${his} heritage, a FN FAL battle rifle`);
					} else {
						w.push(`and a FN F2000 bullpup assault rifle`);
					}
					break;
				default:
					w.push(`and a FN F2000 bullpup assault rifle`);
			}
			w.push(`slung across ${his} chest.`);
		} else if ((slave.muscles + slave.height - 100) / 25 > 3) {
			w.push(`${He} has a`);
			switch (arcInfo.revivalSociety()) {
				case RevivalSociety.ROMAN: w.push('curved ceramic falcata'); break;
				case RevivalSociety.NEO_IMPERIAL: w.push('straight ceramic estoc'); break;
				case RevivalSociety.EGYPTIAN: w.push('sickle-shaped'); break;
				case RevivalSociety.EDO: w.push('curved ceramic wakizashi'); break;
				case RevivalSociety.ARABIAN: w.push('curved ceramic yatagan'); break;
				case RevivalSociety.CHINESE: w.push('curved ceramic dao'); break;
				case RevivalSociety.AZTEC: w.push('straight ceramic macuahuitl'); break;
				case RevivalSociety.ANTEBELLUM: w.push('curved ceramic saber'); break;
				case null: w.push('straight ceramic'); break;
			}
			w.push(`sword strapped to ${his} back`);
			switch (slave.nationality) {
				case "American":
					w.push(`and, in keeping with ${his} heritage, a KRISS Vector SMG`);
					break;
				case "Argentinian":
					w.push(`and, in keeping with ${his} heritage, a Halcon ML-63 SMG`);
					break;
				case "Armenian":
				case "Belarusian":
				case "Kazakh":
				case "Uzbek":
					w.push(`and, in keeping with ${his} heritage, a Kalashnikov AKS-74U PDW`);
					break;
				case "Austrian":
					w.push(`and, in keeping with ${his} heritage, a Steyr AUG A3 Para XS bullpup SMG`);
					break;
				case "Belgian":
					w.push(`and, in keeping with ${his} heritage, a FN P90 bullpup PDW`);
					break;
				case "Brazilian":
					w.push(`and, in keeping with ${his} heritage, a Taurus MT40 G2 SMG`);
					break;
				case "British":
				case "Scottish":
					w.push(`and, in keeping with ${his} heritage, an L91A1 SMG`);
					break;
				case "Chilean":
					w.push(`and, in keeping with ${his} heritage, a FAMAE SAF-200 SMG`);
					break;
				case "Chinese":
					w.push(`and, in keeping with ${his} heritage, a Norinco QCQ-05 SMG`);
					break;
				case "Croatian":
					w.push(`and, in keeping with ${his} heritage, a PM Agram 2000 SMG`);
					break;
				case "Filipina":
					w.push(`and, in keeping with ${his} heritage, a Ferfrans SCW7 PDW`);
					break;
				case "German":
					w.push(`and, in keeping with ${his} heritage, an H&K MP7A1 PDW`);
					break;
				case "Indian":
					w.push(`and, in keeping with ${his} heritage, an OFB MSMC SMG`);
					break;
				case "Indonesian":
					w.push(`and, in keeping with ${his} heritage, a Pindad PM1A1 SMG`);
					break;
				case "Iranian":
					w.push(`and, in keeping with ${his} heritage, a DIO MPT-9 Tondar SMG`);
					break;
				case "Israeli":
					w.push(`and, in keeping with ${his} heritage, an IWI Uzi SMG`);
					break;
				case "Italian":
					w.push(`and, in keeping with ${his} heritage, a Beretta MX4 SMG`);
					break;
				case "Korean":
					w.push(`and, in keeping with ${his} heritage, a Daewoo K1A PDW`);
					break;
				case "Mexican":
					w.push(`and, in keeping with ${his} heritage, a Mendoza Cobra SMG`);
					break;
				case "Polish":
					w.push(`and, in keeping with ${his} heritage, a FB PM-06 Glauberyt SMG`);
					break;
				case "Romanian":
					w.push(`and, in keeping with ${his} heritage, a UM Cugir LP7 SMG`);
					break;
				case "Russian":
					w.push(`and, in keeping with ${his} heritage, a KBP PP-90M1 SMG`);
					break;
				case "Serbian":
					w.push(`and, in keeping with ${his} heritage, a Zastava Master FLG SMG`);
					break;
				case "South African":
					w.push(`and, in keeping with ${his} heritage, a Milkor BXP SMG`);
					break;
				case "Spanish":
					w.push(`and, in keeping with ${his} heritage, a Star Z84 SMG`);
					break;
				case "Swedish":
					w.push(`and, in keeping with ${his} heritage, a Carl Gustav m/45 SMG`);
					break;
				case "Swiss":
					w.push(`and, in keeping with ${his} heritage, a SIG MPX SMG`);
					break;
				case "Turkish":
					w.push(`and, in keeping with ${his} heritage, a Sarsilmaz SAR 109 SMG`);
					break;
				case "Ukrainian":
					w.push(`and, in keeping with ${his} heritage, a Fort-224 bullpup SMG`);
					break;
				case "Vietnamese":
					w.push(`and, in keeping with ${his} heritage, a Z111 K-50M LMG`);
					break;
				case "Zimbabwean":
					if (slave.race === "white") {
						w.push(`and, in keeping with ${his} heritage, a Sterling Mk.IV SMG`);
					} else {
						w.push(`and a H&K MP5 SMG`);
					}
					break;
				default:
					w.push(`and a H&K MP5 SMG`);
			}
			w.push(`slung across ${his} chest.`);
		} else {
			w.push(`${He} has a short ceramic`);
			switch (arcInfo.revivalSociety()) {
				case RevivalSociety.ROMAN: w.push('pugio'); break;
				case RevivalSociety.NEO_IMPERIAL: w.push('baselard'); break;
				case RevivalSociety.EGYPTIAN: w.push('acinaces'); break;
				case RevivalSociety.EDO: w.push('tanto'); break;
				case RevivalSociety.ARABIAN: w.push('jambiya'); break;
				case RevivalSociety.CHINESE: w.push('nandao'); break;
				case RevivalSociety.AZTEC: w.push('macuahuitl'); break;
				case RevivalSociety.ANTEBELLUM: w.push('Bowie knife'); break;
				case null: w.push(''); break;
			}
			w.push(`sword strapped to ${his} back`);
			switch (slave.nationality) {
				case "American":
					w.push(`and, in keeping with ${his} heritage, a MAC-10 machine pistol`);
					break;
				case "Armenian":
				case "Belarusian":
				case "Cuban":
				case "Kazakh":
				case "Ukrainian":
				case "Uzbek":
					w.push(`and, in keeping with ${his} heritage, a Molot Ordnance Stechkin APS machine pistol`);
					break;
				case "Austrian":
					w.push(`and, in keeping with ${his} heritage, a Steyr TMP machine pistol`);
					break;
				case "Belgian":
					w.push(`and, in keeping with ${his} heritage, a VBR-Belgium PDW machine pistol`);
					break;
				case "Chinese":
					w.push(`and, in keeping with ${his} heritage, a Chongqing Changfeng CF-05 machine pistol`);
					break;
				case "Czech":
				case "Slovak":
					w.push(`and, in keeping with ${his} heritage, a CZUB CZ 75 Automatic machine pistol`);
					break;
				case "German":
					w.push(`and, in keeping with ${his} heritage, a H&K VP70 machine pistol`);
					break;
				case "Hungarian":
					w.push(`and, in keeping with ${his} heritage, a FEG KGP-9 machine pistol`);
					break;
				case "Israeli":
					w.push(`and, in keeping with ${his} heritage, an IWI Uzi Pro machine pistol`);
					break;
				case "Italian":
					w.push(`and, in keeping with ${his} heritage, a Beretta 93R machine pistol`);
					break;
				case "Japanese":
					w.push(`and, in keeping with ${his} heritage, a Minebea PM-9 machine pistol`);
					break;
				case "Mexican":
					w.push(`and, in keeping with ${his} heritage, a Trejo Modelo 2 Especial machine pistol`);
					break;
				case "Peruvian":
					w.push(`and, in keeping with ${his} heritage, a SIMA MGP-87 machine pistol`);
					break;
				case "Polish":
					w.push(`and, in keeping with ${his} heritage, a WITU PMM machine pistol`);
					break;
				case "Romanian":
					w.push(`and, in keeping with ${his} heritage, a UMS Pistolul md.98 Dracula machine pistol`);
					break;
				case "Russian":
					w.push(`and, in keeping with ${his} heritage, a KBP OTs-33 Pernach machine pistol`);
					break;
				case "Swedish":
					w.push(`and, in keeping with ${his} heritage, a Saab Bofors Dynamics CBJ-MS machine pistol`);
					break;
				case "Swiss":
					w.push(`and, in keeping with ${his} heritage, a B+T MP9 machine pistol`);
					break;
				case "Turkish":
					w.push(`and, in keeping with ${his} heritage, a TiSAS Zigana F machine pistol`);
					break;
				default:
					w.push(`and a Glock 18 machine pistol`);
			}
			w.push(`at ${his} side.`);
		}

		return w.join(" ");
	}
};


code:
new App.DomPassage("Child Interact",
	() => {
		V.nextButton = "Confirm changes";
		V.nextLink = "Nursery";

		return App.UI.ChildInteract.mainPage(V.activeChild);
	}, ["jump-from-safe"]
);