code:
App.Events.secExpSmilingMan3 = class secExpSmilingMan3 extends App.Events.BaseEvent {
	eventPrerequisites() {
		return [
			() => V.rivalOwner === 0,
			() => V.secExpEnabled > 0,
			() => V.SecExp.smilingMan.progress === 3,
		];
	}

	execute(node) {
		let r = [];

		let smileSlave;
		if (V.seeDicks !== 100) {
			smileSlave = GenerateNewSlave("XX", {
				minAge: 16, maxAge: 18, ageOverridesPedoMode: 1, disableDisability: 1, race: "asian", nationality: "Japanese"
			});
			smileSlave.faceShape = "cute";
			smileSlave.boobs = 450;
			smileSlave.vagina = 0;
			smileSlave.ovaries = 1;
		} else {
			smileSlave = GenerateNewSlave("XY", {
				minAge: 16, maxAge: 18, ageOverridesPedoMode: 1, disableDisability: 1, race: "asian", nationality: "Japanese"
			});
			smileSlave.boobs = 250;
			smileSlave.faceShape = "androgynous";
		}
		smileSlave.boobShape = "perky";
		smileSlave.nipples = "cute";
		smileSlave.origin = "$He was a criminal mastermind, captured shortly after completing $his master plan.";
		smileSlave.career = "a student from a private school";
		smileSlave.intelligence = 100;
		smileSlave.intelligenceImplant = 30;
		smileSlave.slaveSurname = "Yamadera";
		smileSlave.birthSurname = "Yamadera";
		smileSlave.origSkin = "pale";
		applyGeneticColor(smileSlave);
		smileSlave.devotion = 5 * V.SecExp.smilingMan.relationship;
		smileSlave.trust = 5 * V.SecExp.smilingMan.relationship;
		smileSlave.face = random(10, 50);
		setHealth(smileSlave, 70, 0, 0, 0, 0);
		smileSlave.teeth = "normal";
		smileSlave.areolae = 0;
		smileSlave.anus = 0;
		smileSlave.butt = 3;
		smileSlave.lips = 15;
		smileSlave.behavioralFlaw = "odd";
		smileSlave.skill.vaginal = 0;
		smileSlave.skill.oral = 0;
		smileSlave.skill.anal = 0;
		smileSlave.skill.whoring = 0;
		smileSlave.skill.entertainment = 0;
		smileSlave.birthWeek = random(0, 50);
		smileSlave.voice = 2;
		smileSlave.weight = -20;
		smileSlave.muscles = 0;
		smileSlave.shoulders = -1;
		smileSlave.hips = 0;
		smileSlave.clit = 0;
		smileSlave.labia = 0;
		smileSlave.waist = 10;
		smileSlave.preg = 0;
		smileSlave.prestige = 3;
		smileSlave.prestigeDesc = "$He was the famous Smiling Man.";
		smileSlave.clothes = "a military uniform"; // closest thing to commie/punk we have at the moment

		const {
			His, He,
			his, he, him, girl
		} = getPronouns(smileSlave);
		V.nextButton = " ";
		V.fcnn.push("...sometimes high-tech problems have low-tech solutions. Back to you in the...");

		r.push(`The day has come to finally put an end to this story. Your men are ready to go, waiting only on your signal. You quickly don your protective gear and proceed down the busy streets of your arcology.`);
		r.push(`You carefully planned the day so that nothing could exit the arcology without being scanned at least three times and poked twice. The Smiling Man has no escape.`);
		App.Events.addParagraph(node, r);
		r = [];
		r.push(`After a short walk you are in front of the criminal's lair, a rundown old apartment in a scarcely populated part of the arcology. You give the order to breach and your men rush inside without problems.`);
		r.push(`After a couple of seconds pass without a single noise coming from the apartment, you begin to worry. Then you hear the captain calling you inside.`);
		App.Events.addParagraph(node, r);

		App.Events.addResponses(node, [
			new App.Events.Result(`Enter`, inside),
		]);

		function inside() {
			const frag = new DocumentFragment();
			let r = [];

			if (V.seeImages === 1) {
				App.Events.drawEventArt(frag, smileSlave);
			}
			r.push(`"So it was you to find me first.`);
			if (V.SecExp.smilingMan.relationship > 2) {
				r.push(`I was hoping you would be the one!`);
			} else {
				r.push(`I expected you would be the one.`);
			}
			r.push(`Well, I hope I'm everything you expected," you hear a voice say. Despite recognizing it, you struggle to convince yourself that the little ${girl} in front of you is indeed the famous criminal mastermind.`);
			App.Events.addParagraph(frag, r);
			App.Events.addParagraph(frag, [`"As you can see, I have no intention of escaping. I knew my life was over the second my plan went into effect. I accepted my end a long time ago, so feel free to do your worst; my life has already ended in triumph."`]);
			App.Events.addParagraph(frag, [`You evaluate the situation: the burning desire of vengeance for all the damage that little twerp caused is hard to ignore, but equally so is the admiration for ${his} skill and determination. Indeed, ${he} would be a great addition to your court, as a free individual or not.`]);

			App.Events.addResponses(frag, [
				new App.Events.Result(`Offer ${him} a new life`, offer),
				new App.Events.Result(`Make ${him} pay`, punish),
				new App.Events.Result(`Enslave ${him}`, enslave),
			]);

			return frag;

			function offer() {
				const frag = new DocumentFragment();
				let r = [];
				V.SecExp.smilingMan.progress = 10;
				r.push(`You decide it would be criminally wasteful to throw away such talent. You offer ${him} a new life at your side. ${His} expertise will surely guarantee safety, if not supremacy, to your arcology in cyberspace, while ${he} will have safety and luxury in the physical world.`);
				r.push(App.UI.DOM.link(
					"Continue",
					() => {
						$(node).empty().append(result());
					}
				));
				App.Events.addParagraph(frag, r);
				return frag;
			}

			function punish() {
				const frag = new DocumentFragment();
				let r = [];
				V.SecExp.smilingMan.progress = 20;
				r.push(`You decide to end ${his} pitiful life. ${He} has crossed the wrong master of the new world ${he} worked so hard to create. No mercy was asked for and no mercy will be given.`);
				r.push(App.UI.DOM.link(
					"Continue",
					() => {
						$(node).empty().append(result());
					}
				));
				App.Events.addParagraph(frag, r);
				return frag;
			}

			function enslave() {
				const frag = new DocumentFragment();
				let r = [];
				V.SecExp.smilingMan.progress = 30;
				r.push(`You decide to enslave the ${girl}. ${His} skill may be great, but ${his} crimes are equally so, which will make it all the sweeter to turn ${him} into an obedient little toy to play with.`);
				r.push(App.UI.DOM.link(
					"Continue",
					() => {
						$(node).empty().append(result());
					}
				));
				App.Events.addParagraph(frag, r);
				return frag;
			}

			function result() {
				const frag = new DocumentFragment();
				let r = [];
				V.nextButton = "Continue";
				App.Utils.scheduleSidebarRefresh();
				window.scrollTo(0, window.pageYOffset);
				if (V.SecExp.smilingMan.progress < 30) {
					if (V.seeImages === 1) {
						App.Events.drawEventArt(frag, smileSlave);
					}
					if (V.SecExp.smilingMan.progress === 10) {
						r.push(`The ${girl} asks for a few minutes to think about your offer,`);
						if (V.SecExp.smilingMan.relationship >= 4) {
							r.push(`but ${he} quickly comes to terms with the situation and accepts.`);
						} else {
							r.push(`and after some time ${he} reluctantly accepts.`);
						}
						r.push(`In the following weeks ${he} will get acquainted with the security network of the arcology and work to protect ${his} new home in the new world ${he} has created.`);
						r.push(`The world will never find out the truth of the Smiling Man and his legend will only grow with time, outliving his creator and maybe even ${his} new employer.`);
						App.Events.addParagraph(node, r);
						r = [];
						r.push(`The collaboration of the ex-Smiling Man permanently increases security and the rate of prosperity growth. Cash will be also provided, but you're better off not knowing the sources.`);
						App.Events.addParagraph(node, r);
						r = [];
					} else if (V.SecExp.smilingMan.progress === 20) {
						smileSlave.clothes = "no clothing";
						r.push(`For such a criminal a simple execution is not enough. You order the ${girl} captured and crucified outside the city, with a mask resembling ${his} famous symbol. Your men quickly obey. ${He} never once shows sign of pain or fear, remaining stoic and proud to the end.`);
						r.push(`Once ${his} life ends, you order a statue erected in commemoration of the death of the Smiling Man. From this day forward the statue of the crucified criminal will adorn your arcology and his legend will be forever entangled with yours.`);
						App.Events.addParagraph(node, r);
						r = [];
						r.push(`Having dealt with the Smiling Man will provide <span class="reputation inc">a large boost to your reputation, as well as a moderate amount of reputation each week.</span>`);
						App.Events.addParagraph(node, r);
						r = [];
						repX(10000, "architecture");
					}
				} else if (V.SecExp.smilingMan.progress === 30) {
					smileSlave.clothes = "no clothing";
					r.push(`Your men move to immobilize ${him}. Terror flashes through ${his} eyes for`);
					if (V.SecExp.smilingMan.relationship >= 4) {
						r.push(`a second, but ${he} quickly recovers ${his} usual demeanor.`);
					} else {
						r.push(`a second — ${he} barely manages to recover ${his} usual demeanor.`);
					}
					App.Events.addParagraph(node, r);
					r = [];
					r.push(App.Desc.longSlave(smileSlave));
					r.push(App.UI.newSlaveIntro(smileSlave));
					App.Events.addParagraph(node, r);
				}
				delete V.SecExp.smilingMan.relationship;

				App.Events.addParagraph(frag, r);
				return frag;
			}
		}
	}
};


