code:
/*
This is a womb processor/simulator script. It takes care of calculation of belly sizes based on individual fetus sizes, with full support of broodmothers implant random turning on and off possibility. Also this can be expanded to store more parents data in each individual fetus in future.
Design limitations:
- Mother can't gestate children with different speeds at same time. All speed changes apply to all fetuses.
- Sizes of individual fetuses updated only on call of WombGetVolume - not every time as called WombProgress. This is for better overall code speed.
- For broodmothers we need actual "new ova release" code now. But it's possible to control how many children will be added each time, and so - how much children is ready to birth each time.

Usage from sugarcube code (samples):

WombInit($slave) - before first pregnancy, at slave creation, of as backward compatibility update.

WombImpregnate($slave, $fetus_count, $fatherID, $initial_age) - should be added after normal impregnation code, with already calculated fetus count. ID of father - can be used in future for processing children from different fathers in one pregnancy. Initial age normally 1 (as .preg normally set to 1), but can be raised if needed. Also should be called at time as broodmother implant add another fetus(es), or if new fetuses added from other sources in future (transplanting maybe?)

WombProgress($slave, $time_to_add_to_fetuses, $real_time_to_add_to_fetuses) - after code that update $slave.preg, time to add should be the same.

$isReady = WombBirthReady($slave, $birth_ready_age) - how many children ready to be birthed if their time to be ready is $birth_ready_age (40 is for normal length pregnancy). Return int - count of ready to birth children, or 0 if no ready exists.

$children = WombBirth($slave, $birth_ready_age) - for actual birth. Return array with fetuses objects that birthed (can be used in future) and remove them from womb array of $slave. Should be called at actual birth code in sugarcube. fetuses that not ready remained in womb (array).

WombFlush($slave) - clean womb (array). Can be used at broodmother birthstorm or abortion situations in game. But birthstorm logically should use WombBirth($slave, 35) or so before - some children in this event is live capable, others is not.

$slave.bellyPreg = WombGetVolume($slave) - return double, with current womb volume in CC - for updating $slave.bellyPreg, or if need to update individual fetuses sizes.

*/

// Init womb system.
globalThis.WombInit = function(actor) {
	let i;

	if (!Array.isArray(actor.womb)) {
		// alert("creating new womb"); // debugging
		actor.womb = [];
	}

	// console.log("broodmother:" + typeof actor.broodmother);

	if (typeof actor.broodmother !== "number") {
		actor.broodmother = 0;
		actor.broodmotherFetuses = 0;
	}

	if (typeof actor.readyOva !== "number") {
		actor.readyOva = 0;
	}

	if (actor.pregData === undefined) {
		actor.pregData = clone(setup.pregData.human);
		// Setup should be through deep copy, so in future, if we like, these values can be changed individually. Gameplay expansion possibilities. But for dev time to simplify debugging:
		// actor.pregData = setup.pregData.human;  // any changes in setup pregData template will be applied immediately to all. But can't be made separate changes.
	}

	if (typeof actor.eggType !== 'string') {
		actor.eggType = "human";
	}

	// backward compatibility setup. Fully accurate for normal pregnancy only.
	if (actor.womb.length > 0 && actor.womb[0].genetics === undefined && actor.eggType === "human") {
		i = 0;
		actor.womb.forEach(function(ft) {
			if (typeof ft.reserve !== 'string') {
				ft.reserve = "";
			}
			if (typeof ft.motherID !== 'number') { // setting missing biological mother ID for fetus.
				ft.motherID = slave.ID;
			}
			if (ft.ID === undefined) {
				ft.ID = generateNewID();
			}
			if (typeof ft.realAge !== 'number') { // setting missing chronological age
				ft.realAge = ft.age;
			}

			ft.genetics = generateGenetics(actor, actor.pregSource, i);
			i++;
		});
	} else if (actor.womb.length === 0 && actor.pregType > 0 && actor.broodmother === 0) {
		WombImpregnate(actor, actor.pregType, actor.pregSource, actor.preg);
	} else if (actor.womb.length === 0 && actor.pregType > 0 && actor.broodmother > 0 && actor.broodmotherOnHold < 1) {
		// sorry but for already present broodmothers it's impossible to calculate fully, approximation used.
		let pw = actor.preg,
			bCount, bLeft;
		if (pw > actor.pregData.normalBirth) { pw = actor.pregData.normalBirth; } // to avoid disaster.
		bCount = Math.floor(actor.pregType / pw);
		bLeft = actor.pregType - (bCount * pw);
		if (pw > actor.pregType) {
			pw = actor.pregType; // low children count broodmothers not supported here. It's emergency/backward compatibility code, and they not in game anyway. So minimum is 1 fetus in week.
			actor.preg = pw; // fixing initial pregnancy week.
		}
		for (i = 0; i < pw; i++) {
			WombImpregnate(actor, bCount, actor.pregSource, i); // setting fetuses for every week, up to 40 week at max.
		}

		if (bLeft > 0) {
			WombImpregnate(actor, bLeft, actor.pregSource, i + 1); // setting up leftover of fetuses.
		}
	}
};

globalThis.WombImpregnate = function(actor, fCount, fatherID, age, surrogate) {
	let i;
	let tf;
	for (i = 0; i < fCount; i++) {
		tf = {}; // new Object
		tf.age = age; // initial age
		tf.realAge = 1; // initial real age (first week in mother)
		tf.fatherID = fatherID; // We can store who is father too.
		tf.volume = 1; // Initial, to create property. Updated with actual data after WombGetVolume call.
		tf.reserve = ""; // Initial, to create property. Used later to mark if this child is to be kept.
		tf.identical = 0; // Initial, to create property. Updated with actual data during fetalSplit call.
		tf.splitted = 0; // marker for already splitted fetus.
		if (surrogate) {
			tf.motherID = surrogate.ID; // Initial biological mother ID setup.
			if (actor.eggType === "human") {
				tf.genetics = generateGenetics(surrogate, fatherID, i + 1); // Stored genetic information.
			}
		} else {
			tf.motherID = actor.ID; // Initial biological mother ID setup.
			if (actor.eggType === "human") {
				tf.genetics = generateGenetics(actor, fatherID, i + 1); // Stored genetic information.
			}
		}
		tf.ID = generateNewID();

		try {
			if (actor.womb.length === 0) {
				actor.pregWeek = age;
				actor.preg = age;
			}
			actor.womb.push(tf);
		} catch (err) {
			WombInit(actor);
			actor.womb.push(tf);
			alert("WombImpregnate warning - " + actor.slaveName + " " + err);
		}
	}
	MissingParentIDCorrection(actor);
	WombUpdatePregVars(actor);
};

globalThis.WombSurrogate = function(actor, fCount, mother, fatherID, age) {
	WombImpregnate(actor, fCount, fatherID, age, mother);
};

globalThis.WombImpregnateClone = function(actor, fCount, mother, motherOriginal, age) {
	let i;
	let tf;
	for (i = 0; i < fCount; i++) {
		tf = {}; // new Object
		tf.age = age; // initial age
		tf.realAge = 1; // initial real age (first week in mother)
		tf.fatherID = mother.ID; // We can store who is father too.
		tf.volume = 1; // Initial, to create property. Updated with actual data after WombGetVolume call.
		tf.reserve = ""; // Initial, to create property. Used later to mark if this child is to be kept.
		tf.identical = 0; // Initial, to create property. Updated with actual data during fetalSplit call.
		tf.splitted = 0; // marker for already splitted fetus.
		tf.motherID = mother.ID; // Initial biological mother ID setup.
		tf.genetics = generateGenetics(mother, mother.ID, i + 1); // Stored genetic information.
		tf.ID = generateNewID();

		// set up common relatives for the slave and her clone
		setMissingParents(mother);

		// gene corrections
		tf.fatherID = -7;
		tf.genetics.gender = mother.genes;
		tf.genetics.mother = mother.mother;
		tf.genetics.father = mother.father;
		if (mother.ID === -1) {
			tf.genetics.motherName = mother.slaveName;
			tf.genetics.fatherName = mother.slaveName;
			tf.genetics.clone = PlayerName(mother);
			tf.genetics.cloneID = -1;
		} else {
			tf.genetics.motherName = mother.slaveName;
			tf.genetics.fatherName = mother.slaveName;
			tf.genetics.clone = SlaveFullName(mother);
			tf.genetics.cloneID = mother.ID;
		}
		tf.genetics.intelligence = motherOriginal.intelligence;
		tf.genetics.face = motherOriginal.face;
		tf.genetics.faceShape = motherOriginal.faceShape;
		tf.genetics.geneticQuirks = clone(mother.geneticQuirks);
		tf.genetics.skin = motherOriginal.skin;

		try {
			if (actor.womb.length === 0) {
				actor.pregWeek = age;
				actor.preg = age;
				actor.pregSource = -7;
			}
			actor.womb.push(tf);
		} catch (err) {
			WombInit(actor);
			actor.womb.push(tf);
			alert("WombImpregnate warning - " + actor.slaveName + " " + err);
		}
	}
	WombUpdatePregVars(actor);
};

// Should be used to set biological age for fetus (ageToAdd), AND chronological (realAgeToAdd). Speed up or slow down gestation drugs should affect ONLY biological.
globalThis.WombProgress = function(actor, ageToAdd, realAgeToAdd = ageToAdd) {
	ageToAdd = Math.ceil(ageToAdd * 10) / 10;
	realAgeToAdd = Math.ceil(realAgeToAdd * 10) / 10;
	try {
		actor.womb.forEach(ft => {
			ft.age += ageToAdd;
			ft.realAge += realAgeToAdd;
		});
	} catch (err) {
		WombInit(actor);
		alert("WombProgress warning - " + actor.slaveName + " " + err);
	}
};

globalThis.WombBirth = function(actor, readyAge) {
	try {
		WombSort(actor); // For normal processing fetuses that more old should be first. Now - they are.
	} catch (err) {
		WombInit(actor);
		alert("WombBirth warning - " + actor.slaveName + " " + err);
	}

	let birthed = [];
	let ready = WombBirthReady(actor, readyAge);
	let i;

	for (i = 0; i < ready; i++) { // here can't be used "for .. in .." syntax.
		birthed.push(actor.womb.shift());
	}

	return birthed;
};

globalThis.WombFlush = function(actor) {
	actor.womb = [];
	WombUpdatePregVars(actor);
};

globalThis.WombBirthReady = function(actor, readyAge) {
	let readyCnt = 0;
	try {
		readyCnt += actor.womb.filter(ft => ft.age >= readyAge).length;
	} catch (err) {
		WombInit(actor);
		alert("WombBirthReady warning - " + actor.slaveName + " " + err);
		return 0;
	}

	return readyCnt;
};

globalThis.WombGetVolume = function(actor) { // most legacy code from pregJS.tw with minor adaptation.
	if (actor.pregData.sizeType === 0) {
		return getVolByLen(actor);
	} else if (actor.pregData.sizeType === 1) {
		return getVolByWeight(actor);
	} else if (actor.pregData.sizeType === 2) {
		return getVolByRaw(actor);
	} else {
		return 0;
	}

	function getCurData(actor, age) {
		let i = 0;
		let min, max, ageMin, ageMax, rateMin, rateMax, one, rateOne, rate, cage, csize;
		let data = {};

		while (actor.pregData.fetusWeek[i + 1] < age && i < actor.pregData.fetusWeek.length - 1) {
			i++;
		}

		min = actor.pregData.fetusSize[i];
		max = actor.pregData.fetusSize[i + 1];
		ageMin = actor.pregData.fetusWeek[i];
		ageMax = actor.pregData.fetusWeek[i + 1];
		rateMin = actor.pregData.fetusRate[i];
		rateMax = actor.pregData.fetusRate[i + 1];

		cage = age - ageMin;

		one = (max - min) / (ageMax - ageMin);
		rateOne = (rateMax - rateMin) / (ageMax - ageMin);

		rate = rateMin + (rateOne * cage);

		csize = (min + (one * cage));
		// console.log("min:"+min+"  max:"+max+"  ageMin:"+ageMin+"  ageMax:"+ageMax+"  one:"+one+"  rateOne:"+rateOne+"  cage:"+cage+"  rate:"+rate+"  csize:"+csize+"  final size:"+csize*rate);

		data.size = csize;
		data.rate = rate;

		return data; // csize * rate;
		// maybe not very effective code, but simple and easy to debug. May be optimized more in future.
	}

	function getVolByLen(actor) {
		let phi = 1.618;
		let targetData, targetLen;
		let wombSize = 0;

		try {
			actor.womb.forEach(ft => {
				/* legacy block for debug only
				let gestastionWeek = ft.age;
				let oldLen;
				let oldVol;
				if (gestastionWeek <= 32) {
					oldLen = (0.00006396 * Math.pow(gestastionWeek, 4)) -
						(0.005501 * Math.pow(gestastionWeek, 3)) +
						(0.161 * Math.pow(gestastionWeek, 2)) -
						(0.76 * gestastionWeek) +
						0.208;
				} else if (gestastionWeek <= 106) {
					oldLen = (-0.0000004675 * Math.pow(gestastionWeek, 4)) +
						(0.0001905 * Math.pow(gestastionWeek, 3)) -
						(0.029 * Math.pow(gestastionWeek, 2)) +
						(2.132 * gestastionWeek) -
						16.575;
				} else {
					oldLen = (-0.00003266 * Math.pow(gestastionWeek,2)) +
						(0.076 * gestastionWeek) +
						43.843;
				}
				*/

				targetData = getCurData(actor, ft.age);
				targetLen = targetData.size * targetData.rate;

				ft.volume = ((4 / 3) * (Math.PI) * (phi / 2) * (Math.pow((targetLen / 2), 3)));
				wombSize += ft.volume;

				// oldVol = ((4 / 3) * (Math.PI) * (phi / 2) * (Math.pow((oldLen / 2), 3))); // for debug

				// console.log("fetus.age:" + ft.age + "  oldLen:"+oldLen+"  targetLen:"+targetLen+"  ft.volume:"+ft.volume+ "  old volume:"+oldVol );
				/*
					I found, that previous targetLen calculation not exactly accurate if compared to the actual medical data chart for fetal length. It's been rough approximation based only on pregnancy week (giving smaller fetus size then it should in most cases). So I need all this debug code to compare data and verify calculations. After final tweaking I will remove or comment out legacy code. Please not touch this before it.
					Pregmodfan.
				*/
			});
		} catch (err) {
			WombInit(actor);
			alert("WombGetVolume warning - " + actor.slaveName + " " + err);
		}
		if (wombSize < 0) { // catch for strange cases, to avoid messing with outside code.
			wombSize = 0;
		}

		return wombSize;
	}

	function getVolByWeight(actor) {
		let targetData;
		let wombSize = 0;

		actor.womb.forEach(ft => {
			targetData = getCurData(actor, ft.age);

			wombSize += targetData.size * targetData.rate;
		});

		if (wombSize < 0) { // catch for strange cases, to avoid messing with outside code.
			wombSize = 0;
		}

		return wombSize;
	}

	function getVolByRaw(actor) {
		let targetData;
		let wombSize = 0;

		actor.womb.forEach(ft => {
			targetData = getCurData(actor, ft.age);

			wombSize += targetData.size;
		});

		if (wombSize < 0) { // catch for strange cases, to avoid messing with outside code.
			wombSize = 0;
		}

		return wombSize;
	}
};

globalThis.FetusGetPrediction = function(actor, age) {
	let vol = 0.1;
	if (actor.pregData.sizeType === 0) {
		vol = getVolByLen(actor, age);
	} else if (actor.pregData.sizeType === 1) {
		vol = getVolByWeight(actor, age);
	} else if (actor.pregData.sizeType === 2) {
		vol = getVolByRaw(actor, age);
	}

	if (vol === 0) {
		vol = 0.1;
	}

	return vol;

	function getCurData(actor, age) {
		let i = 0;
		let min, max, ageMin, ageMax, rateMin, rateMax, one, rateOne, rate, cage, csize;
		let data = {};

		while (actor.pregData.fetusWeek[i + 1] < age && i < actor.pregData.fetusWeek.length - 1) {
			i++;
		}

		min = actor.pregData.fetusSize[i];
		max = actor.pregData.fetusSize[i + 1];
		ageMin = actor.pregData.fetusWeek[i];
		ageMax = actor.pregData.fetusWeek[i + 1];
		rateMin = actor.pregData.fetusRate[i];
		rateMax = actor.pregData.fetusRate[i + 1];

		cage = age - ageMin;

		one = (max - min) / (ageMax - ageMin);
		rateOne = (rateMax - rateMin) / (ageMax - ageMin);

		rate = rateMin + (rateOne * cage);

		csize = (min + (one * cage));
		// console.log("min:"+min+"  max:"+max+"  ageMin:"+ageMin+"  ageMax:"+ageMax+"  one:"+one+"  rateOne:"+rateOne+"  cage:"+cage+"  rate:"+rate+"  csize:"+csize+"  final size:"+csize*rate);

		data.size = csize;
		data.rate = rate;

		return data; // csize * rate;
		// maybe not very effective code, but simple and easy to debug. May be optimized more in future.
	}

	function getVolByLen(actor, age) {
		let phi = 1.618;
		let targetData, targetLen;
		let volume = 0;

		targetData = getCurData(actor, age);
		targetLen = targetData.size * targetData.rate;

		volume = ((4 / 3) * (Math.PI) * (phi / 2) * (Math.pow((targetLen / 2), 3)));

		if (volume < 0) { // catch for strange cases, to avoid messing with outside code.
			volume = 0;
		}

		return volume;
	}

	function getVolByWeight(actor, age) {
		let targetData;
		let volume = 0;

		targetData = getCurData(actor, age);

		volume += targetData.size * targetData.rate;

		if (volume < 0) { // catch for strange cases, to avoid messing with outside code.
			volume = 0;
		}

		return volume;
	}

	function getVolByRaw(actor) {
		let targetData;
		let volume = 0;

		targetData = getCurData(actor, age);

		volume += targetData.size;

		if (volume < 0) { // catch for strange cases, to avoid messing with outside code.
			volume = 0;
		}

		return volume;
	}
};