code:
App.SecExp.unit = (function() {
	const equipUpgradeCost = 350;
	const secBotsUpgradeCost = 450;
	const secBotsCost = 500;
	return {
		list,
		checkID,
		gen:generate,
		barracksList,
		replenishAll,
		isDeployed,
		describe,
		squads,
		genID,
		unitFree,
		// replenish - helper function
	};

	function list() {
		return new Map([
			["bots", {
				baseID: 0,
				maxID: 99,
				defaultName: "drone squad",
				previousVars: {
					count: (V.SecExp.units.bots && V.SecExp.units.bots.ID || V.secBots ? 1 : undefined),
					squads: (V.SecExp.units.bots && V.SecExp.units.bots.ID || V.secBots) ? [V.SecExp.units.bots || V.secBots] : undefined
				},
				unlock: V.arcologyUpgrade.drones > 0,
				lockedText: "As you have not yet installed a drone security system in your arcology, you will not be able to form a unit of drones.",
				name: "Security Drones",
				squadSizeCap: V.SF.Toggle && V.SF.Active >= 1 && V.SecExp.edicts.SFSupportLevel >= 1 ? 100 : 80
			}],
			["slaves", {
				baseID: 100,
				maxID: 199,
				defaultName: "slave platoon",
				costMod: 0.5,
				previousVars: {
					count: V.createdSlavesUnits,
					squads: V.slaveUnits,
					killed: V.slavesTotalCasualties,
				},
				unlock: true,
				squadSizeCap: 50
			}],
			["militia", {
				baseID: 200,
				maxID: 299,
				defaultName: "citizens' platoon",
				costMod: 1,
				previousVars: {
					count: V.createdMilitiaUnits,
					squads: V.militiaUnits,
					killed: V.militiaTotalCasualties,
					free: V.militiaFreeManpower
				},
				unlock: V.SecExp.edicts.defense.militia > 0,
				lockedText: "As you have not yet founded the militia, you will not be able to form citizens units.",
				squadSizeCap: 50
			}],
			["mercs", {
				baseID: 300,
				maxID: 399,
				defaultName: "mercenary platoon",
				costMod: 1.5,
				previousVars: {
					count: V.createdMercUnits,
					squads: V.mercUnits,
					killed: V.mercTotalCasualties,
					free: V.mercFreeManpower
				},
				unlock: V.mercenaries > 0,
				lockedText: "As mercenaries are not allowed inside the arcology. You will not be able to recruit mercenary units.",
				name: "Mercenaries",
				squadSizeCap: 50
			}],
		]);
	}

	/** Calculate the unit type via passed unitID.
	 * @param {number} unitID
	 * @returns {FC.SecExp.PlayerHumanUnitTypeMod}
	 */
	function checkID(unitID) {
		for (const [u, d] of list()) {
			if (unitID.isBetween(d.baseID, d.maxID, true)) {
				return u;
			}
		}
	}

	/** Creates the requested unit object.
	 * @param {FC.SecExp.PlayerHumanUnitTypeMod} type
	 * @returns {void}
	 */
	function generate(type, addNew=false) {
		if (V.secExpEnabled === 0) {
			return;
		}

		if (!V.SecExp.units[type] || Object.values(list().get(type).previousVars).filter(s => s !== undefined).length) {
			const squads = list().get(type).previousVars.squads;
			const defaultName = list().get(type).defaultName;
			const count = list().get(type).previousVars.count;
			const killed = list().get(type).previousVars.killed;
			const free = list().get(type).previousVars.free;

			V.SecExp.units[type] = V.SecExp.units[type] || {};
			if (type === "bots" && (V.SecExp.units.bots.ID || V.secBots)) {
				V.SecExp.units[type] = {};
			} else {
				V.SecExp.units[type].dead = V.SecExp.units[type].dead || killed || 0;
				if (type !== 'slaves') {
					V.SecExp.units[type].free = V.SecExp.units[type].free || free || 0;
				}
			}
			V.SecExp.units[type].squads = V.SecExp.units[type].squads || squads || [];
			V.SecExp.units[type].defaultName = V.SecExp.units[type].defaultName || defaultName;
			V.SecExp.units[type].created = V.SecExp.units[type].created || count || 0;
		} else if (addNew) {
			V.SecExp.units[type].created++;
			let newUnit = {
				equip: 0, active: 1,
				maxTroops: 30, troops: 30,
				platoonName: `${ordinalSuffix(V.SecExp.units[type].created)} ${V.SecExp.units[type].defaultName}`
			};
			if (type !== "bots") {
				Object.assign(newUnit, {
					training: 0, cyber: 0, medics: 0,
					SF: 0, commissars: 0, battlesFought: 0,
					loyalty: jsRandom(40, 60),
				});
				newUnit.troops = Math.min(newUnit.maxTroops, unitFree(type).print());
				unitFree(type).remove(newUnit.troops);
			}
			genID(newUnit, type);
			V.SecExp.units[type].squads.push(newUnit);
		}
	}

	/** Prints a list of options that can be applied to the passed unit array.
	 * @param {FC.SecExp.PlayerHumanUnitTypeMod} type
	 * @returns {DocumentFragment}
	 */
	function barracksList(type) {
		let list = new DocumentFragment();
		let unitDetail = new DocumentFragment();
		let linkArray = [];

		linkArray.push("Default unit name:", App.UI.DOM.makeTextBox(V.SecExp.units[type].defaultName, str => { V.SecExp.units[type].defaultName = str; App.UI.reload(); }));
		if (unitFree(type).canUpgrade() && App.SecExp.battle.activeUnits() < App.SecExp.battle.maxUnits()) {
			linkArray.push(App.UI.DOM.link(`Form a new unit`, () => {
				generate(type, true);
				if (type === "bots") {
					cashX(forceNeg(secBotsCost * V.SecExp.units.bots.squads.length + 1), "securityExpansion");
				}
				App.UI.reload();
			}
			));
		}

		linkArray.push(bulkUpgrade(V.SecExp.units[type].squads));
		App.UI.DOM.appendNewElement("div", list, App.UI.DOM.generateLinksStrip(linkArray));

		if (type === "slaves") {
			App.UI.DOM.appendNewElement("div", list, App.UI.market({menialWorkersOnly: true}));
		}

		for (const unit of V.SecExp.units[type].squads) {
			linkArray = [];

			App.UI.DOM.appendNewElement("div", unitDetail, describe(unit, false));
			linkArray.push(App.UI.DOM.makeTextBox(unit.platoonName, str => { unit.platoonName = str; App.UI.reload(); }));
			linkArray.push(App.UI.DOM.link(`Disband the unit`, () => {
				unitFree(type).add(unit.troops);
				V.SecExp.units[type].squads.deleteAt(unit);
				V.SecExp.battles.lastSelection.deleteAt(unit);
				App.UI.reload();
			}
			));

			if (unit.active === 1) {
				if (unit.troops < unit.maxTroops && unitFree(unit).canUpgrade()) {
					linkArray.push(App.UI.DOM.link(`Replenish unit`, () => {
						replenish(unit);
						App.UI.reload();
					}
					));
				}
			} else {
				if (type !== "bots") {
					if (unitFree(unit).canUpgrade()) {
						linkArray.push(App.UI.DOM.link(`Reform unit`, () => {
							if (unitFree(type).print() >= unit.maxTroops) {
								unitFree(type).remove(unit.maxTroops);
								unit.troops = unit.maxTroops;
							} else {
								unit.troops += unitFree(type).print();
								unitFree(type).set(0);
							}
							unit.training = 0;
							unit.active = 1;
							App.UI.reload();
						}
						));
					}
				} else {
					linkArray.push(App.UI.DOM.link(`Rebuild the unit`, () => {
						cashX(-((unit.maxTroops - unit.troops) * secBotsCost), "securityExpansion");
						unit.troops = unit.maxTroops;
						unit.active = 1;
						App.UI.reload();
					}
					));
				}
			}
			linkArray.push(bulkUpgrade(unit));
			App.UI.DOM.appendNewElement("div", unitDetail, App.UI.DOM.generateLinksStrip(linkArray));

			if (V.SecExp.settings.showStats === 1) {
				App.UI.DOM.appendNewElement("div", unitDetail, App.SecExp.getUnit(type, unit).printStats());
			}

			let options = new DocumentFragment();
			if (type !== "bots") {
				if (unit.maxTroops < 50) {
					options.append(`For ${cashFormat(upgradeCost("squadSize", unit))} provide this unit's `);
					options.append(App.UI.DOM.link("officers with intensive training", () => {
						cashX(upgradeCost("squadSize", unit), "securityExpansion");
						unit.maxTroops += 10;
						App.UI.reload();
					}
					));
					options.append(` to increase the maximum number of soldiers in the unit by 10.`);
				} else {
					options.append(`Your officers reached their peak. Further training will have little impact on the number of troops they can effectively lead.`);
				}
			} else {
				if (unit.maxTroops < 80) {
					options.append(App.UI.DOM.link("Improve the digital control matrix ", () => {
						cashX(upgradeCost("squadSize", unit), "securityExpansion");
						unit.maxTroops += 10;
						App.UI.reload();
					}
					));
					options.append(`Invest in the development of more refined controls for your drones to increase the maximum number of drones in the unit.`);
					options.append(`Costs ${cashFormat(upgradeCost("squadSize", unit))} per upgrade and each will increase the max by 10`);
				} else if (V.SF.Toggle && V.SF.Active >= 1 && unit.maxTroops < 100 && V.SecExp.edicts.SFSupportLevel >= 1) {
					options.append(App.UI.DOM.link(`Refine the drone network with ${V.SF.Lower} assistance `, () => {
						cashX(upgradeCost("squadSize", unit), "securityExpansion");
						unit.maxTroops += 10;
						App.UI.reload();
					}
					));
					options.append(`Utilize the technological developments made by ${V.SF.Lower} to further improve the control matrix of the security drones.`);
					options.append(`Costs ${cashFormat(upgradeCost("squadSize", unit))} and will increase the max by 10`);
				} else if (V.SF.Toggle && V.SF.Active >= 1 && V.SecExp.edicts.SFSupportLevel === 0) {
					options.append(`There's little left to improve in the matrix. However support from ${V.SF.Lower} might give some more room from improvement, if an assistance contract is signed.`);
				} else {
					options.append(`There's little left to improve in the matrix. Your control systems are at top capacity and won't be able to handle a bigger drone unit.`);
				}
			}
			App.UI.DOM.appendNewElement("div", unitDetail, options);

			options = new DocumentFragment();
			if (type !== "bots") {
				if (unit.equip < 3) {
					options.append(`For ${cashFormat(upgradeCost("equipment", unit))} invest in `);
					options.append(App.UI.DOM.link("better equipment", () => {
						cashX(upgradeCost("equipment", unit), "securityExpansion");
						unit.equip++;
						App.UI.reload();
					}
					));
					options.append(` to increase this unit's attack and defense by 15% per investment.`);
				} else {
					options.append(`The unit is equipped with state of the art weaponry and equipment.`);
				}
			} else {
				if (unit.equip < 3) {
					options.append(App.UI.DOM.link("Improve drone weaponry and armor ", () => {
						cashX(upgradeCost("equipment", unit), "securityExpansion");
						unit.equip++;
						App.UI.reload();
					}
					));
					options.append(`Invest in better equipment for your drones to increase their battle effectiveness.`);
					options.append(`Costs ${cashFormat(upgradeCost("equipment", unit))} and will increase attack and defense value of the unit by 15% for every upgrade.`);
				} else {
					options.append(`Your drones are equipped with top tier weaponry and armor.`);
				}
			}
			App.UI.DOM.appendNewElement("div", unitDetail, options);

			if (type !== "bots") {
				options = new DocumentFragment();
				if (unit.commissars < 2) {
					options.append(`For ${cashFormat(upgradeCost("commissars", unit))} attach `);
					options.append(App.UI.DOM.link("commissars", () => {
						cashX(upgradeCost("commissars", unit), "securityExpansion");
						unit.commissars++;
						App.UI.reload();
					}
					));
					options.append(` to slowly increase this unit's loyalty.`);
				}
				if (unit.commissars === 1) {
					options.append(" The unit has a commissar detachment, keeping under control the ambitions of the unit's officers.");
				} else if (unit.commissars === 2) {
					options.append(" The unit has a perfectly trained and loyal commissar detachment, keeping under control the ambitions of the unit's officers.");
				}
				App.UI.DOM.appendNewElement("div", unitDetail, options);

				options = new DocumentFragment();
				if (V.prostheticsUpgrade >= 2 || V.researchLab.advCombatPLimb === 1) {
					if (unit.cyber === 0) {
						options.append(`For ${cashFormat(upgradeCost("cyber", unit))} `);
						options.append(App.UI.DOM.link("augment all soldiers of the unit", () => {
							cashX(upgradeCost("cyber", unit), "securityExpansion");
							unit.cyber++;
							App.UI.reload();
						}
						));
						options.append(` with high tech cyber enhancements that will increase attack, defense and base hp values.`);
					} else {
						options.append("The unit is equipped with advanced cybernetic enhancements.");
					}
				}
				App.UI.DOM.appendNewElement("div", unitDetail, options);

				options = new DocumentFragment();
				if (unit.medics === 0) {
					options.append(`For ${cashFormat(upgradeCost("medics", unit))} `);
					options.append(App.UI.DOM.link("attach trained medics to the unit", () => {
						cashX(upgradeCost("medics", unit), "securityExpansion");
						unit.medics++;
						App.UI.reload();
					}
					));
					options.append(" which will decrease the number of casualties suffered during battle.");
				} else {
					options.append("The unit has a medic detachment following it into battle, decreasing the number of casualties the unit suffers");
				}
				App.UI.DOM.appendNewElement("div", unitDetail, options);

				if (V.SF.Toggle && V.SF.Active >= 1) {
					options = new DocumentFragment();
					if (unit.SF === 0) {
						options.append(`For ${cashFormat(upgradeCost("SF", unit))} `);
						options.append(App.UI.DOM.link("attach Special Force advisors", () => {
							cashX(upgradeCost("SF", unit), "securityExpansion");
							unit.SF++;
							App.UI.reload();
						}
						));
						options.append(" which will slightly increase the base stats of the unit.");
					} else {
						options.append(`The unit has attached advisors from ${V.SF.Lower} that will help the squad remain tactically aware and active.`);
					}
					App.UI.DOM.appendNewElement("div", unitDetail, options);
				}
			}
			App.UI.DOM.appendNewElement("p", list, unitDetail);
		}

		return list;

		/** Creates a bulk upgrade link for the unit that is passed.
		 * @param {object} unit the unit to be checked
		 */
		function bulkUpgrade(unit) {
			unit = Array.isArray(unit) ? unit : [unit];
			let el = document.createElement("a");

			/**
			 * @param {FC.SecExp.PlayerHumanUnitData} x
			 */
			function upgradeUnit(x) {
				x.equip = 3;
				if (checkID(x.ID) !== "bots") {
					Object.assign(x, {
						maxTroops: 50, commissars: 2,
						cyber: 1, medics: 1
					});
					x.SF = (V.SF.Active >= 1 ? 1 : 0);
				} else {
					if (x.maxTroops < 80) {
						x.maxTroops = 80;
					} else if (V.SF.Toggle && V.SF.Active >= 1 && x.maxTroops < 100 && V.SecExp.edicts.SFSupportLevel >= 1) {
						x.maxTroops = 100;
					}
				}
			}

			/**
			 * @param {FC.SecExp.PlayerHumanUnitData} x
			 */
			function getCost(x) {
				let cost = 0;
				if (checkID(x.ID) !== "bots") {
					if (x.commissars < 2) {
						cost += upgradeCost("commissars", x);
					}
					if ((V.prostheticsUpgrade >= 2 || V.researchLab.advCombatPLimb === 1) && x.cyber === 0) {
						cost += upgradeCost("cyber", x);
					}
					if (x.medics === 0) {
						cost += upgradeCost("medics", x);
					}
					if (V.SF.Toggle && V.SF.Active >= 1 && x.SF === 0) {
						cost += equipUpgradeCost * x.maxTroops + 5000;
					}
				}
				cost += upgradeCost("squadSize", x);
				if (x.equip < 3) {
					cost += upgradeCost("equipment", x);
				}
				return Math.ceil(cost * 1.1);
			}

			const price = unit.map(getCost).reduce((acc, cur) => acc + cur, 0);
			if (price !== 0) {
				el.append(App.UI.DOM.link(`Bulk upgrade for ${cashFormat(price)}`, () => {
					unit.map(upgradeUnit).reduce((acc, cur) => acc + cur, 0);
					cashX(price, "securityExpansion");
					App.UI.reload();
				}
				));
			}
			return el;
		}

		/**
		 * @param {string} upgrade to perform
		 * @param {FC.SecExp.PlayerHumanUnitData} unit
		 */
		function upgradeCost(upgrade, unit) {
			let cost = 0;
			const isBots = checkID(unit.ID) === "bots";
			switch(upgrade) {
				case "squadSize":
					if (isBots) {
						if (unit.maxTroops < 80) {
							cost -= 5000;
						} else if (V.SF.Toggle && V.SF.Active >= 1 && unit.maxTroops < 100 && V.SecExp.edicts.SFSupportLevel >= 1) {
							cost -= 5000 + 10 * secBotsUpgradeCost * unit.equip;
						}
					} else {
						if (unit.maxTroops < 50) {
							cost -= 5000 + 10 * equipUpgradeCost * (unit.equip + unit.commissars + unit.cyber + unit.SF);
						}
					}
					break;
				case "equipment":
					cost -= ((isBots ? secBotsUpgradeCost : equipUpgradeCost) * unit.maxTroops) + 1000;
					break;
				case "commissars":
					cost -= equipUpgradeCost * unit.maxTroops + 1000;
					break;
				case "cyber":
					cost -= equipUpgradeCost * unit.maxTroops + 2000;
					break;
				case "medics":
					cost -= equipUpgradeCost * unit.maxTroops + 3000;
					break;
				case "SF":
					cost -= equipUpgradeCost * unit.maxTroops + 5000;
			}
			return cost;
		}
	}

	/**
	 * @returns {HTMLDivElement}
	 */
	function replenishAll() {
		let el = document.createElement("div");
		let woundedUnit = new Map([]);
		for (const squad of App.SecExp.unit.squads()) {
			const unit = checkID(squad.ID);
			if (squad.troops < squad.maxTroops && unitFree(unit).canUpgrade()) {
				woundedUnit.set(unit);
			}
		}
		if (woundedUnit.size > 0) {
			el.append(App.UI.DOM.link("Replenish all units", () => {
				for (const [u] of woundedUnit) {
					V.SecExp.units[u].squads.forEach(s => replenish(s));
				}
				App.UI.reload();
			}
			));
			el.append(` Will replenish units as long as requirements are met.\n\n`);
		}
		return el;
	}

	/**
	 * @param {FC.SecExp.PlayerHumanUnitData} unit
	 * @returns {boolean}
	 */
	function isDeployed(unit) {
		return V.SecExp.war.type.includes("Attack") && V.SecExp.war.deployed.includes(unit.ID) || V.SecExp.war.type.includes("Rebellion") && unit.active === 1 && !V.SecExp.war.rebellingID.includes(unit.ID);
	}

	/**
	 * @param {FC.SecExp.PlayerHumanUnitData} input
	 * @param {boolean} inBattle - if true appends a deploy/recall link to the description, allowing for [input] to be deployed/recalled.
	 * @returns {HTMLDivElement}
	 */
	function describe(input, inBattle) {
		const brief = V.SecExp.settings.unitDescriptions;
		const unitType = App.SecExp.unit.checkID(input.ID);
		let el = new DocumentFragment();

		if (inBattle) {
			const canDeploy = !isDeployed(input) && App.SecExp.battle.deployableUnits() > 0;
			el.append(App.UI.DOM.link(`(${canDeploy ? 'Deploy' : 'Recall'}) `, () => {
				if (canDeploy) {
					V.SecExp.war.deployed.push(input.ID);
				} else {
					V.SecExp.war.deployed.deleteAt(s => s === input.ID);
				}
				V.SecExp.war.saveValid = 0;
				App.UI.reload();
			}
			));
		}

		App.UI.DOM.appendNewElement("span", el, `${input.platoonName}`, "bold");
		App.UI.DOM.appendNewElement("span", el, `${!brief ? ``:`. `} `);
		if (unitType !== "bots") {
			if (brief === 0) {
				if (input.battlesFought > 1) {
					el.append(`has participated in ${input.battlesFought} battles and is ready to face the enemy once more at your command. `);
				} else if (input.battlesFought === 1) {
					el.append(`is ready to face the enemy once more at your command. `);
				} else {
					el.append(`is ready to face the enemy in battle. `);
				}
				el.append(`Its ${input.troops} men and women are `);

				if (unitType === "militia") {
					el.append(`all proud citizens of your arcology, willing to put their lives on the line to protect their home. `);
				} else if (unitType === "slaves") {
					el.append(`slaves in your possession, tasked with the protection of their owner and their arcology. `);
				} else if (unitType === "mercs") {
					el.append(`mercenaries contracted to defend the arcology against external threats. `);
				}
			} else {
				el.append(`Battles fought: ${input.battlesFought}. `);
			}
		} else {
			if (brief === 0) {
				el.append(`is made up of ${input.troops} drones. All of which are assembled in an ordered formation in front of you, absolutely silent and ready to receive their orders. `);
			}
		}

		if (brief === 0) {
			if (input.troops < input.maxTroops) {
				el.append(`The unit is not at its full strength of ${input.maxTroops} operatives. `);
			}
		} else {
			el.append(`Unit size: ${input.troops}/${input.maxTroops}. `);
		}

		if (brief === 0) {
			if (unitType !== "bots") {
				if (input.equip === 0) {
					el.append(`They are issued with simple, yet effective equipment: firearms, a few explosives and standard uniforms, nothing more. `);
				} else if (input.equip === 1) {
					el.append(`They are issued with good, modern equipment: firearms, explosives and a few specialized weapons like sniper rifles and machine guns. They also carry simple body armor. `);
				} else if (input.equip === 2) {
					el.append(`They are issued with excellent, high tech equipment: modern firearms, explosives, specialized weaponry and modern body armor. They are also issued with modern instruments like night vision and portable radars. `);
				} else {
					el.append(`They are equipped with the best the modern world has to offer: modern firearms, explosives, specialized weaponry, experimental railguns, adaptive body armor and high tech recon equipment. `);
				}
			} else {
				if (input.equip === 0) {
					el.append(`They are equipped with light weaponry, mainly anti-riot nonlethal weapons. Not particularly effective in battle. `);
				} else if (input.equip === 1) {
					el.append(`They are equipped with light firearms, not an overwhelming amount of firepower, but with their mobility good enough to be effective. `);
				} else if (input.equip === 2) {
					el.append(`They are equipped with powerful, modern firearms and simple armor mounted around their frames. They do not make for a pretty sight, but on the battlefield they are a dangerous weapon. `);
				} else {
					el.append(`They are equipped with high energy railguns and adaptive armor. They are a formidable force on the battlefield, even for experienced soldiers. `);
				}
			}
		} else {
			el.append(`Equipment quality: `);
			if (input.equip === 0) {
				el.append(`basic. `);
			} else if (input.equip === 1) {
				el.append(`average. `);
			} else if (input.equip === 2) {
				el.append(`high. `);
			} else {
				el.append(`advanced. `);
			}
		}

		if (unitType !== "bots") {
			if (brief === 0) {
				if (input.training <= 33) {
					el.append(`They lack the experience to be considered professionals, but `);
					if (unitType === "militia") {
						el.append(`their eagerness to defend the arcology makes up for it. `);
					} else if (unitType === "slaves") {
						el.append(`their eagerness to prove themselves makes up for it. `);
					} else if (unitType === "mercs") {
						el.append(`they're trained more than enough to still be an effective unit. `);
					}
				} else if (input.training <= 66) {
					el.append(`They have trained `);
					if (input.battlesFought > 0) {
						el.append(`and fought `);
					}
					el.append(`enough to be considered disciplined, professional soldiers, ready to face the battlefield. `);
				} else {
					el.append(`They are consummate veterans, with a wealth of experience and perfectly trained. On the battlefield they are a well oiled war machine capable of facing pretty much anything. `);
				}

				if (input.loyalty < 10) {
					el.append(`The unit is extremely disloyal. Careful monitoring of their activities and relationships should be implemented. `);
				} else if (input.loyalty < 33) {
					el.append(`Their loyalty is low. Careful monitoring of their activities and relationships is advised. `);
				} else if (input.loyalty < 66) {
					el.append(`Their loyalty is not as high as it can be, but they are not actively working against their arcology owner. `);
				} else if (input.loyalty < 90) {
					el.append(`Their loyalty is high and strong. The likelihood of this unit betraying the arcology is low to non-existent. `);
				} else {
					el.append(`The unit is fanatically loyal. They would prefer death over betrayal. `);
				}

				if (input.cyber > 0) {
					el.append(`The soldiers of the unit have been enhanced with numerous cyberaugmentations which greatly increase their raw power. `);
				}
				if (input.medics > 0) {
					el.append(`The unit has a dedicated squad of medics that will follow them in battle. `);
				}
				if (V.SF.Toggle && V.SF.Active >= 1 && input.SF > 0) {
					el.append(`The unit has "advisors" from ${V.SF.Lower} that will help the squad remain tactically aware and active. `);
				}
			} else {
				el.append(`Training: `);
				if (input.training <= 33) {
					el.append(`low. `);
				} else if(input.training <= 66) {
					el.append(`medium. `);
				} else {
					el.append(`high. `);
				}

				el.append(`Loyalty: `);
				if (input.loyalty < 10) {
					el.append(`extremely disloyal. `);
				} else if (input.loyalty < 33) {
					el.append(`low. `);
				} else if (input.loyalty < 66) {
					el.append(`medium. `);
				} else if (input.loyalty < 90) {
					el.append(`high. `);
				} else {
					el.append(`fanatical. `);
				}
				App.UI.DOM.appendNewElement("div", el);

				if (jsDef(input.cyber) && input.cyber > 0) {
					el.append(`Cyberaugmentations applied. `);
				}
				if (jsDef(input.medics) && input.medics > 0) {
					el.append(`Medical squad attached. `);
				}
				if (V.SF.Toggle && V.SF.Active >= 1 && jsDef(input.SF) && input.SF > 0) {
					el.append(`${capFirstChar(V.SF.Lower || "the special force")} "advisors" attached. `);
				}
			}
		}

		if (!input.active) {
			App.UI.DOM.appendNewElement("div", el, `This unit has lost too many operatives `);
			if (jsDef(input.battlesFought)) {
				el.append(`in the ${input.battlesFought} it fought `);
			}
			el.append(`and can no longer be considered a unit at all. `);
		}
		return el;
	}

	/** Generates an array of units
	 * @returns {Array}
	 */
	function squads(type = '') {
		let array = Object.values(V.SecExp.units).map(s => s.squads).flatten();
		switch(type) {
			case "human": return array.filter(s => checkID(s.ID) !== "bots");
		}
		return array;
	}

	/** Generate a unit ID for a new unit
	 * @param {FC.SecExp.PlayerHumanUnitData} squad
	 * @param {FC.SecExp.PlayerHumanUnitTypeMod} unit
	 * @returns {void}
	 */
	function genID(squad, unit) {
		if (V.SecExp.units[unit].squads.filter(s => !s.ID.isBetween(list().get(unit).baseID, list().get(unit).maxID, true)).length) {
			const oldID = squad.ID;
			let location;
			const newID = list().get(unit).baseID + (unit === 'bots' ? 0 : oldID);
			if (V.SecExp.battles.lastSelection && V.SecExp.battles.lastSelection.includes(oldID) || V.lastSelection && V.lastSelection.includes(oldID)) {
				if (V.SecExp.battles.lastSelection) {
					location = V.SecExp.battles.lastSelection.indexOf(oldID);
				} else {
					location = V.lastSelection.indexOf(oldID);
					V.SecExp.battles.lastSelection = [];
				}
				V.SecExp.battles.lastSelection[location] = newID;
			}
			squad.ID = newID;
		} else if (!jsDef(squad.ID)) {
			if (V.SecExp.units[unit].squads.length === 0) {
				squad.ID = list().get(unit).baseID;
			} else {
				squad.ID = Math.max(
					V.SecExp.units[unit].squads.map(u => u.ID).reduce((acc, cur) => Math.max(acc, cur), 0)
				) + 1;
			}
		}
	}

	/** performs operations on a unit
	 * helper function, not callable externally
	 * @param {FC.SecExp.PlayerHumanUnitTypeMod} type
	 * @returns {object} containing functions
	 */
	function unitFree(type) {
		/**
		 * @returns {number}
		 */
		function print() {
			switch(type) {
				case "slaves": return V.menials;
				case "militia":
				case "mercs":
					return V.SecExp.units[type].free;
			}
		}

		/**
		 * @returns {boolean}
		 */
		function canUpgrade() {
			switch(type) {
				case "bots": return V.cash >= secBotsCost;
				case "slaves": return V.menials > 0;
				case "militia":
				case "mercs":
					return V.SecExp.units[type].free > 0;
			}
		}

		/**
		 * @param {number} value
		 * @returns {void}
		 */
		function add(value) {
			switch(type) {
				case "slaves": V.menials += value; break;
				case "militia":
				case "mercs":
					V.SecExp.units[type].free += value;
			}
		}

		/**
		 * @param {number} value
		 * @returns {void}
		 */
		function remove(value) {
			switch(type) {
				case "slaves": V.menials -= value; break;
				case "militia":
				case "mercs":
					V.SecExp.units[type].free -= value;
			}
		}

		/**
		 * @param {number} value
		 * @returns {void}
		 */
		function set(value) {
			switch(type) {
				case "slaves": V.menials = value; break;
				case "militia":
				case "mercs":
					V.SecExp.units[type].free = value;
			}
		}

		return {
			print, canUpgrade,
			add, remove, set
		};
	}

	/** Replenishes a unit if needed
	 * helper function, not callable externally
	 * @param {FC.SecExp.PlayerHumanUnitData} squad
	 */
	function replenish(squad) {
		const oldTroops = squad.troops;
		const type = checkID(squad.ID);

		if (type !== "bots") {
			if (unitFree(type).print() >= squad.maxTroops - squad.troops) {
				unitFree(type).remove(squad.maxTroops - squad.troops);
				squad.troops = squad.maxTroops;
			} else {
				squad.troops += unitFree(type).print();
				unitFree(type).set(0);
			}
			const expLoss = (squad.troops - oldTroops) / squad.troops;
			squad.training -= squad.training * expLoss;
		} else {
			cashX(-((squad.maxTroops - squad.troops) * secBotsCost), "securityExpansion");
			squad.troops = squad.maxTroops;
		}
	}
})();