globalThis.WombUpdatePregVars = function(actor) {
	WombSort(actor);
	if (actor.womb.length > 0) {
		if (actor.preg > 0 && actor.womb[0].age > 0) {
			actor.preg = actor.womb[0].age;
		}
		actor.pregType = actor.womb.length;
		actor.bellyPreg = WombGetVolume(actor);

		if (actor.womb[0].age >= 10 && actor.pregKnown === 0) {
			actor.pregKnown = 1;
		}
	} else {
		actor.pregType = 0;
		WombNormalizePreg(actor);
	}
};

globalThis.WombMinPreg = function(actor) {
	WombSort(actor);
	if (actor.womb.length > 0) {
		return actor.womb[actor.womb.length - 1].age;
	} else {
		return 0;
	}
};

globalThis.WombMaxPreg = function(actor) {
	WombSort(actor);
	if (actor.womb.length > 0) {
		return actor.womb[0].age;
	} else {
		return 0;
	}
};

globalThis.WombNormalizePreg = function(actor) {
	// console.log("New actor: " + actor.slaveName + " ===============" + actor.name);
	WombInit(actor);

	// this is broodmother on hold.
	if (actor.womb.length === 0 && actor.broodmother >= 1) {
		actor.pregType = 0;
		actor.pregKnown = 0;

		// to avoid legacy code conflicts - broodmother on hold
		// can't be impregnated, but she is not on normal contraceptives.
		// So we set this for special case.
		if (actor.preg >= 0) {
			actor.preg = 0.1;
		}

		if (actor.pregSource !== 0) {
			actor.pregSource = 0;
		}

		if (actor.pregWeek > 0) {
			actor.pregWeek = 0;
		}

		actor.broodmotherCountDown = 0;
	}

	if (actor.womb.length > 0) {
		let max = WombMaxPreg(actor);
		// console.log("max: " + max);
		// console.log(".preg: "+ actor.preg);
		if (actor.pregWeek < 1) {
			actor.pregWeek = 1;
		}

		if (max < actor.preg) {
			WombProgress(actor, actor.preg - max, actor.preg - max);
			// console.log("progressin womb");
		} else if (max > actor.preg) {
			actor.preg = max;
			// console.log("advancing .preg");
		}

		if (actor.womb[0].age >= 10 && actor.pregKnown === 0) {
			actor.pregKnown = 1;
		}

		actor.pregType = actor.womb.length;
		actor.pregSource = actor.womb[0].fatherID;
	} else if (actor.womb.length === 0 && actor.broodmother < 1) {
		// not broodmother
		// console.log("preg fixing");
		actor.pregType = 0;
		actor.pregKnown = 0;

		if (actor.preg > 0) {
			actor.preg = 0;
		}

		if (actor.pregSource !== 0) {
			actor.pregSource = 0;
		}

		// We can't properly set postpartum here,
		// but can normalize obvious error with forgotten property.
		if (actor.pregWeek > 0) {
			actor.pregWeek = 0;
		}
	}
	actor.bellyPreg = WombGetVolume(actor);
};

globalThis.WombZeroID = function(actor, id) {
	WombInit(actor);
	actor.womb
		.filter(ft => ft.fatherID === id)
		.forEach(ft => ft.fatherID = 0);
	WombNormalizePreg(actor);
};

globalThis.WombChangeID = function(actor, fromID, toID) {
	WombInit(actor);
	actor.womb
		.filter(ft => ft.fatherID === fromID)
		.forEach(ft => ft.fatherID = toID);
	WombNormalizePreg(actor);
};

globalThis.WombChangeGeneID = function(actor, fromID, toID) {
	WombInit(actor);
	actor.womb
		.filter(ft => ft.genetics.father === fromID)
		.forEach(ft => ft.genetics.father = toID);
	actor.womb
		.filter(ft => ft.genetics.mother === fromID)
		.forEach(ft => ft.genetics.mother = toID);
	WombNormalizePreg(actor);
};

/* Sorts the womb object by age with oldest and thus soonest to be born, first. This will be needed in the future once individual fertilization is a possibility.*/
globalThis.WombSort = function(actor) {
	actor.womb.sort((a, b) => {
		return b.age - a.age;
	});
};

// now function work with chance. Literary we give it "one from X" as chance.
globalThis.fetalSplit = function(actor, chance) {
	let nft;

	actor.womb.forEach(function(s) {
		if ((jsRandom(1, chance) >= chance) && s.splitted !== 1) {
			nft = {};
			nft.age = s.age;
			nft.realAge = s.realAge;
			nft.fatherID = s.fatherID;
			nft.motherID = s.motherID;
			nft.volume = s.volume;
			nft.reserve = ""; // splitted fetus is new separate, reserve - it's not genetic to split.
			nft.genetics = clone(s.genetics);
			s.splitted = 1; // this is marker that this is already splitted fetus (to not split second time in loop), only source fetus needed it.
			nft.identical = 1; // this is marker that this fetus has at least one twin.
			s.identical = 1; // this is marker that this fetus has at least one twin.

			if (s.twinID === "" || s.twinID === undefined) {
				s.twinID = generateNewID();
			}

			nft.twinID = s.twinID;

			actor.womb.push(nft);
		}
	});
	WombNormalizePreg(actor);
};

// safe alternative to .womb.length.
globalThis.WombFetusCount = function(actor) {
	WombInit(actor);
	return actor.womb.length;
};

// give reference to fetus object, but not remove fetus, use for manipulation in the womb.
globalThis.WombGetFetus = function(actor, fetusNum) {
	WombInit(actor);
	if (actor.womb.length >= fetusNum) {
		return actor.womb[fetusNum];
	} else {
		return null;
	}
};

// give reference to fetus object, and remove it form the womb.
globalThis.WombRemoveFetus = function(actor, fetusNum) {
	WombInit(actor);
	if (actor.womb.length >= fetusNum) {
		let ft = actor.womb[fetusNum];
		actor.womb.splice(fetusNum, 1);
		WombUpdatePregVars(actor);
		return ft;
	} else {
		return null;
	}
};

/* to add fetus object in the womb. Be warned - you can add one single fetus to many wombs, or even add it many times to one womb. It will not show error, but behavior becomes strange, as fetus object will be the same - it's reference, not full copies. If this is not desired - use clone() on fetus before adding.*/
globalThis.WombAddFetus = function(actor, fetus) {
	WombInit(actor);
	actor.womb.push(fetus);
	WombSort(actor);
};

// change property for all fetuses. Like fetus.age = X.
globalThis.WombChangeFetus = function(actor, propName, newValue) {
	WombInit(actor);
	actor.womb.forEach(ft => ft[propName] = newValue);
};

// change genetic property of all fetuses. Like fetus.genetic.intelligence = X
globalThis.WombChangeGene = function(actor, geneName, newValue) {
	WombInit(actor);
	actor.womb.forEach(ft => ft.genetics[geneName] = newValue);
};

// change genetic property of all fetuses based on race
globalThis.WombFatherRace = function(actor, raceName) {
	let skinColor = randomRaceSkin(raceName);
	let eyeColor = randomRaceEye(raceName);
	let hairColor = randomRaceHair(raceName);
	WombChangeGene(actor, "race", raceName);
	WombChangeGene(actor, "skin", skinColor);
	WombChangeGene(actor, "eyeColor", eyeColor);
	WombChangeGene(actor, "hColor", hairColor);
};

// replaces untraceable fatherIDs with missingParentID. Required for concurrent pregnancy to differentiate between siblings.
globalThis.MissingParentIDCorrection = function(actor) {
	WombInit(actor);
	actor.womb
		.filter(ft => (ft.genetics.father === 0 || (ft.genetics.father < -1 && ft.genetics.father >= -20 && ft.genetics.father !== -3)))
		.forEach(ft => ft.genetics.father = V.missingParentID);
	V.missingParentID--;
};