/** Player unit factory - get a unit's data based on its type and the individual unit passed
 * @param {FC.SecExp.PlayerHumanUnitType} type - "bots", "militia", "slaves", "mercs", or "SF"
 * @param {FC.SecExp.PlayerHumanUnitData} [unit]
 * @returns {App.SecExp.Unit}
 */
App.SecExp.getUnit = function(type, unit=null) {
	switch (type) {
		case "SF":
			return new App.SecExp.SFUnit();
		case "bots":
			return new App.SecExp.DroneUnit(unit, App.SecExp.BaseDroneUnit);
		case "militia":
			return new App.SecExp.HumanUnit(unit, App.SecExp.BaseMilitiaUnit);
		case "slaves":
			return new App.SecExp.HumanUnit(unit, App.SecExp.BaseSlaveUnit);
		case "mercs":
			return new App.SecExp.HumanUnit(unit, App.SecExp.BaseMercUnit);
		default:
			throw Error(`Unknown unit type: ${type}`);
	}
};

/** Enemy unit factory - get a unit based on its type and basic data
 * @param {FC.SecExp.EnemyUnitType} type - "raiders", "free city", "old world", or "freedom fighters"
 * @param {number} troops
 * @param {number} equipment
 * @returns {App.SecExp.Unit}
 */
App.SecExp.getEnemyUnit = function(type, troops, equipment) {
	const baseUnitMap = new Map([
		["raiders", App.SecExp.BaseRaiderUnit],
		["free city", App.SecExp.BaseFreeCityUnit],
		["old world", App.SecExp.BaseOldWorldUnit],
		["freedom fighters", App.SecExp.BaseFreedomFighterUnit],
	]);
	const unitData = {
		troops: troops,
		maxTroops: troops,
		equip: equipment
	};
	return new App.SecExp.EnemyUnit(unitData, baseUnitMap.get(type));
};

/** Irregular unit factory - get an irregular unit (without organization/upgrade bonuses) based on its type and basic data
 * @param {FC.SecExp.PlayerHumanUnitTypeModHuman} type - "militia", "slaves", or "mercs"
 * @param {number} troops
 * @param {number} equipment
 * @returns {App.SecExp.Unit}
 */
App.SecExp.getIrregularUnit = function(type, troops, equipment) {
	const baseUnitMap = new Map([
		["militia", App.SecExp.BaseMilitiaUnit],
		["slaves", App.SecExp.BaseSlaveUnit],
		["mercs", App.SecExp.BaseMercUnit],
	]);
	const unitData = {
		troops: troops,
		maxTroops: troops,
		equip: equipment
	};

	return new App.SecExp.IrregularUnit(unitData, baseUnitMap.get(type));
};

/** Equipment multiplier (static balance variable) */
App.SecExp.equipMod = 0.15;

/** Turn a loyalty value into a corresponding bonus factor
 * @param {number} value range: [0-100]
 * @returns {number} bonus - range: [0.0-0.3], cap at input 67
 */
App.SecExp.loyaltyValueToBonusFactor = function(value) {
	return Math.min(value * 3 / 670, 0.3);
};

/** Turn a training value into a corresponding bonus factor
 * @param {number} value range: [0-100]
 * @returns {number} bonus - range: [0.0-0.5], cap at input 67
 */
App.SecExp.trainingValueToBonusFactor = function(value) {
	return Math.min(value * 3 / 400, 0.5);
};

/** Gets the bonus values provided for completing weapon manufacturing upgrades.
 * @param {string} type - unit type to check.
 * @returns {object} bonus values after checking for completed upgrades.
 */
App.SecExp.getAppliedUpgrades = function(type) {
	let hp = 0; let morale = 0; let def = 0; let attack = 0;
	if (V.SecExp.buildings.weapManu) {
		if (type === 'drone') {
			if (V.SecExp.buildings.weapManu.upgrades.completed.includes(-3)) {
				hp++;
			}
			if (V.SecExp.buildings.weapManu.upgrades.completed.includes(-2)) {
				def++;
			}
			if (V.SecExp.buildings.weapManu.upgrades.completed.includes(-1)) {
				attack++;
			}
		} else if (type === 'human') {
			if (V.SecExp.buildings.weapManu.upgrades.completed.includes(0)) {
				attack++;
			}
			if (V.SecExp.buildings.weapManu.upgrades.completed.includes(1)) {
				def++;
			}
			if (V.SecExp.buildings.weapManu.upgrades.completed.includes(2)) {
				hp++;
			}
			if (V.SecExp.buildings.weapManu.upgrades.completed.includes(3)) {
				morale += 10;
			}
			if (V.SecExp.buildings.weapManu.upgrades.completed.includes(4)) {
				attack++; def++;
			}
			if (V.SecExp.buildings.weapManu.upgrades.completed.includes(5)) {
				hp++; morale += 10;
			}
			if (V.SecExp.buildings.weapManu.upgrades.completed.includes(6)) {
				attack++; def++;
			}
			if (V.SecExp.buildings.weapManu.upgrades.completed.includes(7)) {
				hp++; morale += 10;
			}
			if (V.SecExp.buildings.weapManu.upgrades.completed.includes(8)) {
				attack++; def++; hp++; morale += 10;
			}
			if (V.arcologies[0].FSNeoImperialistLaw1) {
				attack++;
			}
		}
	}
	return {
		attack: attack, defense: def, hp: hp, morale: morale
	};
};