globalThis.WombCleanYYFetuses = function(actor) {
	let reserved = [];

	let i = actor.womb.length - 1;
	let ft;

	while (i >= 0) {
		ft = actor.womb[i];

		if (ft.genetics.gender === "YY") {
			reserved.push(ft);
			actor.womb.splice(i, 1);
		}

		i--;
	}
	WombUpdatePregVars(actor);

	return reserved;
};

globalThis.FetusGlobalReserveCount = function(reserveType) {
	let cnt = 0;

	if (typeof reserveType !== 'string') {
		return 0;
	}

	V.slaves.forEach(function(slave) {
		slave.womb.forEach(function(ft) {
			if (ft.reserve === reserveType) {
				cnt++;
			}
		});
	});

	V.PC.womb.forEach(function(ft) {
		if (ft.reserve === reserveType) {
			cnt++;
		}
	});

	return cnt;
};

globalThis.WombSetGenericReserve = function(actor, type, count) {
	// console.log ("actor: " + actor + "  type: " + type + "  typeof: " + typeof type + "  count: " + count);
	actor.womb.forEach(function(ft) {
		// console.log ("  type: " + ft.reserve + "  typeof: " + typeof ft.reserve);
		if ((ft.reserve === "" || ft.reserve === type) && count > 0) {
			// console.log ("!trigger");
			ft.reserve = type;
			count--;
		}
	});
};

globalThis.WombAddToGenericReserve = function(actor, type, count) {
	WombSetGenericReserve(actor, type, (WombReserveCount(actor, type) + count));
};

globalThis.WombChangeReserveType = function(actor, oldType, newType) {
	let count = 0;

	actor.womb.forEach(function(ft) {
		if (ft.reserve === oldType) {
			ft.reserve = newType;
			count++;
		}
	});

	return count;
};

globalThis.WombCleanGenericReserve = function(actor, type, count) {
	actor.womb.forEach(function(ft) {
		if (ft.reserve === type && count > 0) {
			ft.reserve = "";
			count--;
		}
	});
};

globalThis.WombReserveCount = function(actor, type) {
	let cnt = 0;

	actor.womb.forEach(function(ft) {
		if (ft.reserve === type) /* the lazy equality will catch "" case */ {
			cnt++;
		}
	});

	return cnt;
};

globalThis.WombGetReservedFetuses = function(actor, type) {
	let reserved = [];

	actor.womb.forEach(function(ft) {
		if (ft.reserve === type) {
			reserved.push(ft);
		}
	});

	return reserved;
};

globalThis.WombRemoveReservedFetuses = function(actor, type) {
	let reserved = [];

	let i = actor.womb.length - 1;
	let ft;

	while (i >= 0) {
		ft = actor.womb[i];

		if (ft.reserve === type) {
			reserved.push(ft);
			actor.womb.splice(i, 1);
		}

		i--;
	}

	return reserved;
};

globalThis.WombCleanAllReserve = function(actor) {
	actor.womb.forEach(function(ft) {
		ft.reserve = "";
	});
};

/*
Function return object with data about litters in actor womb. This data can be used for descriptions of pregnancy with complicated structure. What it contain:

data.litters.length = summary count of separate litters in the womb.
data.litters[x] = age (.realAge) of litter "x".
data.countLitter[x] = count of fetuses in "x" litter.

data.litterData[x] = contain array with actual fetuses that belong to a litter "x". Can be used to check anything related to fetus. (This is not copy, but reference to actual fetuses, so be careful with changes of this array).

Sample of usage in SugarScript:
---
<<set _wd = WombGetLittersData($activeSlave)>>
$He is _wd.litters[0] weeks pregnant with $his first set of _wd.countLitter[0] children<<if _wd.litters > 1>>, _wd.litters[1] weeks along with $his second set<</if>><<if _wd.litters > 2>>, _wd.litters[2] and _wd.litters[2] weeks along with $his third<</if>>.
In summary $he carry _wd.litters.length separate sets of children. $His most progressed fetus of second pregnancy is already reached _wd.litterData[1][0].age biological week of gestation.
---
*/
globalThis.WombGetLittersData = function(actor) {
	let data = {};
	let unicLiters = []; // array with realAges of separate litters.
	let countLitter = [];
	let litterData = [];
	let tmp;

	// in first place we need to know how many litters here (Assuming that unique litter is have similar .realAge). Also we will know their ages.
	actor.womb.forEach(function(ft) {
		if (!unicLiters.includes(Math.ceil(ft.realAge))) {
			unicLiters.push(Math.ceil(ft.realAge));
		}
	});

	// now we should find and store separate litters data (count of fetuses):
	unicLiters.forEach(function(litter) {
		tmp = actor.womb.filter(ft => Math.ceil(ft.realAge) === litter);
		countLitter.push(tmp.length);
		litterData.push(tmp);
	});

	data.litters = unicLiters;
	data.countLitter = countLitter;
	data.litterData = litterData;

	return data;
};

repo_name: KnightBoulegard/fc