App.SecExp.getEdictUpgradeVal = (function() {
	const data = {
		militia: new Map([
			["legionTradition", {defense: 2, morale: 5, hp: 1}],
			["imperialTradition", {defense: 1, morale: 5, hp: 1}],
			["pharaonTradition", {attack: 2, defense: 2, morale: 10}],
			["sunTzu", {attack: 1, defense: 1, morale: 5}],
			["eliteOfficers", {morale: 5}],
			["lowerRequirements", {defense: -1, hp: -1}]
		]),
		slave: new Map([
			["mamluks", {attack: 2, morale: 10, hp: 1}],
			["sunTzu", {attack: 1, defense: 1, morale: 5}],
			["eliteOfficers", {morale: -5}],
			["martialSchool", {morale: 5}]
		]),
		merc: new Map([
			["eagleWarriors", {attack: 4, defense: -2, morale: 10}],
			["ronin", {attack: 2, defense: 2, morale: 10}],
			["sunTzu", {attack: 1, defense: 1, morale: 5}],
			["imperialTradition", {attack: 1, defense: 2, morale: 5}],
		])
	};

	/** Get the total edict upgrade effect on a particular stat for a particular unit
	 * @param {string} unitType
	 * @param {string} stat
	 * @returns {number}
	 */
	function getNetEffect(unitType, stat) {
		let effect = 0;
		for (const [key, val] of data[unitType]) {
			if (V.SecExp.edicts.defense[key] > 0 && val[stat]) {
				effect += val[stat];
			}
		}
		return effect;
	}

	return getNetEffect;
})();

/**
 * @interface
 * @typedef {object} BaseUnit
 * @property {number} attack
 * @property {number} defense
 * @property {number} morale
 * @property {number} hp
 */

/** @implements {BaseUnit} */
App.SecExp.BaseMilitiaUnit = class BaseMilitiaUnit {
	static get attack() {
		return 7 + App.SecExp.getAppliedUpgrades('human').attack + App.SecExp.getEdictUpgradeVal("militia", "attack");
	}

	static get defense() {
		return 5 + App.SecExp.getAppliedUpgrades('human').defense + App.SecExp.getEdictUpgradeVal("militia", "defense");
	}

	static get morale() {
		return 140 + App.SecExp.getAppliedUpgrades('human').morale + App.SecExp.getEdictUpgradeVal("militia", "morale");
	}

	static get hp() {
		return 3 + App.SecExp.getAppliedUpgrades('human').hp + App.SecExp.getEdictUpgradeVal("militia", "hp");
	}
};

/** @implements {BaseUnit} */
App.SecExp.BaseSlaveUnit = class BaseSlaveUnit {
	static get attack() {
		return 8 + App.SecExp.getAppliedUpgrades('human').attack + App.SecExp.getEdictUpgradeVal("slave", "attack");
	}

	static get defense() {
		return 3 + App.SecExp.getAppliedUpgrades('human').defense + App.SecExp.getEdictUpgradeVal("slave", "defense");
	}

	static get morale() {
		return 110 + App.SecExp.getAppliedUpgrades('human').morale + App.SecExp.getEdictUpgradeVal("slave", "morale");
	}

	static get hp() {
		return 3 + App.SecExp.getAppliedUpgrades('human').hp + App.SecExp.getEdictUpgradeVal("slave", "hp");
	}
};

/** @implements {BaseUnit} */
App.SecExp.BaseMercUnit = class BaseMercUnit {
	static get attack() {
		return 8 + App.SecExp.getAppliedUpgrades('human').attack + App.SecExp.getEdictUpgradeVal("merc", "attack");
	}

	static get defense() {
		return 4 + App.SecExp.getAppliedUpgrades('human').defense + App.SecExp.getEdictUpgradeVal("merc", "defense");
	}

	static get morale() {
		return 125 + App.SecExp.getAppliedUpgrades('human').morale + App.SecExp.getEdictUpgradeVal("merc", "morale");
	}

	static get hp() {
		return 4 + App.SecExp.getAppliedUpgrades('human').hp + App.SecExp.getEdictUpgradeVal("merc", "hp");
	}
};

/** @implements {BaseUnit} */
App.SecExp.BaseDroneUnit = class BaseDroneUnit {
	static get attack() {
		return 7 + App.SecExp.getAppliedUpgrades('drone').attack;
	}

	static get defense() {
		return 3 + App.SecExp.getAppliedUpgrades('drone').defense;
	}

	static get morale() {
		return 200;
	}

	static get hp() {
		return 3 + App.SecExp.getAppliedUpgrades('drone').hp;
	}
};

/** @implements {BaseUnit} */
App.SecExp.BaseRaiderUnit = class BaseRaiderUnit {
	static get attack() {
		return 7 + (V.SecExp.buildings.weapManu ? V.SecExp.buildings.weapManu.sellTo.raiders : 0);
	}

	static get defense() {
		return 2 + (V.SecExp.buildings.weapManu ? V.SecExp.buildings.weapManu.sellTo.raiders : 0);
	}

	static get morale() {
		return 100;
	}

	static get hp() {
		return 2;
	}
};

/** @implements {BaseUnit} */
App.SecExp.BaseFreeCityUnit = class BaseFreeCityUnit {
	static get attack() {
		return 6 + (V.SecExp.buildings.weapManu ? V.SecExp.buildings.weapManu.sellTo.FC : 0);
	}

	static get defense() {
		return 4 + (V.SecExp.buildings.weapManu ? V.SecExp.buildings.weapManu.sellTo.FC : 0);
	}

	static get morale() {
		return 130;
	}

	static get hp() {
		return 3;
	}
};

/** @implements {BaseUnit} */
App.SecExp.BaseOldWorldUnit = class BaseOldWorldUnit {
	static get attack() {
		return 8 + (V.SecExp.buildings.weapManu ? V.SecExp.buildings.weapManu.sellTo.oldWorld : 0);
	}

	static get defense() {
		return 4 + (V.SecExp.buildings.weapManu ? V.SecExp.buildings.weapManu.sellTo.oldWorld : 0);
	}

	static get morale() {
		return 110;
	}

	static get hp() {
		return 2;
	}
};

/** @implements {BaseUnit} */
App.SecExp.BaseFreedomFighterUnit = class BaseFreedomFighterUnit {
	static get attack() {
		return 9 + (V.SecExp.buildings.weapManu ? V.SecExp.buildings.weapManu.sellTo.oldWorld : 0);
	}

	static get defense() {
		return 2 + (V.SecExp.buildings.weapManu ? V.SecExp.buildings.weapManu.sellTo.oldWorld : 0);
	}

	static get morale() {
		return 160;
	}

	static get hp() {
		return 2;
	}
};

/** @implements {BaseUnit} */
App.SecExp.BaseSpecialForcesUnit = class BaseSpecialForcesUnit {
	static get attack() {
		return 8 + App.SecExp.getAppliedUpgrades('human').attack;
	}

	static get defense() {
		return 4 + App.SecExp.getAppliedUpgrades('human').defense;
	}

	static get morale() {
		return 140 + App.SecExp.getAppliedUpgrades('human').morale;
	}

	static get hp() {
		return 4 + App.SecExp.getAppliedUpgrades('human').hp;
	}
};

/** Unit base class */
App.SecExp.Unit = class SecExpUnit {
	/** @param {FC.SecExp.UnitData} data
	 * @param {BaseUnit} baseUnit
	 */
	constructor(data, baseUnit) {
		this._data = data;
		this._baseUnit = baseUnit;
	}

	/** @abstract
	 * @returns {number} */
	get attack() {
		throw Error("derive me");
	}

	/** @abstract
	 * @returns {number} */
	get defense() {
		throw Error("derive me");
	}

	/** @abstract
	 * @returns {number} */
	get morale() {
		return this._baseUnit.morale; // no morale modifiers
	}

	/** @abstract
	 * @returns {number} */
	get hp() {
		throw Error("derive me");
	}

	/** @abstract
	 * @returns {DocumentFragment} */
	printStats() {
		throw Error("derive me");
	}
};

App.SecExp.DroneUnit = class SecExpDroneUnit extends App.SecExp.Unit {
	/** @param {FC.SecExp.PlayerUnitData} data
	 * @param {BaseUnit} baseUnit
	 */
	constructor(data, baseUnit) {
		super(data, baseUnit);
		this._data = data; // duplicate assignment, just for TypeScript
	}

	get attack() {
		const equipmentFactor = this._data.equip * App.SecExp.equipMod;
		return this._baseUnit.attack * (1 + equipmentFactor);
	}

	get defense() {
		const equipmentFactor = this._data.equip * App.SecExp.equipMod;
		return this._baseUnit.defense * (1 + equipmentFactor);
	}

	get hp() {
		return this._baseUnit.hp * this._data.troops;
	}

	printStats() {
		let r = new DocumentFragment();
		App.UI.DOM.appendNewElement("div", r, `Security drones base attack: ${this._baseUnit.attack} (After modifiers: ${Math.trunc(this.attack)})`);
		App.UI.DOM.appendNewElement("div", r, `Security drones base defense: ${this._baseUnit.defense} (After modifiers: ${Math.trunc(this.defense)})`);
		App.UI.DOM.appendNewElement("div", r, `Equipment bonus: +${this._data.equip * 15}%`);
		App.UI.DOM.appendNewElement("div", r, `Security drones base hp: ${this._baseUnit.hp} (Total after modifiers for ${this._data.troops} drones: ${this.hp})`);
		App.UI.DOM.appendNewElement("div", r, `Security drones base morale: ${this._baseUnit.morale}`);
		return r;
	}
};