license: mit


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

	let opinion = 0;

	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 (activeArcology.FSNull !== "unset") {
			opinion += activeArcology.FSNull;
		} else if (targetArcology.FSNull !== "unset") {
			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 obdedient = (arcology.government === "your trustees" || arcology.government === "your agent");

		for (let targetID = 0; targetID < V.arcologies.length; ++targetID) {
			const target = V.arcologies[targetID];
			if (arcology.direction !== target.direction) {
				if (!obdedient || target.direction !== 0) {
					const {shared, conflicting} = FutureSocieties.diplomaticFSes(arcID, targetID);
					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: string[], conflicting: string[][]}>} */
		this._relationships = new Map();
		for (let i = 0; i < V.arcologies.length; ++i) {
			if (i !== arcID) {
				this._relationships.set(i, FutureSocieties.diplomaticFSes(arcID, i));
			}
		}

		const arcology = V.arcologies[this._arcID];
		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 {string} fs
	 */
	output(fs) {
		/** @type {number[]} */
		let shared = [];
		/** @type {Map<string, number[]>} */
		let conflicting = new Map();
		const arcology = V.arcologies[this._arcID];

		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 === "FSSubjugationism") {
				t.push(`of ${arcology.FSSubjugationistRace} people`);
			} else if (fs === "FSSupremacy") {
				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(sharedNames.reduce((res, ch, i, arr) => res + (i === arr.length - 1 ? ' and ' : ', ') + ch) +
				` more advanced ${shared.length === 1 ? "society" : "societies"}.`);
		}

		// passive slowing influence
		arcology[fs] -= conflicting.size;
		if (V.showNeighborDetails && conflicting.size > 0) {
			let actualDisplayName = FutureSocieties.displayName(fs);
			if (fs === "FSSubjugationism") {
				actualDisplayName = `${arcology.FSSubjugationistRace} Subjugationism`;
			} else if (fs === "FSSupremacy") {
				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(conflictNames.reduce((res, ch, i, arr) => res + (i === arr.length - 1 ? ' and ' : ', ') + ch) +
					` more advanced ${FutureSocieties.displayAdj(conflictFS)} ${arcs.length === 1 ? "society" : "societies"}`);
			}
			t.push(conflictOutput.reduce((res, ch, i, arr) => res + (i === arr.length - 1 ? ' and ' : ', ') + ch) + '.');
		}

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


code:
:: Neighbor Description [nobr]

<div class="indent">
<<if $arcologies[$i].direction != 0>>
	''$arcologies[$i].name'' is located to the $arcologies[$i].direction of your arcology. It is governed by
	<<switch $arcologies[$i].government>>
	<<case "elected officials">>
		elected officials, periodically paralyzing its development.
	<<case "a committee">>
		a committee, hindering its development.
	<<case "an oligarchy">>
		a small group of leading citizens, making its development very unpredictable.
	<<case "your trustees">>
		a small group of leading citizens who are serving as @@.mediumseagreen;your trustees.@@
	<<case "an individual">>
		@@.cyan;an individual,@@ making its development vibrant but unpredictable.
	<<case "your agent">>
		@@.deeppink;your agent,@@ who is directing the arcology in your stead.
	<<case "a corporation">>
		a corporation, making its development steady and unspectacular.
	<<default>>
		direct democracy, making its development dangerously unstable.
	<</switch>>
<<else>>
	''$arcologies[$i].name'' is your arcology.
<</if>>
<<set _economicUncertainty = App.Utils.economicUncertainty($i)>>
<<if $arcologies[$i].direction == 0>>
	You control @@.lime;$arcologies[$i].ownership%@@ of the arcology, and the largest minority holder controls @@.orange;$arcologies[$i].minority%.@@
<<elseif ($arcologies[$i].government != "your trustees") && ($arcologies[$i].government != "your agent")>>
	Its leadership has control of approximately @@.orange;<<print Math.trunc($arcologies[$i].ownership*_economicUncertainty)>>%@@ of the arcology<<if $arcologies[$i].minority > $arcologies[$i].ownership-10>>, a dangerously narrow margin over competition with a @@.tan;<<print Math.trunc($arcologies[$i].minority*_economicUncertainty)>>%@@ share<</if>>.
<</if>>
<<if $arcologies[$i].PCminority > 0>>You own @@.lime;$arcologies[$i].PCminority%@@ of this arcology<<if (($arcologies[$i].government == "your trustees") || ($arcologies[$i].government == "your agent")) && $arcologies[$i].minority > $arcologies[$i].PCminority-10>>, a dangerously narrow margin over competition with a @@.red;<<print Math.trunc($arcologies[$i].minority*_economicUncertainty)>>%@@ share<</if>>.<</if>>
The arcology has an estimated GSP of @@.yellowgreen;<<print cashFormat(Math.trunc(0.1*$arcologies[$i].prosperity*_economicUncertainty))>>m,@@
<<if Math.abs($arcologies[$i].prosperity - $averageProsperity) < 5>>
	average among
<<elseif $arcologies[$i].prosperity > $averageProsperity>>
	ahead of
<<else>>
	behind
<</if>>
its neighbors.

<<set $desc = 0>>
<<set _neighborDescription = []>>
<<if $arcologies[$i].FSSubjugationist != "unset">>
	<<if $arcologies[$i].FSSubjugationist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSSubjugationist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Racial Subjugationism,@@ and is ">>
	<<if $arcologies[$i].FSSubjugationist > 95>>
		<<set $desc = $desc + "the home of an advanced project to create a subservient race of ">>
		<<set $desc = $desc + $arcologies[$i].FSSubjugationistRace>>
		<<set $desc = $desc + " slaves.">>
	<<elseif $arcologies[$i].FSSubjugationist > 40>>
		<<set $desc = $desc + "working to refine ">>
		<<set $desc = $desc + $arcologies[$i].FSSubjugationistRace>>
		<<set $desc = $desc + " slavery.">>
	<<else>>
		<<set $desc = $desc + "an excellent dumping ground for low quality ">>
		<<set $desc = $desc + $arcologies[$i].FSSubjugationistRace>>
		<<set $desc = $desc + " slaves.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSSupremacist != "unset">>
	<<if $arcologies[$i].FSSubjugationist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSSubjugationist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Racial Supremacism,@@ and is ">>
	<<if $arcologies[$i].FSSubjugationist > 95>>
		<<set $desc = $desc + "a global magnet for ">>
		<<set $desc = $desc + $arcologies[$i].FSSupremacistRace>>
		<<set $desc = $desc + " nationalists.">>
	<<elseif $arcologies[$i].FSSubjugationist > 40>>
		<<set $desc = $desc + "becoming increasingly free of ">>
		<<set $desc = $desc + $arcologies[$i].FSSupremacistRace>>
		<<set $desc = $desc + " slaves.">>
	<<else>>
		<<set $desc = $desc + "the site of a furious debate over existing ">>
		<<set $desc = $desc + $arcologies[$i].FSSupremacistRace>>
		<<set $desc = $desc + " slaves.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSRepopulationFocus != "unset">>
	<<if $arcologies[$i].FSRepopulationFocus > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSRepopulationFocus > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Repopulation,@@ and is ">>
	<<if $arcologies[$i].FSRepopulationFocus > 95>>
		<<set $desc = $desc + "notorious for the size and number of pregnancies among its population.">>
	<<elseif $arcologies[$i].FSRepopulationFocus > 40>>
		<<set $desc = $desc + "known to be a good place to find slavegirls heavy with children.">>
	<<else>>
		<<set $desc = $desc + "actively importing fertile slave girls.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSRestart != "unset">>
	<<if $arcologies[$i].FSRestart > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSRestart > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Eugenics,@@ and is ">>
	<<if $arcologies[$i].FSRestart > 95>>
		<<set $desc = $desc + "notorious for the number of powerful civilians inhabiting it.">>
	<<elseif $arcologies[$i].FSRestart > 40>>
		<<set $desc = $desc + "known to be a good place make connections.">>
	<<else>>
		<<set $desc = $desc + "actively importing sterilization supplies.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSGenderRadicalist != "unset">>
	<<if $arcologies[$i].FSGenderRadicalist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSGenderRadicalist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Gender Radicalism,@@ and is ">>
	<<if $arcologies[$i].FSGenderRadicalist > 95>>
		<<set $desc = $desc + "notorious for the openness with which its citizens fuck its slavegirls in the ass until they cum.">>
	<<elseif $arcologies[$i].FSGenderRadicalist > 40>>
		<<set $desc = $desc + "known to be a good place to find slavegirls who cum when buttfucked.">>
	<<else>>
		<<set $desc = $desc + "actively importing a wider variety of slave girls.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSGenderFundamentalist != "unset">>
	<<if $arcologies[$i].FSGenderFundamentalist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSGenderFundamentalist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Gender Fundamentalism,@@ and is ">>
	<<if $arcologies[$i].FSGenderFundamentalist > 95>>
		<<set $desc = $desc + "famous for its slave schools, crowded with a future generation of world class slaves.">>
	<<elseif $arcologies[$i].FSGenderFundamentalist > 40>>
		<<set $desc = $desc + "remarkable for its crowds of hugely pregnant slave women.">>
	<<else>>
		<<set $desc = $desc + "importing increasing numbers of fertile slaves.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSPaternalist != "unset">>
	<<if $arcologies[$i].FSPaternalist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSPaternalist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Paternalism,@@ and is ">>
	<<if $arcologies[$i].FSPaternalist > 95>>
		<<set $desc = $desc + "the home of an educated, enlightened caste of slaves more productive than some arcologies' citizens.">>
	<<elseif $arcologies[$i].FSPaternalist > 40>>
		<<set $desc = $desc + "becoming known for its unusually cheerful atmosphere.">>
	<<else>>
		<<set $desc = $desc + "starting to demand abused slaves whose lives can be turned around.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSDegradationist != "unset">>
	<<if $arcologies[$i].FSDegradationist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSDegradationist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Degradationism,@@ and is ">>
	<<if $arcologies[$i].FSDegradationist > 95>>
		<<set $desc = $desc + "renowned and feared by slaves worldwide, as a place of blood and steel from which few ever leave.">>
	<<elseif $arcologies[$i].FSDegradationist > 40>>
		<<set $desc = $desc + "becoming dreaded by slaves, since the few it exports are full of unbelievably awful stories.">>
	<<else>>
		<<set $desc = $desc + "importing a rapidly increasing number of slaves, for some reason.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSIntellectualDependency != "unset">>
	<<if $arcologies[$i].FSIntellectualDependency > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSIntellectualDependency > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Intellectual Dependency,@@ and is ">>
	<<if $arcologies[$i].FSIntellectualDependency > 95>>
		<<set $desc = $desc + "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.">>
	<<elseif $arcologies[$i].FSIntellectualDependency > 40>>
		<<set $desc = $desc + "becoming known for its unusually simple-minded chattel.">>
	<<else>>
		<<set $desc = $desc + "importing large quantities of aphrodisiacs and psychosuppressants.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSSlaveProfessionalism != "unset">>
	<<if $arcologies[$i].FSSlaveProfessionalism > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSSlaveProfessionalism > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Slave Professionalism,@@ and is ">>
	<<if $arcologies[$i].FSSlaveProfessionalism > 95>>
		<<set $desc = $desc + "renowned as a source of some of the world's finest courtesans.">>
	<<elseif $arcologies[$i].FSSlaveProfessionalism > 40>>
		<<set $desc = $desc + "becoming revered as an intellectual paradise.">>
	<<else>>
		<<set $desc = $desc + "seeking out high-class slave trainers.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSBodyPurist != "unset">>
	<<if $arcologies[$i].FSBodyPurist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSBodyPurist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Body Purism,@@ and is ">>
	<<if $arcologies[$i].FSBodyPurist > 95>>
		<<set $desc = $desc + "a world leader in the drug industry due to its pharmaceutical research breakthroughs.">>
	<<elseif $arcologies[$i].FSBodyPurist > 40>>
		<<set $desc = $desc + "pouring an ever increasing amount of money into drug research.">>
	<<else>>
		<<set $desc = $desc + "setting up research programs to develop better slave drugs.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSTransformationFetishist != "unset">>
	<<if $arcologies[$i].FSTransformationFetishist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSTransformationFetishist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Transformation Fetishism,@@ and is ">>
	<<if $arcologies[$i].FSTransformationFetishist > 95>>
		<<set $desc = $desc + "renowned as the source of some of the world's most unbelievable surgical transformations.">>
	<<elseif $arcologies[$i].FSTransformationFetishist > 40>>
		<<set $desc = $desc + "rapidly moving from mere breast expansion to more esoteric surgical fetishism.">>
	<<else>>
		<<set $desc = $desc + "receiving daily shipments of silicone and surgical necessities.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSYouthPreferentialist != "unset">>
	<<if $arcologies[$i].FSYouthPreferentialist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSYouthPreferentialist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Youth Preferentialism,@@ and is ">>
	<<if $arcologies[$i].FSYouthPreferentialist > 95>>
		<<set $desc = $desc + "famous for the intense celebratory attention slaves receive there once reaching their majorities.">>
	<<elseif $arcologies[$i].FSYouthPreferentialist > 40>>
		<<set $desc = $desc + "moving virginity and the taking of virginity ever higher in the public estimation.">>
	<<else>>
		<<set $desc = $desc + "starting to get a reputation as an excellent place to get a good price for a virgin.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSMaturityPreferentialist != "unset">>
	<<if $arcologies[$i].FSMaturityPreferentialist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSMaturityPreferentialist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Maturity Preferentialism,@@ and is ">>
	<<if $arcologies[$i].FSMaturityPreferentialist > 95>>
		<<set $desc = $desc + "world famous among mature slaves, who see it as a paradise in which MILFs are the most valuable girls around.">>
	<<elseif $arcologies[$i].FSMaturityPreferentialist > 40>>
		<<set $desc = $desc + "striking for the variety of well-preserved beauties that can be seen there.">>
	<<else>>
		<<set $desc = $desc + "displaying an increasing demand for enslaved housewives and professional women.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSPetiteAdmiration != "unset">>
	<<if $arcologies[$i].FSPetiteAdmiration > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSPetiteAdmiration > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Petite Admiration,@@ and is ">>
	<<if $arcologies[$i].FSPetiteAdmiration > 95>>
		<<set $desc = $desc + "known as a place where even the shortest person can feel tall.">>
	<<elseif $arcologies[$i].FSPetiteAdmiration > 40>>
		<<set $desc = $desc + "investing large sums of money into petite clothing lines.">>
	<<else>>
		<<set $desc = $desc + "starting to accumulate an unusually large number of short slaves.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSStatuesqueGlorification != "unset">>
	<<if $arcologies[$i].FSStatuesqueGlorification > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSStatuesqueGlorification > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Statuesque Glorification,@@ and is ">>
	<<if $arcologies[$i].FSStatuesqueGlorification > 95>>
		<<set $desc = $desc + "world famous among tall slaves, who see it as a paradise in which they are valued above all.">>
	<<elseif $arcologies[$i].FSStatuesqueGlorification > 40>>
		<<set $desc = $desc + "going out of its way to import only the tallest of slaves.">>
	<<else>>
		<<set $desc = $desc + "starting to drive out its shorter citizenry.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSSlimnessEnthusiast != "unset">>
	<<if $arcologies[$i].FSSlimnessEnthusiast > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSSlimnessEnthusiast > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Slimness Enthusiasm,@@ and is ">>
	<<if $arcologies[$i].FSSlimnessEnthusiast > 95>>
		<<set $desc = $desc + "very well known for the wonderful variety of nubile bodies that can be seen there.">>
	<<elseif $arcologies[$i].FSSlimnessEnthusiast > 40>>
		<<set $desc = $desc + "becoming known as an arcology that slims slaves down rather than turning them into piles of tits and ass.">>
	<<else>>
		<<set $desc = $desc + "starting to display unusual fashions regarding breasts and butts.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSAssetExpansionist != "unset">>
	<<if $arcologies[$i].FSAssetExpansionist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSAssetExpansionist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Asset Expansionism,@@ and is ">>
	<<if $arcologies[$i].FSAssetExpansionist > 95>>
		<<set $desc = $desc + "widely considered an interior design masterpiece for its adaptations to slaves with fifty kilos of tits.">>
	<<elseif $arcologies[$i].FSAssetExpansionist > 40>>
		<<set $desc = $desc + "a popular tourist destination just for the view, which features some truly spectacular bare boobs.">>
	<<else>>
		<<set $desc = $desc + "demanding fatter slaves, since its citizens are learning they absorb growth hormones better.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSPastoralist != "unset">>
	<<if $arcologies[$i].FSPastoralist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSPastoralist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Pastoralist,@@ and is ">>
	<<if $arcologies[$i].FSPastoralist > 95>>
		<<set $desc = $desc + "a world-renowned producer of cowgirl dairy products of all kinds.">>
	<<elseif $arcologies[$i].FSPastoralist > 40>>
		<<set $desc = $desc + "devoting more and more of its slaves to work as cowgirls, or to the service and upkeep of cowgirls.">>
	<<else>>
		<<set $desc = $desc + "displaying an increasing public appetite for dairy, and yet imports almost no true cow's milk.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSCummunism != "unset">>
	<<if $arcologies[$i].FSCummunism > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSCummunism > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Cummunism,@@ and is ">>
	<<if $arcologies[$i].FSCummunism > 95>>
		<<set $desc = $desc + "a world-renowned producer of cum-based products of all kinds.">>
	<<elseif $arcologies[$i].FSCummunism > 40>>
		<<set $desc = $desc + "devoting more and more of its slaves to work as cumtanks, or to the service, upkeep and milking of ballgirls.">>
	<<else>>
		<<set $desc = $desc + "displaying an increasing public appetite for cum, and has begun importing more and more specialized milkers.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSPhysicalIdealist != "unset">>
	<<if $arcologies[$i].FSPhysicalIdealist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSPhysicalIdealist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Physical Idealism,@@ and is ">>
	<<if $arcologies[$i].FSPhysicalIdealist > 95>>
		<<set $desc = $desc + "a constant standout at international athletic competitions, where both its citizens and slaves do very well.">>
	<<elseif $arcologies[$i].FSPhysicalIdealist > 40>>
		<<set $desc = $desc + "quite a sight, since its citizens and slaves all lift. Constantly.">>
	<<else>>
		<<set $desc = $desc + "the site of a musclegirl fetish community.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSHedonisticDecadence != "unset">>
	<<if $arcologies[$i].FSHedonisticDecadence > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSHedonisticDecadence > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Hedonistic Decadence,@@ and is ">>
	<<if $arcologies[$i].FSHedonisticDecadence > 95>>
		<<set $desc = $desc + "very well known as a place where every imaginable desire and fantasy can be fulfilled.">>
	<<elseif $arcologies[$i].FSHedonisticDecadence > 40>>
		<<set $desc = $desc + "importing huge amounts of food and alcohol">>
	<<else>>
		<<set $desc = $desc + "the site of a large number of lazy individuals.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSIncestFetishist != "unset">>
	<<if $arcologies[$i].FSIncestFetishist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSIncestFetishist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Incest Fetishism,@@ and is ">>
	<<if $arcologies[$i].FSIncestFetishist > 95>>
		<<set $desc = $desc + "highly recommended as a place to stop by if you like threesomes with twins or familial gangbangs.">>
	<<elseif $arcologies[$i].FSIncestFetishist > 40>>
		<<set $desc = $desc + "attracting a substantial number of families.">>
	<<else>>
		<<set $desc = $desc + "devoting more and more of its resources into genealogy.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSChattelReligionist != "unset">>
	<<if $arcologies[$i].FSChattelReligionist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSChattelReligionist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Chattel Religionism,@@ and is ">>
	<<if $arcologies[$i].FSChattelReligionist > 95>>
		<<set $desc = $desc + "a significant force in the global development of Chattel Religionist dogma.">>
	<<elseif $arcologies[$i].FSChattelReligionist > 40>>
		<<set $desc = $desc + "a popular destination for devout old world citizens engaging in sex tourism.">>
	<<else>>
		<<set $desc = $desc + "in the throes of public dissention over its religious laws.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSRomanRevivalist != "unset">>
	<<if $arcologies[$i].FSRomanRevivalist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSRomanRevivalist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Roman Revivalism,@@ and is ">>
	<<if $arcologies[$i].FSRomanRevivalist > 95>>
		<<set $desc = $desc + "hugely famous for its broadcasts of gladiatorial combat, popular even in the old world.">>
	<<elseif $arcologies[$i].FSRomanRevivalist > 40>>
		<<set $desc = $desc + "almost obnoxiously aspirational, with citizens competing to serve the state best.">>
	<<else>>
		<<set $desc = $desc + "working its way through sword and sandals fashion towards proper historicity.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSAztecRevivalist != "unset">>
	<<if $arcologies[$i].FSAztecRevivalist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSAztecRevivalist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Aztec Revivalism,@@ and is ">>
	<<if $arcologies[$i].FSAztecRevivalist > 95>>
		<<set $desc = $desc + "world famous for its incredible architecture and highly qualified leading caste and military.">>
	<<elseif $arcologies[$i].FSAztecRevivalist > 40>>
		<<set $desc = $desc + "constructing great pyramids and statues with equally weighty costs.">>
	<<else>>
		<<set $desc = $desc + "struggling to embrace the amount of blood sacrifice and prayer involved.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSEgyptianRevivalist != "unset">>
	<<if $arcologies[$i].FSEgyptianRevivalist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSEgyptianRevivalist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Egyptian Revivalism,@@ and is ">>
	<<if $arcologies[$i].FSEgyptianRevivalist > 95>>
		<<set $desc = $desc + "a world famous tourist destination for the traditional festival in its plaza, which never stops.">>
	<<elseif $arcologies[$i].FSEgyptianRevivalist > 40>>
		<<set $desc = $desc + "very much under renovation, as vast blocks of stone are imported around the clock.">>
	<<else>>
		<<set $desc = $desc + "struggling with the fashion implications of so much white linen everywhere.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSEdoRevivalist != "unset">>
	<<if $arcologies[$i].FSEdoRevivalist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSEdoRevivalist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Edo Revivalism,@@ and is ">>
	<<if $arcologies[$i].FSEdoRevivalist > 95>>
		<<set $desc = $desc + "visibly trailing cherry blossoms, blown off its balconies by the wind.">>
	<<elseif $arcologies[$i].FSEdoRevivalist > 40>>
		<<set $desc = $desc + "becoming a notable cultural center, even in the old world.">>
	<<else>>
		<<set $desc = $desc + "the object of considerable debate among socially awkward teenagers worldwide.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSArabianRevivalist != "unset">>
	<<if $arcologies[$i].FSArabianRevivalist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSArabianRevivalist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
	<<set $desc = "just establishing ">>
		<</if>>
	<<set $desc = $desc + "@@.gold;Arabian Revivalism,@@ and is ">>
	<<if $arcologies[$i].FSArabianRevivalist > 95>>
		<<set $desc = $desc + "a famous center of Arabian romanticism, since it has the wisdom not to be historically perfect.">>
	<<elseif $arcologies[$i].FSArabianRevivalist > 40>>
		<<set $desc = $desc + "working through which parts of Arabian romanticism to adopt and which to discard.">>
	<<else>>
		<<set $desc = $desc + "eagerly reveling in the most romantic parts of superficial Arabian romanticism.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>
<<if $arcologies[$i].FSChineseRevivalist != "unset">>
	<<if $arcologies[$i].FSChineseRevivalist > 80>>
		<<set $desc = "committed to ">>
	<<elseif $arcologies[$i].FSChineseRevivalist > 20>>
		<<set $desc = "pursuing ">>
	<<else>>
		<<set $desc = "just establishing ">>
	<</if>>
	<<set $desc = $desc + "@@.gold;Chinese Revivalism,@@ and is ">>
	<<if $arcologies[$i].FSChineseRevivalist > 95>>
		<<set $desc = $desc + "now confidently mature about its possession of the Mandate of Heaven.">>
	<<elseif $arcologies[$i].FSChineseRevivalist > 40>>
		<<set $desc = $desc + "often aggressive about its status as the middle kingdom to the point that it betrays uncertainty.">>
	<<else>>
		<<set $desc = $desc + "moving towards a stage of refinement that will allow it to present itself properly.">>
	<</if>>
	<<set _neighborDescription.push($desc)>>
<</if>>

<<if _neighborDescription.length > 0>>
	<<if _neighborDescription.length > 2>>
		Its culture is complex. First, it is _neighborDescription[0]
		<<for $j = 1; $j < _neighborDescription.length; $j++>>
			<<if $j < _neighborDescription.length-1>>It is<<else>>Finally, the arcology is<</if>> _neighborDescription[$j]
		<</for>>
	<<elseif _neighborDescription.length == 2>>
		Its culture is developing along two lines. First, it is _neighborDescription[0] Second, it is _neighborDescription[1]
	<<else>>
		Its culture is diverging from the old world: it is _neighborDescription[0]
	<</if>>
<</if>>
</div>


code:
.neighbor-container {
  background-color: rgba(17,17,17,1);
  padding: 5px;
  max-width: fit-content;
  margin: 0 auto;
}

.neighbor-container-grid {
  display: grid;
  grid-template-columns: auto auto auto;
}

.neighbor-container-list {
  display: flex;
  flex-flow: row wrap;
  align-items: flex-start;
}

.neighbor-item {
  margin: 3px;
  border: 5px solid;
  padding: 10px;
  text-align: center;
}

.neighbor-item-selected {
  background-color: rgba(255, 255, 255, 0.2);
}

.neighbor-item-empty {
  border-color: rgba(17,17,17,1);
}

.neighbor-item-self {
  border-color: white;
}

.neighbor-item-owned {
  border-color: steelblue;
}

.neighbor-item-unowned {
  border-color: red;
}

.neighbor-item-rival {
  border-color: crimson;
}


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 = document.createElement("div");
		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 $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());
	}

	/** 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 tooltip = App.UI.DOM.makeElement("span", tooltipText, "tooltip");
			let res = App.UI.DOM.makeElement("span", text, ["textWithTooltip", className]);
			res.appendChild(tooltip);
			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: "));
			if (arcID === 0) {
				const fses = FutureSocieties.activeFSes(0);
				for (const fs of fses) {
					frag.appendChild(withTooltip("⯁", FutureSocieties.displayName(fs), "steelblue"));
				}
			} else {
				const fses = FutureSocieties.activeFSes(arcID);
				const diplo = FutureSocieties.diplomaticFSes(arcID, 0);
				for (const fs of fses) {
					let style = "white";
					if (diplo.shared.includes(fs)) {
						style = "steelblue";
					} else {
						const conflict = diplo.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, "choicesStrip");
		}

		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() {
	let nd = new App.Neighbor.Display((id) => replaceDetails(id));

	/** Output the arcology list
	 * @returns {DocumentFragment}
	 */
	function list() {
		V.arcologies.forEach((a) => a.prosperity = Math.clamp(a.prosperity, 1, 300));
		if (!V.activeArcologyIdx) {
			V.activeArcologyIdx = 0;
		}

		let frag = document.createDocumentFragment();

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

		// empty container for details
		const detailSpan = App.UI.DOM.appendNewElement("span", frag);
		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.activeArcologyIdx = arcID;
		const container = $("#neighbor-details").empty();
		container.append(description(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 div containing the neighbor's description
	 * @param {number} arcID
	 * @returns {Element}
	 */
	function description(arcID) {
		// still in Twine for now
		let container = document.createElement("div");
		V.i = arcID;
		App.UI.DOM.includePassage(container, "Neighbor Description");
		return container;
	}

	/** 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(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 ${desc.reduce((res, ch, i, arr) => res + (i === arr.length - 1 ? ' and ' : ', ') + ch)}.`);
			} 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 {
			const ownershipCost = 500*Math.trunc(V.arcologies[arcID].prosperity*(1+(V.arcologies[arcID].demandFactor/100)));
			frag.append(`A 1% interest in this arcology is worth ${cashFormat(ownershipCost)}. `);
			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", (f) => {
					cashX(forceNeg(ownershipCost), "war");
					cashX(-10000, "war");
					V.arcologies[arcID].PCminority += 1;
					V.arcologies[arcID].demandFactor += 2;
				}, [], "Neighbor Interact"));
				if (V.arcologies[arcID].ownership + V.arcologies[arcID].PCminority + V.arcologies[arcID].minority <= 90) {
					if (V.cash > ownershipCost*10) {
						const link = App.UI.DOM.link("10%", (f) => {
							cashX(forceNeg(ownershipCost*10), "war");
							cashX(-10000, "war");
							V.arcologies[arcID].PCminority += 10;
							V.arcologies[arcID].demandFactor += 20;
						}, [], "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", (f) => {
					cashX(ownershipCost, "war");
					V.arcologies[arcID].PCminority -= 1;
					V.arcologies[arcID].demandFactor -= 2;
					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%", (f) => {
						cashX((ownershipCost*10), "war");
						V.arcologies[arcID].PCminority -= 10;
						V.arcologies[arcID].demandFactor -= 20;
						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;
	}

	/** 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 = V.slaves.find((s) => s.assignment === Job.AGENTPARTNER && s.relationshipTarget === agent.ID);
			if (agentPartner) {
				linkText = `Recall them and reenslave your agent`;
				residentList.push(agentPartner.ID);
			}
			container.append(App.UI.SlaveList.render.listDOM(residentList, [], App.UI.SlaveList.SlaveInteract.stdInteract));
			container.append(App.UI.DOM.link(linkText, (f) => { 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 (setup.HGCareers.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 === "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] = "unset"; arcChanged(arcID); };
		for (const fs of FutureSocieties.activeFSes(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");
		let r = [];
		r.push(`If ${V.arcologies[arcID].name} has developed enough to begin exporting worthwhile goods, it may be of interest to acquire some.`);
		const opinionDiscount = App.Neighbor.opinion(arcID, 0)*10;
		const basePrice = Math.trunc((7500-opinionDiscount)*V.upgradeMultiplierTrade);
		const controlled = (V.arcologies[arcID].government === "your trustees") || (V.arcologies[arcID].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 (V.arcologies[arcID].direction === V.arcologies[0].embargoTarget) {
			r.push(`However, due to your active embargo, trade with ${V.arcologies[arcID].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 {string} property - 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 (V.arcologies[arcID][fsRequired] > 95) {
				if (!isItemAccessible.entry(itemName, category)) {
					if (controlled) {
						const link = App.UI.DOM.link(`Request a shipment of ${itemDisplay}`, (f) => {
							V[property] = 1;
							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}`, (f) => {
							V[property] = 1;
							replaceDetails(arcID);
						});
						App.UI.DOM.appendNewElement("div", container, link);
					} else if (V.arcologies[arcID].direction !== V.arcologies[0].embargoTarget) {
						const link = App.UI.DOM.link(`Divert an outgoing shipment of ${itemDisplay}`, (f) => {
							V[property] = 1;
							cashX(forceNeg(itemPrice), "capEx");
							// replaceDetails(arcID); - cash changed, force passage transition for sidebar update
						}, [], "Neighbor Interact");
						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", "clothing", "togas", "clothesBoughtToga");
		addAcquisitionBlock("FSEdoRevivalist", "a kimono", "clothing", "kimonos", "clothesBoughtKimono");
		addAcquisitionBlock("FSArabianRevivalist", "harem gauze", "clothing", "silken harem garb", "clothesBoughtHarem");
		addAcquisitionBlock("FSAztecRevivalist", "a huipil", "clothing", "huipils", "clothesBoughtHuipil");
		addAcquisitionBlock("FSChineseRevivalist", "a slutty qipao", "clothing", "skimpy qipaos", "clothesBoughtQipao");
		addAcquisitionBlock("FSEgyptianRevivalist", "ancient Egyptian", "collar", "Egyptian necklace replicas", "clothesBoughtEgypt");
		addAcquisitionBlock("FSPaternalist", "conservative clothing", "clothing", "conservative clothing", "clothesBoughtConservative");
		addAcquisitionBlock("FSDegradationist", "chains", "clothing", "binding chains", "clothesBoughtChains");
		addAcquisitionBlock("FSGenderFundamentalist", "a bunny outfit", "clothing", "bunny suits", "clothesBoughtBunny");
		addAcquisitionBlock("FSIntellectualDependency", "a bimbo outfit", "cloting", "bimbo attire", "clothesBoughtBimbo");
		addAcquisitionBlock("FSSlaveProfessionalism", "a courtesan dress", "clothing", "cortesan dresses", "clothesBoughtCourtesan");
		// addAcquisitionBlock("FSPetiteAdmiration", "petite dress", "clothing", "petite-sized dresses", "clothesBoughtPetite");
		addAcquisitionBlock("FSPhysicalIdealist", "body oil", "clothing", "body oil", "clothesBoughtOil");
		addAcquisitionBlock("FSHedonisticDecadence", "stretch pants and a crop-top", "clothing", "stretch pants and crop-tops", "clothesBoughtLazyClothes");
		addAcquisitionBlock("FSChattelReligionist", "a chattel habit", "clothing", "chattel religionist habits", "clothesBoughtHabit");
		addAcquisitionBlock("FSPastoralist", "Western clothing", "clothing", "Western clothing", "clothesBoughtWestern");
		addAcquisitionBlock("FSRepopulationFocus", "a maternity dress", "clothing", "maternity dresses", "clothesBoughtMaternityDress");
		addAcquisitionBlock("FSRepopulationFocus", "attractive lingerie for a pregnant woman", "clothing", "maternity lingerie", "clothesBoughtMaternityLingerie");
		addAcquisitionBlock("FSRepopulationFocus", "a small empathy belly", "bellyAccessory", "empathy bellies", "clothesBoughtBelly");
		addAcquisitionBlock("FSStatuesqueGlorification", "platform heels", "shoes", "platform shoes", "shoesBoughtHeels");

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

		return container;
	}

	return list;
})();


code:
:: Neighbor Interact [nobr jump-to-safe jump-from-safe]

<<set $averageProsperity = _.mean($arcologies.map((a) => a.prosperity))>>

<<if $cheatMode == 1>>
	[[Cheat Edit Neighboring Arcologies|MOD_Edit Neighbor Arcology Cheat]]

<</if>>

<<set $nextLink = "Main">>
<<set $nextButton = "Back">>
You have <<print $arcologies.length-1>> neighbors.

<<if $arcologies[0].embargoTarget == -1>>
	You are not engaged in economic warfare against a neighboring arcology.
<<else>>
	<<set _nei = $arcologies.findIndex(function(s) { return s.direction == $arcologies[0].embargoTarget; })>>
	You have targeted '' $arcologies[_nei].name'' for economic warfare.

    Its planned impacts will be
	<<if $arcologies[0].embargo == 3>>
		''widespread.'' [[Moderate|Neighbor Interact][$arcologies[0].embargo -= 1]]
	<<elseif $arcologies[0].embargo == 2>>
		''limited.'' [[Intensify|Neighbor Interact][$arcologies[0].embargo += 1]] | [[Moderate|Neighbor Interact][$arcologies[0].embargo -= 1]]
	<<else>>
		''nominal.'' [[Intensify|Neighbor Interact][$arcologies[0].embargo += 1]]
	<</if>>
	| [[Cancel|Neighbor Interact][$arcologies[0].embargoTarget = -1]]
<</if>>

<<if $arcologies[0].influenceTarget == -1>>
	You are not using your arcology's culture to attempt to influence neighboring arcologies' development.
<<else>>
	<<set _nei = $arcologies.findIndex(function(s) { return s.direction == $arcologies[0].influenceTarget; })>>
	You have targeted '' $arcologies[_nei].name'' for cultural influence.
	[[Stop|Neighbor Interact][$arcologies[0].influenceTarget = -1]]
<</if>>

<<if $PC.skill.hacking > 0>>

	<<if $arcologies[0].CyberEconomicTarget == -1>>
		You are not engaged in cyber warfare against a neighboring arcology.
	<<else>>
		<<set _nei = $arcologies.findIndex(function(s) { return s.direction == $arcologies[0].CyberEconomicTarget; })>>
		You have targeted ''$arcologies[_nei].name'' for cyber economic warfare.

    Its planned impacts will be
		<<if $arcologies[0].CyberEconomic == 3>>
			''widespread.'' [[Moderate|Neighbor Interact][$arcologies[0].CyberEconomic -= 1]]
		<<elseif $arcologies[0].CyberEconomic == 2>>
			''limited.'' [[Intensify|Neighbor Interact][$arcologies[0].CyberEconomic += 1]] | [[Moderate|Neighbor Interact][$arcologies[0].CyberEconomic -= 1]]
		<<else>>
			''nominal.'' [[Intensify|Neighbor Interact][$arcologies[0].CyberEconomic += 1]]
		<</if>>
		| [[Revert|Neighbor Interact][$arcologies[0].CyberEconomicTarget = -1]]
	<</if>>

	<<if $arcologies[0].CyberReputationTarget == -1>>
		You are not engaged in character assassination against a neighboring arcology.
	<<else>>
		<<set _nei = $arcologies.findIndex(function(s) { return s.direction == $arcologies[0].CyberReputationTarget; })>>
		You have targeted the leadership of ''$arcologies[_nei].name'' for character assassination.

    Its planned impacts will be
		<<if $arcologies[0].CyberReputation == 3>>
			''widespread.'' [[Moderate|Neighbor Interact][$arcologies[0].CyberReputation -= 1]]
		<<elseif $arcologies[0].CyberReputation == 2>>
			''limited.'' [[Intensify|Neighbor Interact][$arcologies[0].CyberReputation += 1]] | [[Moderate|Neighbor Interact][$arcologies[0].CyberReputation -= 1]]
		<<else>>
			''nominal.'' [[Intensify|Neighbor Interact][$arcologies[0].CyberReputation += 1]]
		<</if>>
		| [[Abort|Neighbor Interact][$arcologies[0].CyberReputationTarget = -1]]
	<</if>>
<</if>>

<<includeDOM App.Neighbor.Interact()>>