App.SecExp.HumanUnit = class SecExpHumanUnit extends App.SecExp.Unit {
	/** @param {FC.SecExp.PlayerHumanUnitData} data
	 * @param {BaseUnit} baseUnit
	 */
	constructor(data, baseUnit) {
		super(data, baseUnit);
		this._data = data; // duplicate assignment, just for TypeScript
	}

	get attack() {
		const equipmentFactor = this._data.equip * App.SecExp.equipMod;
		const experienceFactor = App.SecExp.trainingValueToBonusFactor(this._data.training);
		const loyaltyFactor = App.SecExp.loyaltyValueToBonusFactor(this._data.loyalty);
		const SFFactor = 0.20 * this._data.SF;
		return this._baseUnit.attack * (1 + equipmentFactor + experienceFactor + loyaltyFactor + SFFactor) + this._data.cyber;
	}

	get defense() {
		const equipmentFactor = this._data.equip * App.SecExp.equipMod;
		const experienceFactor = App.SecExp.trainingValueToBonusFactor(this._data.training);
		const loyaltyFactor = App.SecExp.loyaltyValueToBonusFactor(this._data.loyalty);
		const SFFactor = 0.20 * this._data.SF;
		return this._baseUnit.defense * (1 + equipmentFactor + experienceFactor + loyaltyFactor + SFFactor) + this._data.cyber;
	}

	get hp() {
		const medicFactor = 0.25 * this._data.medics;
		const singleTroopHp = this._baseUnit.hp * (1 + medicFactor) + this._data.cyber;
		return singleTroopHp * this._data.troops;
	}

	printStats() {
		let r = new DocumentFragment();
		this._descType = App.SecExp.unit.checkID(this._data.ID);
		App.UI.DOM.appendNewElement("div", r, `${this._descType} base attack: ${this._baseUnit.attack} (After modifiers: ${Math.trunc(this.attack)})`);
		App.UI.DOM.appendNewElement("div", r, `${this._descType} base defense: ${this._baseUnit.defense} (After modifiers: ${Math.trunc(this.defense)})`);
		if (this._data.equip > 0) {
			App.UI.DOM.appendNewElement("div", r, `Equipment bonus: +${this._data.equip * 15}%`);
		}
		if (this._data.cyber > 0) {
			App.UI.DOM.appendNewElement("div", r, `Cyber enhancements bonus: +1`);
		}
		if (this._data.training > 0) {
			App.UI.DOM.appendNewElement("div", r, `Experience bonus: +${Math.trunc(App.SecExp.trainingValueToBonusFactor(this._data.training)*100)}%`);
		}
		if (this._data.loyalty > 0) {
			App.UI.DOM.appendNewElement("div", r, `Loyalty bonus: +${Math.trunc(App.SecExp.loyaltyValueToBonusFactor(this._data.loyalty)*100)}%`);
		}
		if (this._data.SF > 0) {
			App.UI.DOM.appendNewElement("div", r, `Special Force advisors bonus: +20%`);
		}
		App.UI.DOM.appendNewElement("div", r, `${this._descType} base morale: ${this._baseUnit.morale} (After modifiers: ${this.morale})`);
		if (jsDef(V.SecExp.buildings.barracks) && V.SecExp.buildings.barracks.luxury > 0) {
			App.UI.DOM.appendNewElement("div", r, `Barracks bonus: +${V.SecExp.buildings.barracks.luxury * 5}%`);
		}
		App.UI.DOM.appendNewElement("div", r, `${this._descType} base hp: ${this._baseUnit.hp} (Total after modifiers for ${this._data.troops} troops: ${this.hp})`);
		if (this._data.medics > 0) {
			App.UI.DOM.appendNewElement("div", r, `Medics detachment bonus: +25%`);
		}
		return r;
	}
};

App.SecExp.troopsFromSF = function() {
	if (V.SecExp.war.type.includes("Attack")) {
		const transportMax = Math.trunc(125 * (V.SF.Squad.GunS + (V.terrain !== "oceanic" ? ((V.SF.Squad.AV + V.SF.Squad.TV)/2) : 0)));
		return Math.min(transportMax, V.SF.ArmySize);
	} else {
		return V.SF.ArmySize; // rebellion: transport capabilities are irrelevant
	}
};

App.SecExp.SFUnit = class SFUnit extends App.SecExp.Unit {
	constructor() {
		super(null, App.SecExp.BaseSpecialForcesUnit);
		this._distancePenalty = (V.SecExp.war.type.includes("Attack")) ? 0.10 : 0.0;
	}

	get attack() {
		// ignores base attack? weird.
		const attackUpgrades = V.SF.Squad.Armoury + V.SF.Squad.Drugs + V.SF.Squad.AA + (V.terrain !== "oceanic" ? V.SF.Squad.AV : 0);
		return (0.75 - this._distancePenalty) * attackUpgrades;
	}

	get defense() {
		// ignores base defense? weird.
		const defenseUpgrades = V.SF.Squad.Armoury + V.SF.Squad.Drugs + (V.SF.Squad.AA + V.SF.Squad.TA) / 2 + (V.terrain !== "oceanic" ? (V.SF.Squad.AV + V.SF.Squad.TV) / 2 : 0);
		return (0.5 - this._distancePenalty) * defenseUpgrades;
	}

	get hp() {
		return this._baseUnit.hp * App.SecExp.troopsFromSF();
	}
};

App.SecExp.EnemyUnit = class SecExpEnemyUnit extends App.SecExp.Unit {
	/** @param {FC.SecExp.UnitData} data
	 * @param {BaseUnit} baseUnit
	 */
	constructor(data, baseUnit) {
		super(data, baseUnit);
	}

	get attack() {
		const equipmentFactor = this._data.equip * App.SecExp.equipMod;
		return this._baseUnit.attack * (1 + equipmentFactor);
	}

	get defense() {
		const equipmentFactor = this._data.equip * App.SecExp.equipMod;
		return this._baseUnit.defense * (1 + equipmentFactor);
	}

	get hp() {
		return this._baseUnit.hp * this._data.troops;
	}
};

App.SecExp.IrregularUnit = class SecExpEnemyUnit extends App.SecExp.Unit {
	/** @param {FC.SecExp.UnitData} data
	 * @param {BaseUnit} baseUnit
	 */
	constructor(data, baseUnit) {
		super(data, baseUnit);
	}

	get attack() {
		const equipmentFactor = this._data.equip * App.SecExp.equipMod;
		return (this._baseUnit.attack - App.SecExp.getAppliedUpgrades('human').attack) * (1 + equipmentFactor);
	}

	get defense() {
		const equipmentFactor = this._data.equip * App.SecExp.equipMod;
		return (this._baseUnit.defense - App.SecExp.getAppliedUpgrades('human').defense) * (1 + equipmentFactor);
	}

	get hp() {
		return (this._baseUnit.hp - App.SecExp.getAppliedUpgrades('human').hp) * this._data.troops;
	}
};

App.SecExp.mercenaryAvgLoyalty = function() {
	return _.mean(V.SecExp.units.mercs.squads.filter((u) => u.active === 1).map((u) => u.loyalty));
};

App.SecExp.Manpower = {
	get totalMilitia() {
		return this.employedMilitia + this.freeMilitia;
	},

	get employedMilitia() {
		return V.SecExp.units.militia.squads.reduce((acc, cur) => acc + cur.troops, 0);
	},

	get freeMilitia() {
		return V.SecExp.units.militia.free;
	},

	get employedSlave() {
		return V.SecExp.units.slaves.squads.reduce((acc, cur) => acc + cur.troops, 0);
	},

	get totalMerc() {
		return this.employedMerc + this.freeMerc;
	},

	get employedMerc() {
		return V.SecExp.units.mercs.squads.reduce((acc, cur) => acc + cur.troops, 0);
	},

	get freeMerc() {
		return V.SecExp.units.mercs.free;
	},
};

repo_name: Banyanael/fc

license: mit


code:
App.SecExp.authorityReport = function() {
	let authGrowth = 0;
	let r = V.useTabs === 0 ? [`<h2>Authority</h2>`] : [];
	r.push(`<br>Your authority is`);

	if (V.SecExp.core.authority > 19500) {
		r.push(`nearly absolute. The arcology is yours to command as it pleases you.`);
	} else if (V.SecExp.core.authority > 15000) {
		r.push(`extremely high. There's little you cannot do within the walls of your arcology.`);
	} else if (V.SecExp.core.authority > 10000) {
		r.push(`high. You command respect and fear in equal measure.`);
	} else if (V.SecExp.core.authority > 5000) {
		r.push(`moderate. You command some respect from your citizens.`);
	} else {
		r.push(`low. You command no respect or fear from your citizens.`);
	}

	if (V.PC.career === "wealth") {
		r.push(`As part of the idle rich, you were used to having obedience coming naturally to you. Now you find it harder to maintain authority over the arcology.`);
	} else if (V.PC.career === "slaver") {
		r.push(`Your past as a slaver helps you assert your authority over the arcology.`);
	} else if (V.PC.career === "escort") {
		r.push(`Given your past career as an escort, you find it hard to assert your authority over the arcology and its inhabitants.`);
	} else if (V.PC.career === "servant") {
		r.push(`Given your past career as a servant, you find it hard to assert your authority over the arcology and its inhabitants.`);
	} else if (V.PC.career === "BlackHat") {
		r.push(`Given your past career as a (rather questionable) incursion specialist, you find it hard to assert your authority over the arcology and its inhabitants, despite what you may know about them.`);
	} else if (V.PC.career === "gang") {
		r.push(`Given your past life as a gang leader, you find it easier to assert your authority over the arcology and its inhabitants.`);
	}

	if (["BlackHat", "escort", "servant", "wealth"].includes(V.PC.career)) {
		authGrowth -= (10 * random(5, 15));
	} else if (["slaver", "gang"].includes(V.PC.career)){
		authGrowth += (10 * random(5, 15));
	}

	if (V.rep >= 19500) {
		r.push(`Your legend is so well known that your mere presence commands respect and obedience, increasing your authority.`);
		authGrowth += (10 * random(10, 20));
	} else if (V.rep >= 15000) {
		r.push(`Your authority is so high that your mere presence commands respect, increasing your authority.`);
		authGrowth += (10 * random(5, 15));
	} else if (V.rep >= 10000) {
		r.push(`Your authority is high enough that your presence commands some respect, increasing your authority.`);
		authGrowth += (10 * random(2, 8));
	}

	if (V.SecExp.core.security >= 90) {
		r.push(`Your arcology is incredibly secure and your citizens know quite well who to thank, greatly increasing your authority.`);
		authGrowth += (10 * random(10, 20));
	} else if (V.SecExp.core.security >= 70) {
		r.push(`Your arcology is really secure and your citizens know quite well who to thank, increasing your authority.`);
		authGrowth += (10 * random(5, 15));
	} else if (V.SecExp.core.security >= 50) {
		r.push(`Your arcology is quite secure and your citizens know who to thank, increasing your authority.`);
		authGrowth += (10 * random(2, 8));
	}

	if (V.SecExp.core.crimeLow >= 90) {
		r.push(`The all powerful criminal organizations controlling the arcology have a very easy time undermining your authority.`);
		authGrowth -= (10 * random(10, 20));
	} else if (V.SecExp.core.crimeLow >= 70) {
		r.push(`Crime is king in the arcology, powerful criminals have a very easy time undermining your authority.`);
		authGrowth -= (10 * random(5, 15));
	} else if (V.SecExp.core.crimeLow >= 50) {
		r.push(`Criminal organizations have a strong foothold in the arcology, their activities undermine your authority.`);
		authGrowth -= (10 * random(2, 8));
	}

	if (V.averageDevotion >= 50 && V.averageTrust >= 50) {
		r.push(`The high devotion and trust of your slaves speak eloquently of your leadership capabilities, helping your authority grow.`);
		authGrowth += (5 * ((V.averageDevotion + V.averageTrust) / 10));
	} else if (V.averageDevotion >= 50) {
		r.push(`The high devotion of your slaves speaks eloquently of your leadership capabilities, helping your authority grow.`);
		authGrowth += (5 * (V.averageDevotion / 10));
	} else if (V.averageTrust >= 50) {
		r.push(`The high trust of your slaves speaks eloquently of your leadership capabilities, helping your authority grow.`);
		authGrowth += (5 * (V.averageTrust / 10));
	}

	if (V.arcologies[0].ownership >= 90) {
		r.push(`You own so much of the arcology that your authority quickly increases.`);
	} else if (V.arcologies[0].ownership >= 70) {
		r.push(`You own a big part of the arcology, causing your authority to increase.`);
	} else if (V.arcologies[0].ownership >= 50) {
		r.push(`You own the majority of the arcology, causing your authority to slowly increase.`);
	} else {
		r.push(`Your low ownership of the arcology causes your authority to decrease.`);
	}

	if (V.arcologies[0].ownership >= 50) {
		authGrowth += (5 * Math.trunc(V.arcologies[0].ownership / 10));
	} else {
		authGrowth -= (5 * Math.trunc(V.arcologies[0].ownership / 10));
	}

	const activeUnits = App.SecExp.battle.activeUnits();
	if (activeUnits >= 4) {
		r.push(`Your military is`);
		if (activeUnits >= 9) {
			r.push(`massive; commanding so many troops greatly`);
		} else if (activeUnits >= 7) {
			r.push(`huge; commanding such a number of soldiers`);
		} else if (activeUnits >= 4) {
			r.push(`at a decent size; commanding a small army`);
		}
		r.push(`increases your authority.`);
		authGrowth += (12 * activeUnits);
	}

	const SF = App.SecExp.assistanceSF('authority');
	r.push(SF.text); authGrowth += SF.bonus;

	const FSChattel = V.arcologies[0].FSChattelReligionist;
	if (FSChattel >= 50) {
		r.push(`Religious organizations have a tight grip on the minds of your residents and their dogma ${FSChattel >= 90 ? 'greatly' : ''} helps your authority grow.`);
		authGrowth += FSChattel;
	}

	const FSRestart = V.arcologies[0].FSRestart;
	if (FSRestart >= 50) {
		r.push(`The arcology's society is ${FSRestart >= 90 ? 'extremely' : 'very'} stratified. The reliance on the ${V.arcologies[0].FSNeoImperialistLaw2 === 1 ? 'Barons' : 'Societal Elite'} by the lower classes increases ${FSRestart >= 90 ? 'greatly' : ''} your authority.`);
		authGrowth += FSRestart;
	}

	const FSPaternalist = V.arcologies[0].FSPaternalist;
	if (FSPaternalist >= 50) {
		r.push(`Your ${FSPaternalist >= 90 ? 'extremely' : ''} paternalistic society has the unfortunate side effects of spreading dangerous ideals in the arcology, ${FSPaternalist >= 90 ? 'greatly' : ''} damaging your authority.`);
		authGrowth -= Math.clamp(FSPaternalist, 0, 100);
	}

	const FSNull = V.arcologies[0].FSNull;
	if (FSNull >= 50) {
		r.push(`${FSNull >= 90 ? 'Extreme' : 'Mild'} cultural openness allows dangerous ideas to spread in your arcology, ${FSNull >= 90 ? 'greatly' : ''} damaging your authority.`);
		authGrowth -= FSNull;
	}

	if (V.SecExp.buildings.propHub) {
		if (V.SecExp.buildings.propHub.upgrades.miniTruth >= 1) {
			r.push(`Your authenticity department works tirelessly to impose your authority in all of the arcology.`);
			authGrowth += (15 * V.SecExp.buildings.propHub.upgrades.miniTruth);
		}
		if (V.SecExp.buildings.propHub.upgrades.secretService >= 1) {
			r.push(`Your secret services constantly keep under surveillance any potential threat, intervening when necessary. Rumors of the secretive security service and mysterious disappearances make your authority increase.`);
			authGrowth += (15 * V.SecExp.buildings.propHub.upgrades.secretService);
		}
	}

	if (App.SecExp.upkeep.edictsAuth() > 0) {
		r.push(`Some of your authority is spent maintaining your edicts.`);
		authGrowth -= App.SecExp.upkeep.edictsAuth();
	}

	if (authGrowth > 0) {
		r.push(`This week <span class="green">authority has increased.</span>`);
	} else if (authGrowth === 0) {
		r.push(`This week <span class="yellow">authority did not change.</span>`);
	} else {
		r.push(`This week <span class="red">authority has decreased.</span>`);
	}
	App.SecExp.authorityX(authGrowth);

	if (V.SecExp.settings.rebellion.enabled === 1) {
		const authorityEffects = function(group) {
			let text;
			let bonus;
			if (V.SecExp.core.authority <= 3000) {
				text = `Your very low authority allows ${group} to think too freely.`;
				bonus = 30;
			} else if (V.SecExp.core.authority <= 6000) {
				text = `Your low authority allows ${group} to think too freely.`;
				bonus = 25;
			} else if (V.SecExp.core.authority <= 9000) {
				text = `Your moderate authority allows ${group} to think a bit too freely.`;
				bonus = 20;
			} else if (V.SecExp.core.authority <= 12000) {
				text = `Your good authority does not allow ${group} to think too freely.`;
				bonus = 15;
			} else if (V.SecExp.core.authority <= 15000) {
				text = `Your high authority does not allow ${group} to think too freely.`;
				bonus = 10;
			} else if (V.SecExp.core.authority <= 18000) {
				text = `Your very high authority does not allow ${group} to think too freely.`;
				bonus = 5;
			} else {
				text = `Your absolute authority does not allow ${group} to have a single free thought.`;
				bonus = 1;
			}
			return {text, bonus};
		};

		let slave;
		let citizen;
		const CSratio = V.ACitizens / V.ASlaves;
		r.push(`<br><br><strong>Slaves security analysis:</strong>`);
		r.push(authorityEffects('slaves').text); slave = authorityEffects('slaves').bonus;
		if (CSratio <= 0.4) {
			r.push(`There are a lot more slaves than citizens, making some doubt their masters are strong enough to stop them.`);
			slave += 30;
		} else if (CSratio <= 0.6) {
			r.push(`There are a lot more slaves than citizens, making some doubt their masters are strong enough to stop them.`);
			slave += 25;
		} else if (CSratio <= 0.8) {
			r.push(`There are more slaves than citizens, making some doubt their masters are strong enough to stop them.`);
			slave += 20;
		} else if (CSratio <= 1) {
			r.push(`There are more slaves than citizens, making some doubt their masters are strong enough to stop them.`);
			slave += 15;
		} else if (CSratio <= 1.5) {
			r.push(`There are fewer slaves than citizens, making some doubt they would be strong enough to defeat their masters.`);
			slave += 10;
		} else if (CSratio >= 3) {
			r.push(`There are fewer slaves than citizens, making some doubt they would be strong enough to defeat their masters.`);
			slave -= 5;
		} else {
			r.push(`Citizen and slave populations are sufficiently balanced not to cause problems either way.`);
			slave -= 1;
		}
		if (V.SecExp.core.security <= 10) {
			r.push(`The very low security of the arcology leaves free space for slaves to organize and agitate.`);
			slave += 30;
		} else if (V.SecExp.core.security <= 30) {
			r.push(`The low security of the arcology leaves free space for slaves to organize and agitate.`);
			slave += 20;
		} else if (V.SecExp.core.security <= 60) {
			r.push(`The moderate security of the arcology does not allow free space for slaves to organize and agitate.`);
			slave += 10;
		} else if (V.SecExp.core.security >= 90) {
			r.push(`The high security of the arcology does not allow free space for slaves to organize and agitate.`);
			slave -= 5;
		} else {
			r.push(`The high security of the arcology does not allow free space for slaves to organize and agitate.`);
			slave -= 1;
		}
		if (V.arcologies[0].FSDegradationist !== "unset") {
			r.push(`Many slaves are so disgusted by your degradationist society, that they are willing to rise up against their masters to escape.`);
			slave += 30;
		} else if (V.arcologies[0].FSPaternalist !== "unset") {
			r.push(`Many slaves are content to live in your paternalist society.`);
			slave -= 5;
		} else {
			slave += 5;
		}
		if (V.arcologies[0].FSRestart !== "unset") {
			r.push(`Many slaves are worried by your eugenics projects and some are pushed towards radicalization.`);
			slave += 30;
		} else if (V.arcologies[0].FSRepopulationFocus !== "unset") {
			r.push(`Many slaves are pleasantly happy of your repopulation effort, affording them the freedom to reproduce.`);
			slave -= 5;
		} else {
			slave += 5;
		}

		r.push(`<br><br><strong>Citizens security analysis:</strong>`);
		r.push(authorityEffects('your citizens').text); citizen = authorityEffects('your citizens').bonus;
		if (V.SecExp.core.crimeLow >= 90) {
			r.push(`The very high crime level of the arcology breeds extreme discontent between your citizens.`);
			citizen += 30;
		} else if (V.SecExp.core.crimeLow >= 60) {
			r.push(`The high crime level of the arcology breeds high discontent between your citizens.`);
			citizen += 15;
		} else if (V.SecExp.core.crimeLow >= 30) {
			r.push(`The low crime level of the arcology leaves your citizens happy and satisfied.`);
			citizen += 5;
		} else {
			r.push(`The very low crime level of the arcology leaves your citizens happy and satisfied.`);
			citizen -= 5;
		}

		const militia = V.SecExp.edicts.defense.militia;
		const militarizedSociety = [
			'FSRomanRevivalist', 'FSAztecRevivalist', 'FSEgyptianRevivalist',
			'FSEdoRevivalist', 'FSArabianRevivalist', 'FSChineseRevivalist',
			'FSNeoImperialist'].every((FS) => V.arcologies[0][FS] === "unset");
		if (militia >= 1) {
			if (militia >= 4) {
				r.push(`${!militarizedSociety ? 'Many' : 'Some'} of your citizens are offended by your ${militia === 5 ? 'extreme' : ''} militarization of the arcology's society.`);
				if (!militarizedSociety) {
					citizen += militia === 5 ? 20 : 15;
				} else {
					citizen += militia === 5 ? 10 : 5;
				}
			} else {
				citizen += !militarizedSociety ? 10 : -5;
			}
		}
		if (V.arcologies[0].FSNull !== "unset") {
			r.push(`Many of your more conservative citizens do not enjoy the cultural freedom you afford the residents of the arcology.`);
			citizen += either(20, 30);
		}
		if (V.arcologies[0].FSRestart !== "unset") {
			if (CSratio > 2 || CSratio > 1) {
				r.push(`Your citizens are not happy with the ${CSratio > 2 ? 'noticeable' : ''} lack of slaves compared to their numbers.`);
				citizen += CSratio > 2 ? 20 : 15;
			} else if (CSratio < 0.5) {
				citizen -= 5;
			}
		} else if (V.arcologies[0].FSRepopulationFocus !== "unset") {
			if (CSratio < 0.5 || CSratio < 1) {
				r.push(`Your citizens are not happy about being outbred by the slaves of the arcology.`);
				citizen += CSratio < 0.5 ? 20 : 15;
			} else if (CSratio > 1.4) {
				citizen += 5;
			}
		}

		slave = Math.clamp(slave, 0, 95);
		citizen = Math.clamp(citizen, 0, 95); // rolls to see if event happens - there's always a min 5% chance nothing happens
		const roll = random(1, slave + citizen);
		slave = Math.trunc(slave * V.SecExp.settings.rebellion.speed); citizen = Math.trunc(citizen * V.SecExp.settings.rebellion.speed);
		if (V.SecExp.buildings.riotCenter && V.SecExp.buildings.riotCenter.brainImplant === 106) {
			slave /= 2; citizen /= 2;
		}

		let rebellionEventFires;
		if (roll <= slave && random(1, 100) < slave) {
			rebellionEventFires = 'slave';
		} else if (roll > slave && random(1, 100) < citizen) {
			rebellionEventFires = 'citizen';
		}

		const oldTension = V.SecExp.rebellions.tension;
		if (rebellionEventFires) { // if there is an advancement selects a random mini event
			let miniEvent;
			let rand;
			V.SecExp.rebellions[rebellionEventFires + 'Progress'] += random(1, 5);
			if (V.SecExp.rebellions.tension !== 0) {
				V.SecExp.rebellions[rebellionEventFires + 'Progress'] *= Math.trunc(random(1, 5) * (V.SecExp.rebellions.tension / 100) * 10); // progress scales with tension
			}
			if (V.SecExp.rebellions.tension <= 33) {
				miniEvent = rebellionEventFires === 'slave' ? 1 : 4;
				V.SecExp.rebellions.tension += random(1, 5);
				rand = random(0, 6);
			} else if (V.SecExp.rebellions.tension <= 66) {
				miniEvent = rebellionEventFires === 'slave' ? 2 : 5;
				V.SecExp.rebellions.tension += random(5, 10);
				rand = random(0, 5);
			} else {
				miniEvent = rebellionEventFires === 'slave' ? 3 : 6;
				V.SecExp.rebellions.tension += random(10, 15);
				rand = random(0, 4);
			}

			r.push(`<br><br>`);
			const {HeU, heU, hisU, himU, himselfU} = getNonlocalPronouns(V.seeDicks).appendSuffix("U");
			switch (miniEvent) {
				case 1:
					if (rand === 0) {
						r.push(`This week several slaves were found plotting the death of their master. They were quickly dealt with, but their owner's choice of punishment did little to calm tensions in the arcology.`);
					} else if (rand === 1) {
						r.push(`This week a large group of slaves attempted to escape. Several were recaptured, but others were deemed too dangerous and were shot on sight. The unfortunate circumstances raised the disapproval of many citizens, either because of the waste of good slaves or the brutality with which the operation was carried. With a bit of luck, however, the incident will be soon forgotten.`);
					} else if (rand === 2) {
						r.push(`This week books of unknown origin and dangerous content were found in the possession of several slaves. They were mostly sociopolitical treaties, making it clear that the intent of the ones responsible was to fan the fire of rebellion. The books were quickly collected and archived, hopefully this affair will not have lasting consequences.`);
					} else if (rand === 3) {
						r.push(`This week a citizen was caught giving refuge to an escaped slave. He was not able to pay for the value of the stolen goods, so he was processed as the case required and the slave returned to their rightful master. Many questions however remain without answers.`);
					} else if (rand === 4) {
						r.push(`This week a member of a well known anti-slavery group was caught trying to infiltrate the arcology. During the capture attempt shots were fired and several guards were injured, and in the end the fugitive unfortunately managed to escape. Reports indicate several slaves helped the criminal, some going as far as using themselves as shields against the bullets of the security drones.`);
					} else if (rand === 5) {
						r.push(`This week a slave was caught attempting to sabotage a machine in one of the factories. ${HeU} explained ${hisU} action as "trying to defend ${himselfU} from a dangerous machine". Reports confirmed that the apparatus is indeed quite deadly, having killed several slaves since it was installed, but the expert way ${heU} handled the sabotage leaves open the possibility of a deliberate plan or even external help.`);
					} else {
						r.push(`This week a slave was found dead in one of the sewer tunnels. It seems ${heU} was stabbed repeatedly with a sharp object. ${HeU} was fairly famous for ${hisU} capabilities as a slave trainer; ${hisU} old master spent not an insignificant amount of money trying to find ${himU} once he realized ${heU} was missing. The episode might have been a simple mugging gone wrong, but ${hisU} activities as a slave breaker might have played a role in ${hisU} homicide.`);
					}
					break;
				case 2:
					if (rand === 0) {
						r.push(`This week some strange reports came in: it seems some assemblies of slaves were observed several nights in a row. The slaves were traced and their masters notified, but many suspect there may be something deeper than a few slaves congregating in the night.`);
					} else if (rand === 1) {
						r.push(`This week an underground railroad was discovered. The rebels did not go down without a fight, but in the end your ${V.mercenaries >= 1 ? 'mercenaries' : 'security drones'} managed to destroy the old tunnels they were using to ship out slaves out of the arcology.`);
					} else if (rand === 2) {
						r.push(`This week a famous citizen was assaulted and brutally murdered by his slaves. The ones responsible were apprehended and dealt with easily enough, but the mere fact something like this could have happened is concerning. Those slaves had to be aware of their certain doom.`);
					} else if (rand === 3) {
						r.push(`This week a group of slavers entering the arcology was assaulted. Many reported heavy injuries, but fortunately there were no casualties. The attackers were disguised, but the security systems already identified several slaves who were likely part of the group, based on camera feeds.`);
					} else if (rand === 4) {
						r.push(`This week the waterways were found infected by a virulent pathogen. The cause was later found to be a diseased slave that died while in the maintenance tunnels. It's not clear if the slave was there because of orders given to ${himU} or if ${heU} was trying to escape.`);
					} else {
						r.push(`This week a sleeper cell of a famous anti slavery organization was discovered in the low levels of the arcology. The group, however, was aware of the coming security forces and retreated before they could be dealt with.`);
					}
					break;
				case 3:
					if (rand === 0) {
						r.push(`This week a group of slaves took control of one of the manufacturing plants and barricaded themselves inside. It took several days of negotiations and skirmishes to finally end this little insurrection. Many of the slaves involved will be executed in the next few days.`);
					} else if (rand === 1) {
						r.push(`This week a number of shops were burned to the ground by rioting slaves and sympathetic citizens. It took considerable effort for the security forces to take control of the situation. Harsh punishment is required and scheduled for the instigators.`);
					} else if (rand === 2) {
						r.push(`This week a mass escape attempt was barely stopped before becoming a catastrophe. Many citizens were trampled by the desperate horde of slaves. It will take some time to restore the streets involved to working order.`);
					} else if (rand === 3) {
						r.push(`This week a number of riots inflamed the arcology. Many slaves took violent actions against citizens and security personnel. The number of victims keeps getting higher as still now the last sparks of revolt are still active.`);
					}
					break;
				case 4:
					if (rand === 0) {
						r.push(`This week a citizen refused to pay rent, claiming ideological opposition to the arcology's ownership policies. He was quickly dealt with, but his words might not have fallen silent yet.`);
					} else if (rand === 1) {
						r.push(`This week books of unknown origin and dangerous content were found in the possession of several citizens. They were mostly sociopolitical treaties, making it clear that the intent of the ones responsible was to fan the fire of rebellion. Most of them were bought and archived, but a few are still circling amongst the citizens of the arcology.`);
					} else if (rand === 2) {
						r.push(`This week a citizen was caught giving refuge to other citizens, who would be liable to be enslaved because of their debts. The situation was quickly resolved, but the misplaced generosity of that citizen might have inflamed a few souls.`);
					} else if (rand === 3) {
						r.push(`This week a citizen died in one of the factories. His death sparked some outrage, even some talk of protests against the owners of the factory, but things seem to have calmed down for now.`);
					} else if (rand === 4) {
						r.push(`This week a citizen refused to be evicted from his house. After some negotiations the man was forcibly removed from the property by your security forces. Unfortunately the forced entry caused some damage to the building.`);
					} else if (rand === 5) {
						r.push(`This week a citizen refused to be enslaved as his contract established. With an impressive display of his rhetoric capabilities he managed to gather a small crowd agreeing with his position. The impromptu assembly was promptly disrupted by the drones.`);
					} else {
						r.push(`This week a security drone was found disabled and stripped of important electronic components. It seems the act was not dictated by greed, as the most precious parts of the drone were left on the machine, but rather to cover up something that the drone saw.`);
					}
					break;
				case 5:
					if (rand === 0) {
						r.push(`This week a factory was subject to a strike by a group of citizens protesting against the owner. They were promptly arrested and the factory returned to its rightful proprietor by your security department.`);
					} else if (rand === 1) {
						r.push(`This week a group of citizens organized a protest against the systemic enslavement of the citizens of the arcology. Their little parade gathered a surprisingly large crowd, but it was nonetheless quickly suppressed by your forces.`);
					} else if (rand === 2) {
						r.push(`This week the security department registered the formation of several assemblies of citizens, whose purpose seems to be political in nature. For now no further steps were taken, but it's a worrying sign of further political opposition within the arcology.`);
					} else if (rand === 3) {
						r.push(`This week there was a protest against one of the wealthiest citizen of the arcology. Many criticize his near monopoly. Supporters of the citizen met the protesters on the streets and it was just thanks to the intervention of the security drones that violence was avoided.`);
					} else if (rand === 4) {
						r.push(`This week several cameras were sabotaged and in many cases damaged beyond repair. A group of anonymous citizens claims to be responsible; their motivation is apparently the excessive surveillance in the arcology and their attack a response to the breach of their privacy.`);
					} else {
						r.push(`This week several citizens barricaded themselves in a private brothel. It seems their intention is to protest against the use of ex-citizens in the sex trade, claiming that such a position is unfitting for them. The problem was quickly resolved with the intervention of the security department.`);
					}
					break;
				case 6:
					if (rand === 0) {
						r.push(`This week the arcology was shaken by a number of strikes throughout the manufacturing levels. Many lament the predatory nature of Free Cities society, many other just want to cause damage to their perceived oppressors. It was a significant effort for the security department to stop all protests.`);
					} else if (rand === 1) {
						r.push(`This week several factories were set aflame by their workers. The security department worked day and night to control the fire and apprehend the criminals behind the act. Many are known dissidents, but there are a fair few new faces within them. This is a worrying sign.`);
					} else if (rand === 2) {
						r.push(`This week numerous riots exploded all over the arcology. Many citizens took to the streets to protest against the arcology owner and its supporters. The security forces slowly managed to stop the rioters, with no small amount of trouble and only through generous use of violence.`);
					} else if (rand === 3) {
						r.push(`This week a massive protest of citizens and slaves gathered just outside the penthouse. The crowd was dispersed only after several hours. There were several victims from both sides and no shortage of injured.`);
					}
					break;
			}
			V.SecExp.rebellions.tension = Math.clamp(V.SecExp.rebellions.tension, 0, 100);
		} else if (V.SecExp.rebellions.tension > 0) { // otherwise SecExp.rebellions.tension decays
			r.push(`<br><br><strong>Tension</strong>:`);
			if (V.SecExp.buildings.riotCenter && V.SecExp.buildings.riotCenter.upgrades.freeMedia >= 1) {
				r.push(`The guaranteed free media access you offer does wonders to lower tensions in the arcology.`);
				V.SecExp.rebellions.tension = Math.trunc(Math.clamp(V.SecExp.rebellions.tension - V.SecExp.buildings.riotCenter.upgrades.freeMedia / 2, 0, 100));
			}
			r.push(`In the absence of noteworthy events, tensions in the arcology are able to relax.`);
			V.SecExp.rebellions.tension = Math.trunc(Math.clamp(V.SecExp.rebellions.tension * 0.97, 0, 100));
		}

		if (V.SecExp.rebellions.tension < oldTension) {
			r.push(`<br><br>This week <span class="green">tensions relaxed.</span><br>`);
		} else if (V.SecExp.rebellions.tension === oldTension && V.SecExp.rebellions.tension !== 0) {
			r.push(`<br><br>This week <span class="yellow">tensions did not change.</span><br>`);
		} else if (V.SecExp.rebellions.tension > oldTension) {
			r.push(`<br><br>This week <span class="red">tension rose</span> and <span class="red">${rebellionEventFires} malcontent increased.</span><br>`);
		} else if (!Number.isInteger(V.SecExp.rebellions.tension)) {
			r.push(`<br><br><span class="red">Error: tension is outside accepted range.</span><br>`);
		}

		App.SecExp.generator.rebellion(); // rolls for rebellions
	}
	return App.Events.makeNode(r);
};