code:
App.Mods.SecExp.secHub = (function() {
	return {
		init,
		GUI
	};

	function init() {
		V.SecExp.buildings.secHub = {
			menials: 0,
			coldstorage: 0,
			upgrades: {
				security: {
					nanoCams: 0,
					cyberBots: 0,
					eyeScan: 0,
					cryptoAnalyzer: 0,
				},
				crime: {
					autoTrial: 0,
					autoArchive: 0,
					worldProfiler: 0,
					advForensic: 0,
				},
				intel : {
					sensors: 0,
					radar: 0,
					signalIntercept: 0,
				},
				readiness: {
					earlyWarn: 0,
					rapidPlatforms: 0,
					pathways: 0,
					rapidVehicles: 0,
				}
			}
		};
	}
	function GUI() {
		const node = new DocumentFragment();
		V.nextButton = "Back";
		V.nextLink = "Main";
		let r = [];

		r.push("The security headquarters stand in front of you. Innumerable screens flood with light the great central room.");
		if (V.SecExp.buildings.secHub.menials > 0) {
			r.push("Some slaves see you enter and interrupt their work to greet you.");
		}
		r.push("From here you can build a safe and prosperous arcology.");
		node.append(r.join(" "));
		if (V.SecExp.core.authority < 10000 || V.SecExp.core.authority < 12000) {
			App.UI.DOM.appendNewElement("div", node, "You lack the authority to access more advanced upgrades.", "red");
		}

		App.UI.DOM.appendNewElement("div", node, `You have ${num(V.SecExp.buildings.secHub.menials)} slaves working in the HQ. ${App.Mods.SecExp.Check.reqMenials()} are required and you have ${num(V.menials)} free menial slaves.`);
		if (V.SecExp.buildings.secHub.menials < App.Mods.SecExp.Check.reqMenials()) {
			App.UI.DOM.appendNewElement("div", node, "You do not have enough slaves here. You will not receive the full benefit of the installed upgrades.");
		} else {
			App.UI.DOM.appendNewElement("div", node, "You have enough slaves to man all security systems.");
		}
		App.UI.DOM.appendNewElement("div", node, App.UI.tradeMenials(true));

		const optionsList = [1, 5, 10, 100, 500, 1000];
		let linkArray;
		for (const flow of ["in", "out"]) {
			linkArray = [];
			for (const value of optionsList.filter(s => (flow === "in" ? V.menials : V.SecExp.buildings.secHub.menials) >= s)) {
				linkArray.push(
					App.UI.DOM.link(
						`${value}`,
						() => {
							if (flow === "in") {
								V.menials -= value;
								V.SecExp.buildings.secHub.menials += value;
							} else {
								V.menials += value;
								V.SecExp.buildings.secHub.menials -= value;
							}
							App.UI.reload();
						}
					)
				);
			}
			if (linkArray.length > 0) {
				const line = new DocumentFragment();
				line.append(`Transfer ${flow}: `, App.UI.DOM.generateLinksStrip(linkArray));
				App.UI.DOM.appendNewElement("div", node, line);
			}
		}

		if (V.SecExp.buildings.secHub.menials > 0) {
			App.UI.DOM.appendNewElement("div", node,
				App.UI.DOM.link(
					"Match the requirement",
					() => {
						if (V.menials >= App.Mods.SecExp.Check.reqMenials() - V.SecExp.buildings.secHub.menials) {
							V.menials -= App.Mods.SecExp.Check.reqMenials() - V.SecExp.buildings.secHub.menials;
							V.SecExp.buildings.secHub.menials = App.Mods.SecExp.Check.reqMenials();
						} else if (App.Mods.SecExp.Check.reqMenials() < V.SecExp.buildings.secHub.menials) {
							V.menials += App.Mods.SecExp.Check.reqMenials() - V.SecExp.buildings.secHub.menials;
							V.SecExp.buildings.secHub.menials = App.Mods.SecExp.Check.reqMenials();
						} else {
							V.SecExp.buildings.secHub.menials += V.menials;
							V.menials = 0;
						}
						App.UI.reload();
					}
				)
			);
		}

		App.Events.addParagraph(node, []);
		switch (App.Mods.SecExp.battle.recon()) {
			case 0:
				App.UI.DOM.appendNewElement("div", node, "Your reconnaissance capabilities are very limited. Very little information will be available if the arcology is attacked.");
				break;
			case 1:
				App.UI.DOM.appendNewElement("div", node, "You have limited reconnaissance capabilities. You'll have limited intel available in case of an attack.");
				break;
			case 2:
				App.UI.DOM.appendNewElement("div", node, "You have good reconnaissance capabilities. Good, reliable intel will be available if the arcology is attacked.");
				break;
			case 3:
				App.UI.DOM.appendNewElement("div", node, "You have great reconnaissance capabilities. You'll have very accurate information on the enemy if the arcology is attacked.");
		}

		switch (App.Mods.SecExp.battle.deploySpeed()) {
			case 1:
				App.UI.DOM.appendNewElement("div", node, "You have low readiness. You won't be able to mobilize many troops in time in case of an attack.");
				break;
			case 2:
				App.UI.DOM.appendNewElement("div", node, "You have decent readiness. You will be able to muster up sufficient forces to handle an average attack.");
				break;
			case 3:
				App.UI.DOM.appendNewElement("div", node, "You have good readiness. You will be able to mobilize a lot of troops in case of an attack.");
				break;
			case 4:
				App.UI.DOM.appendNewElement("div", node, "You have great readiness. You can mobilize a small army in very little time.");
		}

		App.UI.DOM.appendNewElement("div", node, situationAnalysis("security"));
		App.UI.DOM.appendNewElement("div", node, situationAnalysis("crime"));

		if (V.SecExp.core.authority > 12000) {
			App.Events.addParagraph(node, []);
			App.UI.DOM.appendNewElement("h2", node, `Cold Data Storage Facility:`);
			if (App.Mods.SecExp.Check.reqMenials() <= 10) {
				App.UI.DOM.appendNewElement("div", node, "Personnel cannot be further reduced.");
			} else {
				r = [];
				if (V.SecExp.buildings.secHub.coldstorage > 0) {
					r.push("You have installed a cold storage facility for the Security HQ's archives with a data retention capability of");
				}
				if (V.SecExp.buildings.secHub.coldstorage > 6) {
					r.push("three years.");
				} else {
					let cost;
					let authorityGate = 19500;
					let text = ["Expand the cold storage facility to increase data retention to"];
					const targetValue = V.SecExp.buildings.secHub.coldstorage;
					const note = `Will lower the amount of required slaves by ${targetValue > 0 ? "a further" : ""} 10, but will increase upkeep. The remaining slaves will be more efficient in dealing with crime.`;
					if (V.SecExp.buildings.secHub.coldstorage === 6) {
						cost = 2400000;
						r.push("two years.");
						text.push("three years.");
					} else if (V.SecExp.buildings.secHub.coldstorage === 5) {
						cost = 1200000;
						r.push("one year.");
						text.push("two years.");
					} else if (V.SecExp.buildings.secHub.coldstorage === 4) {
						cost = 900000;
						r.push("nine months.");
						text.push("one year.");
					} else if (V.SecExp.buildings.secHub.coldstorage === 3) {
						authorityGate = 18000;
						cost = 600000;
						r.push("six months.");
						text.push("nine months.");
					} else if (V.SecExp.buildings.secHub.coldstorage === 2) {
						authorityGate = 16000;
						cost = 300000;
						r.push("three months.");
						text.push("six months.");
					} else if (V.SecExp.buildings.secHub.coldstorage === 1) {
						authorityGate = 14000;
						cost = 100000;
						r.push("one month.");
						text.push("three months.");
					} else if (V.SecExp.buildings.secHub.coldstorage === 0) {
						authorityGate = 12000;
						cost = 50000;
						text = ["Install a cold storage facility"];
					}
					if (checkStatus(targetValue, authorityGate)) {
						App.UI.DOM.appendNewElement("div", node, makeLink(text.join(" "), cost, note, "coldstorage"));
					} else {
						App.UI.DOM.appendNewElement("div", node, `At least ${num(authorityGate)} authority is required to unlock the next tier.`, "red");
					}
				}
				if (r.length > 0) {
					App.UI.DOM.appendNewElement("div", node, r.join(" "));
				}
			}
		}

		App.Events.addParagraph(node, []);
		const tabBar = new App.UI.Tabs.TabBar("SecExpSecurityHQ");
		tabBar.addTab("Security and crime", "SecurityCrime", SecurityCrime());
		tabBar.addTab("Reconnaissance and readiness", "ReconReadiness", ReconReadiness());
		node.append(tabBar.render());

		return node;

		function SecurityCrime() {
			const z = new DocumentFragment();
			App.UI.DOM.appendNewElement("h2", z, `Security`);
			if (V.SecExp.buildings.secHub.upgrades.security.nanoCams === 0) {
				z.append(
					makeLink("Install a nano-camera system",
						5000,
						"Will raise rest point of security by 10 points, but will require 5 extra slaves in the headquarters and increases upkeep. The remaining slaves will be more efficient in dealing with crime.",
						"nanoCams")
				);
			} else {
				z.append("You have installed all across the arcology closed circuit nano-cameras to keep the arcology under your watchful eye.");
			}

			if (V.SecExp.buildings.secHub.upgrades.security.cyberBots === 0) {
				App.UI.DOM.appendNewElement("div", z,
					makeLink("Buy cybersecurity algorithms",
						7500,
						"Will raise rest point of security by 10 points, but will require 5 extra slaves in the headquarters and increases upkeep. The remaining slaves will be more efficient in dealing with crime.",
						"cyberBots")
				);
			} else {
				App.UI.DOM.appendNewElement("div", z, "You have bought advanced cybersecurity algorithms that will defend your arcology against hack attempts or cyber frauds.");
			}

			if (V.SecExp.core.authority > 10000) {
				if (V.SecExp.buildings.secHub.upgrades.security.eyeScan === 0) {
					App.UI.DOM.appendNewElement("div", z,
						makeLink("Install invisible eye scanners",
							10000,
							"Will raise rest point of security by 15 points, but will require 10 extra slaves in the headquarters and increases upkeep. The remaining slaves will be more efficient in dealing with crime.",
							"eyeScan")
					);
				} else {
					App.UI.DOM.appendNewElement("div", z, "You have installed numerous hidden eye scanners that accurately register the movements of everyone inside the arcology.");
				}

				if (V.SecExp.buildings.secHub.upgrades.security.cryptoAnalyzer === 0) {
					App.UI.DOM.appendNewElement("div", z,
						makeLink("Buy and install crypto analyzers",
							15000,
							"Will raise rest point of security by 15 points, but will require 10 extra slaves in the headquarters and increases upkeep. The remaining slaves will be more efficient in dealing with crime.",
							"cryptoAnalyzer")
					);
				} else {
					App.UI.DOM.appendNewElement("div", z, "You have bought and employed sophisticated crypto analyzing software to accurately track and archive every financial movement or transaction made inside the walls of your arcology.");
				}
			}

			App.UI.DOM.appendNewElement("h2", z, `Crime`);
			if (V.SecExp.buildings.secHub.upgrades.crime.advForensic === 0) {
				App.UI.DOM.appendNewElement("div", z,
					makeLink("Install advanced forensic equipment",
						5000,
						"Will bring down the crime level cap by 10 points, but will require 10 extra slaves in the headquarters and increases upkeep. The remaining slaves will be more efficient in dealing with crime.",
						"advForensic")
				);
			} else {
				App.UI.DOM.appendNewElement("div", z, "You have installed advanced forensic equipment, able to extract every bit of precious information from any clue.");
			}
			if (V.SecExp.buildings.secHub.upgrades.crime.autoArchive === 0) {
				App.UI.DOM.appendNewElement("div", z,
					makeLink("Install auto-curating archiver",
						7500,
						"Will bring down the crime level cap by 10 points, but will require 5 extra slaves in the headquarters and increases upkeep. The remaining slaves will be more efficient in dealing with crime.",
						"autoArchive")
				);
			} else {
				App.UI.DOM.appendNewElement("div", z, "You have installed auto-curating archiver software, which will update in real time your data archives with any new relevant information on criminals residing in your arcology.");
			}

			if (V.SecExp.core.authority > 10000) {
				if (V.SecExp.buildings.secHub.upgrades.crime.autoTrial === 0) {
					App.UI.DOM.appendNewElement("div", z,
						makeLink("Install automated trials software",
							10000,
							"Will bring down the crime level cap by 15 points, but will require 5 extra slaves in the headquarters and increases upkeep. The remaining slaves will be more efficient in dealing with crime.",
							"autoTrial")
					);
				} else {
					App.UI.DOM.appendNewElement("div", z, "You have installed advanced legal algorithms that allows the handling of legal matters much quicker and much more accurately.");
				}

				if (V.SecExp.buildings.secHub.upgrades.crime.worldProfiler === 0) {
					App.UI.DOM.appendNewElement("div", z,
						makeLink("Install worldwide profilers",
							15000,
							"Will bring down the crime level cap by 15 points, but will require 10 extra slaves in the headquarters and increases upkeep. The remaining slaves will be more efficient in dealing with crime.",
							"worldProfiler")
					);
				} else {
					App.UI.DOM.appendNewElement("div", z, "You have installed advanced profiler software, which will constantly scour every known data archive on the globe (legally or not) to gather as much information as possible on dangerous criminals.");
				}
			}
			return z;
		}
		function ReconReadiness() {
			const z = new DocumentFragment();
			App.UI.DOM.appendNewElement("h2", z, `Reconnaissance`);
			if (V.SecExp.buildings.secHub.upgrades.intel.sensors === 0) {
				App.UI.DOM.appendNewElement("div", z,
					makeLink("Install perimeter sensors",
						5000,
						"Will increase recon capabilities, but will require 5 extra slaves in the headquarters and increases upkeep. The remaining slaves will be more efficient in dealing with crime.",
						"sensors")
				);
			} else {
				App.UI.DOM.appendNewElement("div", z, "You have installed perimeter seismic sensors able to detect movement with high accuracy.");
			}
			if (V.SecExp.buildings.secHub.upgrades.intel.signalIntercept === 0) {
				App.UI.DOM.appendNewElement("div", z,
					makeLink("Create signal interception hub",
						10000,
						"Will increase recon capabilities, but will require 5 extra slaves in the headquarters and increases upkeep. The remaining slaves will be more efficient in dealing with crime.",
						"signalIntercept")
				);
			} else {
				App.UI.DOM.appendNewElement("div", z, "You have installed advanced signal interception equipment.");
			}

			if (V.SecExp.core.authority > 10000) {
				if (V.SecExp.buildings.secHub.upgrades.intel.radar === 0) {
					App.UI.DOM.appendNewElement("div", z,
						makeLink("Install advanced radar equipment",
							15000,
							"Will increase recon capabilities, but will require 10 extra slaves in the headquarters and increases upkeep. The remaining slaves will be more efficient in dealing with crime.",
							"radar")
					);
				} else {
					App.UI.DOM.appendNewElement("div", z, "You have installed sophisticated radar equipment.");
				}
			}

			App.UI.DOM.appendNewElement("h2", z, `Readiness`);
			App.UI.DOM.appendNewElement("div", z,  `You can deploy a maximum of ${App.Mods.SecExp.battle.deploySpeed()} units, and you have ${App.Mods.SecExp.battle.activeUnits()} active units right now.`);
			if (V.SecExp.buildings.secHub.upgrades.readiness.pathways === 0) {
				App.UI.DOM.appendNewElement("div", z,
					makeLink("Build specialized pathways in the arcology",
						5000,
						"Will increase readiness by 1, but will require 5 extra slaves in the headquarters and increase upkeep. The remaining slaves will be more efficient in dealing with crime.",
						"pathways")
				);
			} else {
				App.UI.DOM.appendNewElement("div", z, "You have built specialized pathways inside the arcology to quickly move troops around the structure.");
			}
			if (V.SecExp.buildings.secHub.upgrades.readiness.rapidVehicles === 0) {
				App.UI.DOM.appendNewElement("div", z,
					makeLink("Buy rapid armored transport vehicles",
						7500,
						"Will increase readiness by 2, but will require 5 extra slaves in the headquarters and increases upkeep. The remaining slaves will be more efficient in dealing with crime.",
						"rapidVehicles")
				);
			} else {
				App.UI.DOM.appendNewElement("div", z, "You have bought rapid armored transport vehicles able to bring your troops to battle much quicker than before.");
			}

			if (V.SecExp.core.authority > 10000) {
				if (V.SecExp.buildings.secHub.upgrades.readiness.rapidPlatforms === 0) {
					App.UI.DOM.appendNewElement("div", z,
						makeLink("Build rapid deployment platforms",
							10000,
							"Will increase readiness by 2, but will require 10 extra slaves in the headquarters and increases upkeep. The remaining slaves will be more efficient in dealing with crime.",
							"rapidPlatforms",
							false)
					);
				} else {
					App.UI.DOM.appendNewElement("div", z, "You have built rapid deployment platforms able to equip and deploy units within very limited time windows.");
				}
				if (V.SecExp.buildings.secHub.upgrades.readiness.earlyWarn === 0) {
					App.UI.DOM.appendNewElement("div", z,
						makeLink("Institute early warning systems",
							15000,
							"Will increase readiness by 2, but will require 10 extra slaves in the headquarters and increases upkeep. The remaining slaves will be more efficient in dealing with crime.",
							"earlyWarn",
							false)
					);
				} else {
					App.UI.DOM.appendNewElement("div", z, "You have created early warning systems that constantly analyze in real time data to determine the likeness of an attack.");
				}
			}

			return z;
		}
		function situationAnalysis(state) {
			const isSecurity = state === "security";
			const variable = isSecurity ? "security" : "crimeLow";
			const colour = isSecurity ? "deepskyblue" : "orangered";
			let text = [];

			const r = new DocumentFragment();
			r.append(`Your ${state} level (`, App.UI.DOM.makeElement("span", `${V.SecExp.core[variable]}`, colour), `)`);
			text.push(" ");
			if (V.SecExp.core[variable] <= 20) {
				text.push(`is ${isSecurity ? "dangerously" : "very"} low.`);
			} else if (V.SecExp.core[variable] <= 40) {
				text.push("is low.");
			} else if (V.SecExp.core[variable] <= 60) {
				text.push(`is ${isSecurity ? "decent" : "average"}.`);
			} else if (V.SecExp.core[variable] <= 80) {
				text.push(`is ${isSecurity ? "good" : "high"}.`);
			} else {
				text.push(`is ${isSecurity ? "great" : "extremely high"}.`);
			}
			text.push("Considering the current upgrades the");
			if (isSecurity) {
				text.push(`resting level for security is ${Math.trunc(App.Mods.SecExp.Check.secRestPoint() * (Math.clamp(V.SecExp.buildings.secHub.menials, 0, App.Mods.SecExp.Check.reqMenials()) / App.Mods.SecExp.Check.reqMenials()))}, `);
				text.push(`while the effective maximum level is ${Math.trunc(App.Mods.SecExp.Check.reqMenials() * (Math.clamp(V.SecExp.buildings.secHub.menials, 0, App.Mods.SecExp.Check.reqMenials()) / App.Mods.SecExp.Check.reqMenials()))}.`);
			} else {
				text.push(`maximum level of crime is ${App.Mods.SecExp.Check.crimeCap()}, `);
				text.push(`while the effective maximum level is ${Math.trunc(Math.clamp(App.Mods.SecExp.Check.crimeCap() + (App.Mods.SecExp.Check.crimeCap() - App.Mods.SecExp.Check.crimeCap() * (V.SecExp.buildings.secHub.menials / App.Mods.SecExp.Check.reqMenials())), 0, 100))}.`);
			}
			r.append(text.join(" "));

			return r;
		}
		function checkStatus(value, authority) {
			return V.SecExp.buildings.secHub.coldstorage === value && V.SecExp.core.authority >= authority && App.Mods.SecExp.Check.reqMenials() > 10;
		}
		function makeLink(text, price, note, type, applyHacking=true) {
			const r = new DocumentFragment();
			r.append(App.UI.DOM.link(text,
				() => {
					cashX(-getCost(), "capEx");
					if (applyHacking) {
						V.PC.skill.hacking++;
					}
					switch (type) {
						case "coldstorage":
							V.SecExp.buildings.secHub.coldstorage++;
							break;
						case "nanoCams":
						case "cyberBots":
						case "eyeScan":
						case "cryptoAnalyzer":
							V.SecExp.buildings.secHub.upgrades.security[type] = 1;
							break;
						case "advForensic":
						case "autoArchive":
						case "autoTrial":
						case "worldProfiler":
							V.SecExp.buildings.secHub.upgrades.crime[type] = 1;
							break;
						case "sensors":
						case "signalIntercept":
						case "radar":
							V.SecExp.buildings.secHub.upgrades.intel[type] = 1;
							break;
						case "pathways":
						case "rapidVehicles":
						case "rapidPlatforms":
						case "earlyWarn":
							V.SecExp.buildings.secHub.upgrades.readiness[type] = 1;
							break;
					}
					App.UI.reload();
				},
			));
			r.append(App.UI.DOM.makeElement("div", `Costs ${cashFormat(getCost())}. ${note}`, "note"));
			return r;

			function getCost() {
				const HistoryDiscount = ["mercenary", "gang", "slaver"].includes(V.PC.career) ? 0.5 : 1;
				return Math.trunc(price * V.upgradeMultiplierArcology * HistoryDiscount * (applyHacking ? V.HackingSkillMultiplier : 1));
			}
		}
	}
})();


code:
App.Mods.SecExp.transportHub = (function() {
	return {
		Init,
		GUI
	};

	function Init() {
		V.SecExp.buildings.transportHub = {
			airport: 1,
			security: 1,
			surfaceTransport: 1,
		};
	}
	function GUI() {
		const frag = new DocumentFragment();
		V.nextButton = "Back";
		V.nextLink = "Main";
		frag.append(
			intro(),
			trade(),
			upgrades(),
		);
		return frag;

		function intro() {
			const div = document.createElement("div");
			const text = [];

			text.push(`You quickly reach the transport hub, where a constant stream of vehicles, people and goods greets you. Part of the structure is dedicated to air travel and the other is mainly occupied by the ${V.terrain !== "oceanic" && V.terrain !== "marine" ? `monorail station` : `docks`}.`);

			if (V.SecExp.edicts.limitImmigration || V.policies.immigrationRep === -1) {
				text.push(`Due to your strict policies concerning immigration, very few new citizens arrive in the transport hub.`);
			} else if (V.SecExp.edicts.openBorders || V.policies.immigrationCash) {
				text.push(`Due to your liberal policies concerning immigration, the transport hub is filled with a flow of new citizens.`);
			}

			App.Events.addNode(div, text, "div", ['scene-intro']);
			App.UI.DOM.appendNewElement("div", div, App.UI.DOM.passageLink(`Decommission the Transport Hub`, "Main", () => {
				delete V.SecExp.buildings.transportHub;

				App.Arcology.cellUpgrade(V.building, App.Arcology.Cell.Market, "Transport Hub", "Markets");
			}), ['indent']);
			return div;
		}

		function trade() {
			const div = document.createElement("div");
			const text = [];

			App.UI.DOM.appendNewElement("h2", div, `Trade`);
			if (V.SecExp.core.trade <= 20) {
				text.push(`Trade is <span class="red">almost non-existent.</span> Outside the supplies for the arcology's domestic consumption little else crosses the territory of the free city.`);
			} else if (V.SecExp.core.trade <= 40) {
				text.push(`Trade is <span class="orange">low.</span> There's some but limited commercial activity crossing the territory of the free city.`);
			} else if (V.SecExp.core.trade <= 60) {
				text.push(`Trade is at <span class="yellow">positive levels.</span> There's a good amount commercial activity outside the supplies for the arcology's domestic consumption.`);
			} else if (V.SecExp.core.trade <= 80) {
				text.push(`Trade is at <span class="limegreen">high levels.</span> There's a lot of commercial activity outside the supplies for the arcology's domestic consumption.`);
			} else {
				text.push(`Trade is at <span class="green">extremely high levels.</span> There's a constant stream of commercial activity crossing the arcology.`);
			}
			App.Events.addNode(div, text);
			return div;
		}

		function upgrades() {
			const div = document.createElement("div");
			const price = cost => Math.trunc(cost * V.upgradeMultiplierArcology);
			App.UI.DOM.appendNewElement("h2", div, `Upgrades`);
			div.append(
				airport(),
				V.terrain !== "oceanic" && V.terrain !== "marine"
					? land()
					: sea(),
				security(),
			);
			return div;

			function airport() {
				const div = document.createElement("div");
				const text = [];
				const links = [];

				if (V.SecExp.buildings.transportHub.airport === 1) {
					text.push(`The arcology's airport is relatively small and poorly equipped. It can handle some traffic, but nothing noteworthy.`);
					links.push(App.UI.DOM.link(`Modernize the airport`, () => {
						V.SecExp.buildings.transportHub.airport = 2;
						cashX(forceNeg(price(5000)), "capEx");

						App.UI.reload();
					}, [], '', `Costs ${cashFormat(price(5000))} and will increase trade, but will affect security.`));
				} else if (V.SecExp.buildings.transportHub.airport === 2) {
					text.push(`The arcology's airport is relatively small, but fairly well equipped. It can handle some traffic, but nothing too serious.`);
					links.push(App.UI.DOM.link(`Enlarge the airport`, () => {
						V.SecExp.buildings.transportHub.airport = 3;
						cashX(forceNeg(price(15000)), "capEx");

						App.UI.reload();
					}, [], '', `Costs ${cashFormat(price(15000))} and will increase trade, but will affect security.`));
				} else if (V.SecExp.buildings.transportHub.airport === 3) {
					text.push(`The arcology's airport is good sized and well equipped. It can handle a good amount of traffic.`);
					links.push(App.UI.DOM.link(`Further modernize the airport`, () => {
						V.SecExp.buildings.transportHub.airport = 4;
						cashX(forceNeg(price(45000)), "capEx");

						App.UI.reload();
					}, [], '', `Costs ${cashFormat(price(45000))} and will increase trade, but will affect security.`));
				} else if (V.SecExp.buildings.transportHub.airport === 4) {
					links.push(App.UI.DOM.link(`Further enlarge the airport`, () => {
						V.SecExp.buildings.transportHub.airport = 5;
						cashX(forceNeg(price(85000)), "capEx");

						App.UI.reload();
					}, [], '', `Costs ${cashFormat(price(85000))} and will increase trade, but will affect security.`));
					text.push(`The arcology's airport is good sized and very well equipped. It can handle a lot of traffic.`);
				} else {
					text.push(`The arcology's airport is huge and very well equipped. It can handle an impressive amount of traffic.`);
				}

				App.Events.addNode(div, text);
				App.UI.DOM.appendNewElement("div", div, App.UI.DOM.generateLinksStrip(links), ['indent']);
				return div;
			}

			function land() {
				const div = document.createElement("div");
				const text = [];
				const links = [];

				if (V.SecExp.buildings.transportHub.surfaceTransport === 1) {
					text.push(`The railway network is old and limited. It can handle some traffic, but not sustain commercial activity.`);
					links.push(App.UI.DOM.link(`Modernize the railway`, () => {
						V.SecExp.buildings.transportHub.surfaceTransport = 2;
						cashX(forceNeg(price(10000)), "capEx");

						App.UI.reload();
					}, [], '', `Costs ${cashFormat(price(10000))} and will increase trade, but will affect security.`));
				} else if (V.SecExp.buildings.transportHub.surfaceTransport === 2) {
					text.push(`The railway network is modern and efficient, but limited in reach. It can handle some traffic, but not sustain commercial activity of any significant size.`);
					links.push(App.UI.DOM.link(`Enlarge the railway`, () => {
						V.SecExp.buildings.transportHub.surfaceTransport = 3;
						cashX(forceNeg(price(25000)), "capEx");

						App.UI.reload();
					}, [], '', `Costs ${cashFormat(price(25000))} and will increase trade, but will affect security.`));
				} else if (V.SecExp.buildings.transportHub.surfaceTransport === 3) {
					text.push(`The railway network is modern, efficient and expansive. It can handle a significant amount of traffic.`);
					links.push(App.UI.DOM.link(`Further modernize the railway`, () => {
						V.SecExp.buildings.transportHub.surfaceTransport = 4;
						cashX(forceNeg(price(65000)), "capEx");

						App.UI.reload();
					}, [], '', `Costs ${cashFormat(price(65000))} and will increase trade, but will affect security.`));
				} else {
					text.push(`The railway network is high tech and very far reaching. It can handle an enormous amount of traffic.`);
				}

				App.Events.addNode(div, text);
				App.UI.DOM.appendNewElement("div", div, App.UI.DOM.generateLinksStrip(links), ['indent']);
				return div;
			}

			function sea() {
				const div = document.createElement("div");
				const text = [];
				const links = [];

				if (V.SecExp.buildings.transportHub.surfaceTransport === 1) {
					text.push(`The docks are old and small. They can handle some traffic, but not sustain commercial activity.`);
					links.push(App.UI.DOM.link(`Modernize the docks`, () => {
						V.SecExp.buildings.transportHub.surfaceTransport = 2;
						cashX(forceNeg(price(10000)), "capEx");

						App.UI.reload();
					}, [], '', `Costs ${cashFormat(price(10000))} and will increase trade, but will affect security.`));
				} else if (V.SecExp.buildings.transportHub.surfaceTransport === 2) {
					text.push(`The docks are modern and efficient, but limited in size. They can handle some traffic, but not sustain commercial activity of significant size.`);
					links.push(App.UI.DOM.link(`Enlarge the docks`, () => {
						V.SecExp.buildings.transportHub.surfaceTransport = 3;
						cashX(forceNeg(price(25000)), "capEx");

						App.UI.reload();
					}, [], '', `Costs ${cashFormat(price(25000))} and will increase trade, but will affect security.`));
				} else if (V.SecExp.buildings.transportHub.surfaceTransport === 3) {
					text.push(`The docks are modern, efficient and expansive. They can handle a significant amount of traffic.`);
					links.push(App.UI.DOM.link(`Further modernize the docks`, () => {
						V.SecExp.buildings.transportHub.surfaceTransport = 4;
						cashX(forceNeg(price(65000)), "capEx");

						App.UI.reload();
					}, [], '', `Costs ${cashFormat(price(65000))} and will increase trade, but will affect security.`));
				} else {
					text.push(`The docks are huge in size and high tech. They can handle an enormous amount of traffic.`);
				}

				App.Events.addNode(div, text);
				App.UI.DOM.appendNewElement("div", div, App.UI.DOM.generateLinksStrip(links), ['indent']);
				return div;
			}

			function security() {
				const div = document.createElement("div");
				const text = [];
				const links = [];

				if (V.SecExp.buildings.transportHub.security === 1) {
					text.push(`The security of the hub is limited to a few cameras and the occasional guard.`);
					links.push(App.UI.DOM.link(`Expand and modernize the surveillance system`, () => {
						V.SecExp.buildings.transportHub.security = 2;
						cashX(forceNeg(price(15000)), "capEx");

						App.UI.reload();
					}, [], '', `Costs ${cashFormat(price(15000))}, increases upkeep costs, and will increase security.`));
				} else if (V.SecExp.buildings.transportHub.security === 2) {
					text.push(`The security of the hub is guaranteed by a powerful camera surveillance system.`);
					links.push(App.UI.DOM.link(`Establish a rapid response team`, () => {
						V.SecExp.buildings.transportHub.security = 3;
						cashX(forceNeg(price(35000)), "capEx");

						App.UI.reload();
					}, [], '', `Costs ${cashFormat(price(35000))}, increases upkeep costs, and will increase security.`));
				} else if (V.SecExp.buildings.transportHub.security === 3) {
					text.push(`The security of the hub is guaranteed by a powerful camera surveillance system and a rapid response team constantly patrolling the structure.`);
					links.push(App.UI.DOM.link(`Add additional security drones to the structure`, () => {
						V.SecExp.buildings.transportHub.security = 4;
						cashX(forceNeg(price(55000)), "capEx");

						App.UI.reload();
					}, [], '', `Costs ${cashFormat(price(55000))}, increases upkeep costs, and will increase security.`));
				} else {
					text.push(`The security of the hub is guaranteed by a powerful camera surveillance system, a rapid response team constantly patrolling the building and additional security drones making the rounds around the exterior.`);
				}

				App.Events.addNode(div, text);
				App.UI.DOM.appendNewElement("div", div, App.UI.DOM.generateLinksStrip(links), ['indent']);
				return div;
			}
		}
	}
})();

repo_name: Nithhogg/fc

license: mit


code:
App.Mods.SecExp.weapManu = (function() {
	return {
		Init,
		baseTime,
		current,
		completed,
		GUI,
		// purchase - helper function
	};

	function Init() {
		V.SecExp.buildings.weapManu = {
			space: 500,
			menials: 0,
			productivity: 1,
			lab: 1,
			sellTo: {
				citizen: 1,
				raiders: 1,
				oldWorld: 1,
				FC: 1,
			},
			upgrades: {completed: []}
		};
	}

	/** Weeks to completion without any modification.
	 * @returns {number}
	 */
	function baseTime() { return 10; }

	/** Checks the supplied ID value and assigns values.
	 * @param {number} [upgradeID] passed ID
	 * @returns {object}
	 */
	function current(upgradeID) {
		switch (upgradeID) {
			case -3:
				return {
					dec: "advanced synthetic alloys", type: "hp",
					purpose: "survivability", cost: 10000*V.HackingSkillMultiplier,
					unit: "the security drones"
				};
			case -2:
				return {
					dec: "adaptive armored frames", type: "defense",
					purpose: "defensive capabilities", cost: 10000,
					unit: "the security drones"
				};
			case -1:
				return {
					dec: "dynamic battle aware AI", type: "attack",
					purpose: "attack power", cost: 30000,
					unit: "the security drones"
				};
			case 0:
				return {
					dec: "magnetic based ballistic weaponry", type: "attack",
					purpose: "attack power", cost: 30000,
					unit: "our human troops"
				};
			case 1:
				return {
					dec: "ceramo-metallic alloys", type: "defense",
					purpose: "defensive capabilities", cost: 30000,
					unit: "our human troops"
				};
			case 2:
				return {
					dec: "rapid action stimulants", type: "hp",
					purpose: "survivability", cost: 60000,
					unit: "our human troops"
				};
			case 3:
				return {
					dec: "fast response neural stimulant", type: "morale",
					purpose: "standing power", cost: 60000,
					unit: "our human troops"
				};
			case 4:
				return {
					dec: "universal cyber enhancements", type: "attack and defense",
					purpose: "offensive and defensive effectiveness", cost: 120000*V.HackingSkillMultiplier,
					unit: "our human troops"
				};
			case 5:
				return {
					dec: "remote neural links", type: "hp and morale",
					purpose: "morale and survivability", cost: 120000*V.HackingSkillMultiplier,
					unit: "our human troops"
				};
			case 6:
				return {
					dec: `combined training regimens with ${capFirstChar(V.SF.Lower)}`, type: "attack and defense",
					purpose: "offensive and defensive effectiveness", cost: 0,
					unit: "our human troops"
				};
			case 7:
				return {
					dec: `a variant of the stimulant cocktail that ${capFirstChar(V.SF.Lower)} created`, type: "hp and morale",
					purpose: "morale and survivability", cost: 300000,
					unit: "our human troops"
				};
			case 8:
				return {
					dec: `a mesh network based off the custom network of ${capFirstChar(V.SF.Lower)}`, type: "all",
					purpose: "offensive,defensive effectiveness in addition to morale and survivability", cost: 1000000*V.HackingSkillMultiplier,
					unit: "our human troops"
				};
		}
	}

	/** Checks if the class of unit is fully upgraded.
	 * @returns {boolean}
	 */
	 function completed(check = "all") {
		let human = [0, 1, 2, 3, 4, 5];
		if (V.SF.Toggle && V.SF.Active >= 1) {
			human.push(6, 7, 8);
		}

		if (check === "human") {
			return V.SecExp.buildings.weapManu.upgrades.completed.includesAll(human);
		} else if (check === "bots") {
			return V.SecExp.buildings.weapManu.upgrades.completed.includesAll(-1, -2, -3);
		}
		return V.SecExp.buildings.weapManu.upgrades.completed.includesAll(human.concat(-1, -2, -3));
	}

	function GUI() {
		const frag = new DocumentFragment();

		V.nextButton = "Back";
		V.nextLink = "Main";
		const refreshDiv = App.UI.DOM.makeElement("div", content());

		function content() {
			const div = document.createElement("div");
			div.append(
				intro(),
				menials(),
				rules(),
				upgrades(),
				research(),
			);
			return div;
		}
		frag.append(refreshDiv);
		return frag;

		function intro() {
			const div = document.createElement("div");
			const text = [];
			const units = [];

			text.push(`This sector of the arcology has been dedicated to weapons manufacturing. These factories supply`);
			if (V.SecExp.units.militia.squads) {
				units.push(`your militia`);
			}
			if (V.SecExp.units.slaves.squads) {
				units.push(`your slave soldiers`);
			}
			if (V.mercenaries) {
				units.push(`your mercenaries`);
			}

			text.push(`${toSentence([...units, `many small old world nations`])} as the advanced technology that Free Cities have available is hard to come by otherwise.`);

			App.Events.addNode(div, text, "div", ['scene-intro']);
			App.UI.DOM.appendNewElement("div", div, App.UI.DOM.passageLink(`Decommission the Weapons Manufacturing`, "Main", () => {
				delete V.SecExp.buildings.weapManu;

				App.Arcology.cellUpgrade(V.building, App.Arcology.Cell.Manufacturing, "Weapon Manufacturing", "Manufacturing");
			}), ['indent']);

			return div;
		}

		function menials() {
			const div = App.UI.DOM.makeElement("div", null, ['margin-bottom']);
			App.UI.DOM.appendNewElement("h2", div, `Menials`);
			const unitCost = Math.trunc(1000 * V.upgradeMultiplierArcology * (V.SecExp.buildings.weapManu.space/100));
			let text = [];
			// let links = [];

			// Transferring
			if (V.SecExp.buildings.weapManu.menials > 0) {
				text.push(`Assigned here ${V.SecExp.buildings.weapManu.menials === 1 ? `is` : `are`} ${numberWithPluralOne(V.SecExp.buildings.weapManu.menials, 'menial slave')}, working to produce as much equipment as they can.`);
			}
			text.push(`You ${V.menials ? `own ${num(V.menials)}` : `don't own any`} free menial slaves. This manufacturing complex is currently housing ${num(V.SecExp.buildings.weapManu.menials)} out of ${num(V.SecExp.buildings.weapManu.space)} possible menial slaves.`);
			div.append(text.join(' '));
			text = [];

			App.UI.DOM.appendNewElement("div", div, App.UI.tradeMenials(true), ['indent']);
			const optionsList = [1, 5, 10, 100, 500];
			let linkArray;
			for (const flow of ["in", "out"]) {
				linkArray = [];
				for (const value of optionsList.filter(s => (flow === "in" ? ((V.SecExp.buildings.weapManu.space - V.SecExp.buildings.weapManu.menials) >= s && V.menials >= s) : V.SecExp.buildings.weapManu.menials >= s) )) {
					linkArray.push(
						App.UI.DOM.link(
							`${value}`,
							() => {
								V.menials += (flow === "in" ? forceNeg(value) : value);
								V.SecExp.buildings.weapManu.menials += (flow === "in" ? value : forceNeg(value));
								App.UI.reload();
							}
						)
					);
				}
				if (linkArray.length > 0) {
					if (flow !== "in" || flow === "in" && V.SecExp.buildings.weapManu.menials < V.SecExp.buildings.weapManu.space) {
						const line = new DocumentFragment();
						line.append(`Transfer ${flow}: `, App.UI.DOM.generateLinksStrip(linkArray));
						App.UI.DOM.appendNewElement("div", div, line);
					}
				}
			}
			// FIXME: this is unused
			// if (V.SecExp.buildings.weapManu.menials) {
			// 	links.push(App.UI.DOM.link("Transfer out all menial slaves", () => {
			// 		V.menials += V.SecExp.buildings.weapManu.menials;
			// 		V.SecExp.buildings.weapManu.menials = 0;
			// 		App.UI.reload();
			// 	}));
			// }

			// Housing
			App.UI.DOM.appendNewElement("div", div, `There is enough room in the complex to build housing, enough to give an additional 100 workers a place to sleep and relax.`);
			App.UI.DOM.appendNewElement("div", div, App.UI.DOM.link(`Build a new housing unit`, () => {
				cashX(forceNeg(unitCost), "capEx");
				V.SecExp.buildings.weapManu.space += 100;
				App.UI.reload();
			},
			[], '', `Costs ${cashFormat(unitCost)} and increases housing by 100.`), ['indent']);

			return div;
		}

		function upgrades() {
			const div = App.UI.DOM.makeElement("div", null, ['margin-bottom']);
			/** @type {FC.Upgrade[]} */
			const upgrades = [
				new App.Upgrade(
					"productivity",
					[
						{
							value: 1,
							upgraded: 2,
							text: `Production is completely manned by human workers. The complex has close to zero automation.`,
							link: `Invest in automating the complex`,
							cost: 10000 * V.SecExp.buildings.weapManu.productivity,
							handler: () => V.PC.skill.engineering += 0.1,
							notes: [`will increase income in addition to speeding up upgrade production`],
						},
						{
							value: 2,
							upgraded: 3,
							text: `Production is mostly handled by human workers. A few of the most tiresome tasks are handled by robots.`,
							link: `Invest in automating the complex`,
							cost: 10000 * V.SecExp.buildings.weapManu.productivity,
							handler: () => V.PC.skill.engineering += 0.1,
							notes: [`will increase income in addition to speeding up upgrade production`],
						},
						{
							value: 3,
							upgraded: 4,
							text: `A good part of production is handled by robots with humans handling the most complex tasks.`,
							link: `Invest in automating the complex`,
							cost: 10000 * V.SecExp.buildings.weapManu.productivity,
							handler: () => V.PC.skill.engineering += 0.1,
							notes: [`will increase income in addition to speeding up upgrade production`],
						},
						{
							value: 4,
							upgraded: 5,
							text: `Almost all production is handled by robots, with humans acting as support for the machines.`,
							link: `Invest in automating the complex`,
							cost: 10000 * V.SecExp.buildings.weapManu.productivity,
							handler: () => V.PC.skill.engineering += 0.1,
							notes: [`will increase income in addition to speeding up upgrade production`],
						},
						{
							value: 5,
							text: `All production here is handled by robots. The few humans working in the complex occupy themselves exclusively with management and quality assurance.`,
						},
					],
					V.SecExp.buildings.weapManu,
				),
				new App.Upgrade(
					"lab",
					[
						{
							value: 1,
							upgraded: 2,
							text: `There's a very spartan lab attached to the complex that occupies itself mainly with weapons testing and small adjustments to the manufacturing process.`,
							link: `Invest in research and development`,
							cost: 10000 * V.SecExp.buildings.weapManu.lab,
							handler: () => V.PC.skill.engineering += 0.1,
							notes: [`will increase income in addition to unlocking more advanced upgrades`],
						},
						{
							value: 2,
							upgraded: 3,
							text: `There's a lab attached to the complex. It mainly test weapons effectiveness and manufacturing efficiency, but has enough equipment and personnel to develop new technology.`,
							link: `Invest in research and development`,
							cost: 10000 * V.SecExp.buildings.weapManu.lab,
							handler: () => V.PC.skill.engineering += 0.1,
							notes: [`will increase income in addition to unlocking more advanced upgrades`],
						},
						{
							value: 3,
							text: `There's a large lab attached to the complex. The complement of equipment and personnel makes it a great beacon of military science in an otherwise ignorant world.`,
						},
					],
					V.SecExp.buildings.weapManu,
				),
			];

			App.UI.DOM.appendNewElement("h2", div, `Upgrades`);
			upgrades.forEach(upgrade => div.append(upgrade.render()));
			return div;
		}

		function rules() {
			const div = App.UI.DOM.makeElement("div", null, ['margin-bottom']);
			App.UI.DOM.appendNewElement("h2", div, `Rules`);
			[
				{
					property: "citizen",
					options: [
						{
							get text() { return `We are not selling our weaponry to our citizens.`; },
							link: `Forbid`,
							value: 0,
						},
						{
							get text() { return `We are currently selling our weapons to the domestic market of the arcology.`; },
							link: `Allow`,
							value: 1,
						}
					],
					object: V.SecExp.buildings.weapManu.sellTo,
				},
				{
					property: "raiders",
					options: [
						{
							get text() { return `We are not selling our weaponry to raiders.`; },
							link: `Forbid`,
							value: 0,
						},
						{
							get text() { return `We are currently selling our weapons to various groups of outlaws, also known as raiders.`; },
							link: `Allow`,
							value: 1,
						}
					],
					object: V.SecExp.buildings.weapManu.sellTo,
				},
				{
					property: "oldWorld",
					options: [
						{
							get text() { return `We are not selling our weaponry to old world nations.`; },
							link: `Forbid`,
							value: 0,
						},
						{
							get text() { return `We are currently selling our weapons to many old world nations.`; },
							link: `Allow`,
							value: 1,
						}
					],
					object: V.SecExp.buildings.weapManu.sellTo,
				},
				{
					property: "FC",
					options: [
						{
							get text() { return `We are not selling our weaponry to other Free Cities.`; },
							link: `Forbid`,
							value: 0,
						},
						{
							get text() { return `We are currently selling our weapons to other Free Cities.`; },
							link: `Allow`,
							value: 1,
						}
					],
					object: V.SecExp.buildings.weapManu.sellTo,
				},
			].forEach(rule => {
				const options = new App.UI.OptionsGroup();
				const option = options.addOption(null, rule.property, V.SecExp.buildings.weapManu.sellTo);

				rule.options.forEach(o => {
					option.addValue(o.link, o.value);
					if (o.handler) {
						option.addCallback(o.handler);
					}
					if (o.note) {
						option.addComment(o.note);
					}

					if (_.isEqual(V.SecExp.buildings.weapManu.sellTo[rule.property], o.value)) {
						App.UI.DOM.appendNewElement("div", div, o.text);
					}
				});
				App.UI.DOM.appendNewElement("div", div, options.render(), ['indent', 'margin-bottom']);
			});
			return div;
		}

		function research() {
			const div = App.UI.DOM.makeElement("div", null, ['margin-bottom']);
			const time = Math.ceil(baseTime() / V.SecExp.buildings.weapManu.productivity);
			const completedArray = V.SecExp.buildings.weapManu.upgrades.completed;

			App.UI.DOM.appendNewElement("h2", div, `Research`);
			div.append(`With our current industrial and research capabilities, upgrades will be finished in ${time} weeks.`);
			if (completed()) {
				delete V.SecExp.buildings.weapManu.upgrades.queue;
			} else {
				V.SecExp.buildings.weapManu.upgrades.queue = V.SecExp.buildings.weapManu.upgrades.queue || [];
				if (V.SecExp.buildings.weapManu.lab < 3) {
					div.append(`Upgrade the research facility further to unlock additional upgrades.`);
				}
			}

			App.UI.DOM.appendNewElement("h3", div, `Security Drones`);
			if (completed("bots")) {
				div.append(`You have fully upgraded the security drones.`);
			} else {
				div.append(purchase(-1));
				if (V.SecExp.buildings.weapManu.lab > 1) {
					div.append(purchase(-2));
				}
				if (V.SecExp.buildings.weapManu.lab > 2) {
					div.append(purchase(-3));
				}
			}

			App.UI.DOM.appendNewElement("h3", div, `Troops`);
			if (V.SF.Toggle && V.SF.Active >= 1 &&
				(App.Mods.SecExp.getAppliedUpgrades('human').attack >= 4 ||
					App.Mods.SecExp.getAppliedUpgrades('human').hp >= 4 ||
					App.Mods.SecExp.getAppliedUpgrades('human').morale >= 40 ||
					App.Mods.SecExp.getAppliedUpgrades('human').defense >= 4)) {
				div.append(`You have fully upgraded your human troops.`);
			} else if (App.Mods.SecExp.getAppliedUpgrades('human').attack >= 2 ||
				App.Mods.SecExp.getAppliedUpgrades('human').hp >= 2 ||
				App.Mods.SecExp.getAppliedUpgrades('human').morale >= 20 ||
				App.Mods.SecExp.getAppliedUpgrades('human').defense >= 2) {
				if (V.SF.Toggle && V.SF.Active >= 1) {
					const reasons = [];
					if (V.SF.Squad.Drugs < 8) {
						reasons.push(`the drug lab needs more advanced equipment`);
					}
					if (V.SecExp.edicts.SFSupportLevel < 2 || V.SecExp.edicts.SFSupportLevel < 4 || V.SecExp.edicts.SFSupportLevel < 5) {
						reasons.push(`you need to pass higher SF support edicts`);
					}
					if (V.SF.Squad.Firebase < 7) {
						reasons.push(`the Firebase need additional expansion(s)`);
					}

					div.append(`With support from ${V.SF.Lower}, we may be able to further upgrade our troops.`);
					if (reasons.length > 0) {
						div.append(`The Colonel says ${toSentence(reasons)}.`);
					}
				} else {
					div.append(`You have fully upgraded your human troops.`);
				}
			}

			div.append(purchase(0));
			div.append(purchase(1));
			if (V.SecExp.buildings.weapManu.lab > 1) {
				div.append(purchase(2));
				div.append(purchase(3));
			}
			if (V.SecExp.buildings.weapManu.lab > 2) {
				div.append(purchase(4));
				div.append(purchase(5));
			}

			if (V.SF.Toggle && V.SF.Active > 0) {
				if (V.SecExp.buildings.weapManu.lab > 1 &&
					V.SecExp.edicts.SFSupportLevel > 1 &&
					V.SF.Squad.Firebase > 6) {
					div.append(purchase(6));
				}
				if (V.SecExp.buildings.weapManu.lab > 1 &&
					V.SecExp.edicts.SFSupportLevel > 3 &&
					V.SF.Squad.Firebase > 7) {
					div.append(purchase(7));
				}
				if (V.SecExp.buildings.weapManu.lab > 2 &&
					V.SecExp.edicts.SFSupportLevel > 4) {
					div.append(purchase(8));
				}
			}

			if (V.SecExp.buildings.weapManu.upgrades.queue) {
				App.Events.addParagraph(div, []);
				for (let i = 0; i < V.SecExp.buildings.weapManu.upgrades.queue.length; i++) {
					const upgrade = V.SecExp.buildings.weapManu.upgrades.queue[i];
					const latest = current(upgrade.ID);
					if (i === 0) {
						div.append(`Currently developing`);
					} else {
						div.append(`${ordinalSuffix(i + 1)} in queue:`);
					}
					div.append(` ${latest.dec} for ${latest.unit}. It will enhance their ${latest.purpose}. Estimated completion time is ${numberWithPluralOne(upgrade.time, "week")}.`);
					App.UI.DOM.appendNewElement("div", div);
				}
			}

			if (completedArray.length) {
				App.UI.DOM.appendNewElement("div", div, `You have completed the following projects: ${toSentence(completedArray.map(u => current(u).dec))}.`, ['margin-top']);
			}
			return div;
		}
	}

	/** Prints a line that allows a user to purchase an upgrade.
	 * helper function, not callable externally
	 * @param {number} [upgradeID] ID value to when generating.
	 * @returns {Node}
	 */
	 function purchase(upgradeID) {
		let el = document.createElement("div");
		const found = V.SecExp.buildings.weapManu.upgrades.queue && V.SecExp.buildings.weapManu.upgrades.queue.find(s => s.ID === upgradeID);
		if (!V.SecExp.buildings.weapManu.upgrades.completed.includes(upgradeID)) {
			const item = current(upgradeID);
			const time = Math.ceil(baseTime() / V.SecExp.buildings.weapManu.productivity);
			if (!found) {
				el.append(App.UI.DOM.link(`Develop ${item.dec}.`, () => {
					V.SecExp.buildings.weapManu.upgrades.queue.push({ID: upgradeID, time: time});
					cashX(-item.cost, "capEx");
					App.UI.reload();
				}
				));
				el.append(` This will take ${time} weeks`);
				if (item.cost > 0) {
					el.append(`, cost ${cashFormat(item.cost)}`);
				}
				el.append(` and will increase `);
				if (item.type !== "all") {
					el.append(`the base ${item.type} value${item.type.includes("and") ? 's' : ''}`);
				} else {
					el.append('all base stats');
				}
				el.append(` of ${item.unit}.`);
			} else {
				el.append(App.UI.DOM.link(`Remove ${item.dec} from the queue`, () => {
					V.SecExp.buildings.weapManu.upgrades.queue.deleteAt(found);
					App.UI.reload();
				}
				));
			}
		}
		return el;
	}
})();


code:
/* global V, Engine, num, random */
App.Events.conflictHandler = function() {
	V.nextButton = ' ';
	App.UI.StoryCaption.encyclopedia = 'Battles';

	const node = new DocumentFragment();
	const showStats = V.SecExp.settings.showStats === 1;
	const inBattle = V.SecExp.war.type.includes('Attack');
	const isMajorBattle = inBattle && V.SecExp.war.type.includes('Major');
	const inRebellion = V.SecExp.war.type.includes('Rebellion');
	const turns = isMajorBattle || inRebellion ? 20 : 10;
	const showProgress = function(message, tag = 'div') {
		if (showStats) {
			App.UI.DOM.appendNewElement(tag, node, message);
		}
	};
	const setResult = function(varA, varB, text, value, count) {
		if (varA <= 0 || varB <= 0) {
			showProgress(`${text}!`);
			V.SecExp.war.result = value;
			V.SecExp.war.turns = count;
		}
	};
	const atEnd = function(passage) {
		if (showStats) {
			App.UI.DOM.appendNewElement(
				'div',
				node,
				App.UI.DOM.passageLink('Proceed', passage),
			);
		} else {
			setTimeout(() => Engine.play(passage), Engine.minDomActionDelay);
		}
	};
	const turnReport = function() {
		showProgress(`Turn: ${i + 1}`);
		// player army attacks
		damage = Math.round(
			Math.clamp(
				attack.total - enemyDefense,
				attack.total * 0.1,
				attack.total,
			),
		);
		showProgress(`Player damage: ${num(Math.round(damage))}`);
		enemyHp -= damage;
		showProgress(`Remaining enemy Hp: ${num(Math.round(enemyHp))}`);
		V.SecExp.war.attacker.losses += damage / enemyBaseHp;
		enemyMorale -= Math.clamp(
			damage / 2 +
			damage / enemyBaseHp,
			0,
			damage * 1.5,
		);
		showProgress(`Remaining enemy morale: ${num(Math.round(enemyMorale))}`);
		setResult(enemyHp, enemyMorale, 'Victory', 3, i);

		// OpFor army attacks
		damage = enemyAttack - defense.total;
		if (damage < enemyAttack * 0.1) {
			damage = enemyAttack * 0.1;
		}
		damage = Math.round(damage);
		showProgress(`Enemy damage: ${num(Math.round(damage))}`);
		hp.total -=
			damage * (inRebellion && V.SecExp.rebellions.sfArmor ? 0.85 : 1);
		showProgress(`Remaining hp: ${num(Math.round(hp.total))}`);
		V.SecExp.war.losses += damage / baseHp;
		morale.total -= Math.clamp(
			damage / 2 +
			damage / baseHp,
			0,
			damage * 1.5,
		);
		showProgress(`Remaining morale: ${num(Math.round(morale.total))}`);
		setResult(hp.total, morale.total, 'Defeat', -3, i);
	};

	const attack = {base: 0, modifier: 1};
	const defense = {base: 0, modifier: 1};
	const morale = {base: 0};
	const hp = {base: 0};
	let unitData;
	let damage;
	let baseHp;
	let enemyBaseHp;
	let enemyMorale;
	let enemyAttack = 0;
	let enemyDefense = 0;
	let enemyHp = 0;
	let armyMod = V.SecExp.war.attacker.troops / (inBattle ? 80 : 100);
	const activeSF = V.SF.Toggle && V.SF.Active >= 1;
	// Battles
	morale.militia = isMajorBattle ? 1.5 : 1;
	morale.slaves = isMajorBattle ? 1.5 : 1;
	morale.mercs = isMajorBattle ? 1.5 : 1;
	morale.enemy = isMajorBattle ? 1.5 : 1;
	morale.SF = isMajorBattle ? 1.5 : 1;
	let tacChance = 0.5; // by default tactics have a 50% chance of succeeding
	// Rebellions
	let irregularMod = V.SecExp.war.irregulars / 60;
	let engageMod = 0.5; // V.SecExp.war.engageRule === 0
	let rebellingSlaves = 0;
	let rebellingMilitia = 0;

	if (inBattle && (V.SecExp.war.result === 1 || V.SecExp.war.result === -1)) {
		// bribery/surrender check
		showProgress(`${V.SecExp.war.result === 1 ? 'Bribery' : 'Surrender'} chosen`);
		if (inBattle && V.SecExp.war.result === 1) {
			if (V.cash >= App.Mods.SecExp.battle.bribeCost()) {
				// if there's enough cash there's a 10% chance bribery fails. If there isn't there's instead a 50% chance it fails
				if (
					(
						V.SecExp.war.attacker.type === 'freedom fighters' &&
						random(1, 100) <= 50
					) ||
					random(1, 100) <= 10
				) {
					V.SecExp.war.result = 0;
				}
			} else {
				if (random(1, 100) <= 50) {
					V.SecExp.war.result = 0;
				}
			}
			showProgress(`${V.SecExp.war.result === 0 ? 'Failed' : 'Successful'}!`, 'span');
			atEnd('conflictReport');
			return node;
		}
	}

	if (inBattle) {
		if (isMajorBattle) {
			if (activeSF) {
				if (V.SF.Squad.Firebase >= 7) {
					attack.modifier += (V.SF.Squad.Firebase - 6) * 0.05;
				}
				if (V.SF.Squad.GunS >= 1) {
					defense.modifier += V.SF.Squad.GunS * 0.05;
				}
				if (V.SF.Squad.Satellite >= 5 && V.SF.SatLaunched > 0) {
					attack.modifier += (V.SF.Squad.Satellite - 5) * 0.05;
				}
				if (V.SF.Squad.GiantRobot >= 6) {
					defense.modifier += (V.SF.Squad.GiantRobot - 5) * 0.05;
				}
				if (V.SF.Squad.MissileSilo >= 1) {
					attack.modifier += V.SF.Squad.MissileSilo * 0.05;
				}
			}
		}

		const commanderEffectiveness = App.Mods.SecExp.commanderEffectiveness('handler');
		morale.slaves += commanderEffectiveness.slaveMod;
		morale.militia += commanderEffectiveness.militiaMod;
		morale.mercs += commanderEffectiveness.militiaMod;
		morale.SF += commanderEffectiveness.SFMod;
		morale.enemy += commanderEffectiveness.enemyMod;
		attack.modifier += commanderEffectiveness.atkMod;
		defense.modifier += commanderEffectiveness.defMod;
		tacChance += commanderEffectiveness.tacChance;

		// Terrain and Tactics
		const tacticsObj = App.Data.SecExp.TerrainAndTactics.get(V.SecExp.war.terrain)[V.SecExp.war.chosenTactic];
		attack.modifier += tacticsObj.atkMod;
		defense.modifier += tacticsObj.defMod;
		tacChance += tacticsObj.tacChance;

		if (V.SecExp.war.chosenTactic === 'Bait and Bleed') {
			if (V.SecExp.war.attacker.type === 'raiders') {
				tacChance -= 0.1;
			} else if (V.SecExp.war.attacker.type === 'free city') {
				tacChance += 0.1;
			} else if (V.SecExp.war.attacker.type === 'old world') {
				tacChance += 0.25;
			} else if (V.SecExp.war.attacker.type === 'freedom fighters') {
				tacChance -= 0.15;
			}
		} else if (V.SecExp.war.chosenTactic === 'Guerrilla') {
			if (V.SecExp.war.attacker.type === 'raiders') {
				tacChance -= 0.2;
			} else if (V.SecExp.war.attacker.type === 'free city') {
				tacChance += 0.15;
			} else if (V.SecExp.war.attacker.type === 'old world') {
				tacChance += 0.25;
			} else if (V.SecExp.war.attacker.type === 'freedom fighters') {
				tacChance -= 0.25;
			}
		} else if (V.SecExp.war.chosenTactic === 'Choke Points') {
			if (V.SecExp.war.attacker.type === 'raiders') {
				tacChance += 0.25;
			} else if (V.SecExp.war.attacker.type === 'free city') {
				tacChance -= 0.05;
			} else if (V.SecExp.war.attacker.type === 'old world') {
				tacChance -= 0.1;
			} else if (V.SecExp.war.attacker.type === 'freedom fighters') {
				tacChance += 0.05;
			}
		} else if (V.SecExp.war.chosenTactic === 'Interior Lines') {
			if (V.SecExp.war.attacker.type === 'raiders') {
				tacChance -= 0.15;
			} else if (V.SecExp.war.attacker.type === 'free city') {
				tacChance += 0.15;
			} else if (V.SecExp.war.attacker.type === 'old world') {
				tacChance += 0.2;
			} else if (V.SecExp.war.attacker.type === 'freedom fighters') {
				tacChance -= 0.1;
			}
		} else if (V.SecExp.war.chosenTactic === 'Pincer Maneuver') {
			if (V.SecExp.war.attacker.type === 'raiders') {
				tacChance += 0.15;
			} else if (V.SecExp.war.attacker.type === 'free city') {
				tacChance += 0.1;
			} else if (V.SecExp.war.attacker.type === 'old world') {
				tacChance -= 0.1;
			} else if (V.SecExp.war.attacker.type === 'freedom fighters') {
				tacChance += 0.15;
			}
		} else if (V.SecExp.war.chosenTactic === 'Defense In Depth') {
			if (V.SecExp.war.attacker.type === 'raiders') {
				tacChance -= 0.2;
			} else if (V.SecExp.war.attacker.type === 'free city') {
				tacChance += 0.1;
			} else if (V.SecExp.war.attacker.type === 'old world') {
				tacChance += 0.2;
			} else if (V.SecExp.war.attacker.type === 'freedom fighters') {
				tacChance -= 0.05;
			}
		} else if (V.SecExp.war.chosenTactic === 'Blitzkrieg') {
			if (V.SecExp.war.attacker.type === 'raiders') {
				tacChance += 0.1;
			} else if (V.SecExp.war.attacker.type === 'free city') {
				tacChance -= 0.2;
			} else if (V.SecExp.war.attacker.type === 'old world') {
				tacChance += 0.25;
			} else if (V.SecExp.war.attacker.type === 'freedom fighters') {
				tacChance -= 0.1;
			}
		} else if (V.SecExp.war.chosenTactic === 'Human Wave') {
			if (V.SecExp.war.attacker.type === 'raiders') {
				tacChance -= 0.1;
			} else if (V.SecExp.war.attacker.type === 'free city') {
				tacChance += 0.1;
			} else if (V.SecExp.war.attacker.type === 'old world') {
				tacChance -= 0.15;
			} else if (V.SecExp.war.attacker.type === 'freedom fighters') {
				tacChance += 0.1;
			}
		}
		tacChance = Math.clamp(tacChance, 0.1, tacChance); // Calculates if tactics are successful - minimum chance is 10%

		if (random(1, 100) <= tacChance * 100) {
			morale.enemy -= 0.3;
			morale.militia += 0.2;
			morale.slaves += 0.2;
			morale.mercs += 0.2;
			attack.modifier += 0.1;
			defense.modifier += 0.1;
			V.SecExp.war.tacticsSuccessful = 1;
		} else {
			morale.enemy += 0.2;
			morale.militia -= 0.2;
			morale.slaves -= 0.2;
			morale.mercs -= 0.2;
			attack.modifier -= 0.1;
			defense.modifier -= 0.1;
		}

		// enemy morale mods
		if (V.week < 30) {
			morale.enemy += 0.15;
		} else if (V.week < 60) {
			morale.enemy += 0.3;
		} else if (V.week < 90) {
			morale.enemy += 0.45;
		} else if (V.week < 120) {
			morale.enemy += 0.6;
		} else {
			morale.enemy += 0.75;
		}
	}

	// calculates PC army stats
	if (inRebellion) {
		if (V.SecExp.war.engageRule === 1) {
			engageMod = 0.75;
		} else if (V.SecExp.war.engageRule === 2) {
			engageMod = 1;
		} else if (V.SecExp.war.engageRule > 2) {
			engageMod = 1.4;
		}

		if (V.week > 30 && V.week <= 60) {
			irregularMod = V.SecExp.war.irregulars / 50;
		} else if (V.week <= 90) {
			irregularMod = V.SecExp.war.irregulars / 40;
		} else if (V.week <= 120) {
			irregularMod = V.SecExp.war.irregulars / 30;
		} else {
			irregularMod = V.SecExp.war.irregulars / 20;
		}
		if (V.SecExp.war.irregulars > 0) {
			irregularMod = Math.trunc(irregularMod);
			unitData = App.Mods.SecExp.getIrregularUnit(
				'militia',
				V.SecExp.war.irregulars,
				V.SecExp.war.attacker.equip,
			);
			attack.irregulars = unitData.attack * irregularMod * 0.8;
			defense.irregulars = unitData.defense * irregularMod * 0.8;
			hp.irregulars = unitData.hp;
		}
	}

	for (const type of Array.from(App.Mods.SecExp.unit.list().keys())) {
		for (const unit of V.SecExp.units[type].squads) {
			if (App.Mods.SecExp.unit.isDeployed(unit)) {
				unitData = App.Mods.SecExp.getUnit(type, unit);
				attack.base += unitData.attack;
				defense.base += unitData.defense;
				hp.base += unitData.hp;
			}
		}
	}

	if (activeSF && ((inBattle && V.SecExp.war.deploySF) || inRebellion)) {
		unitData = App.Mods.SecExp.getUnit('SF');
		attack.SF = unitData.attack;
		defense.SF = unitData.defense;
		hp.SF = unitData.hp;
	}

	if (inRebellion) {
		for (const zone of ['assistant', 'reactor', 'penthouse', 'waterway']) {
			if (V.SecExp.war[zone + 'Defense']) {
				attack.base *= 0.95;
				defense.base *= 0.95;
				hp.base *= 0.95;
			}
		}
	}

	// morale and baseHp calculation
	if (inBattle) {
		// minimum modifier is -50%, maximum is +50%
		morale.slaves = Math.clamp(morale.slaves, 0.5, 1.5);
		morale.militia = Math.clamp(morale.militia, 0.5, 1.5);
		morale.mercs = Math.clamp(morale.mercs, 0.5, 1.5);
		morale.SF = Math.clamp(morale.SF, 0.5, 1.5);
	}
	const moraleTroopMod = Math.clamp(
		App.Mods.SecExp.battle.troopCount() / 100,
		1,
		inBattle ? 5 : 10,
	);
	const modifierSF = activeSF ? 1 : 0;

	if (inBattle) {
		morale.total =
			(
				App.Mods.SecExp.BaseDroneUnit.morale *
					App.Mods.SecExp.battle.deployedUnits('bots') +
				App.Mods.SecExp.BaseMilitiaUnit.morale *
					morale.militia *
					App.Mods.SecExp.battle.deployedUnits('militia') +
				App.Mods.SecExp.BaseSlaveUnit.morale *
					morale.slaves *
					App.Mods.SecExp.battle.deployedUnits('slaves') +
				App.Mods.SecExp.BaseMercUnit.morale *
					morale.mercs *
					App.Mods.SecExp.battle.deployedUnits('mercs') +
				App.Mods.SecExp.BaseSpecialForcesUnit.morale *
					V.SecExp.war.deploySF *
					morale.SF
			) /
			(
				App.Mods.SecExp.battle.deployedUnits('bots') +
				App.Mods.SecExp.battle.deployedUnits('militia') +
				App.Mods.SecExp.battle.deployedUnits('slaves') +
				App.Mods.SecExp.battle.deployedUnits('mercs') +
				V.SecExp.war.deploySF
			);
		if (V.SecExp.buildings.barracks) {
			morale.total += morale.total * V.SecExp.buildings.barracks.luxury * 0.05; // barracks bonus
		}
	} else {
		morale.total =
			(
				App.Mods.SecExp.BaseDroneUnit.morale *
					App.Mods.SecExp.battle.deployedUnits('bots') +
				App.Mods.SecExp.BaseMilitiaUnit.morale *
					App.Mods.SecExp.battle.deployedUnits('militia') +
				App.Mods.SecExp.BaseSlaveUnit.morale *
					App.Mods.SecExp.battle.deployedUnits('slaves') +
				App.Mods.SecExp.BaseMercUnit.morale *
					App.Mods.SecExp.battle.deployedUnits('mercs') +
				App.Mods.SecExp.BaseSpecialForcesUnit.morale *
					modifierSF
			) /
			(
				App.Mods.SecExp.battle.deployedUnits('bots') +
				App.Mods.SecExp.battle.deployedUnits('militia') +
				App.Mods.SecExp.battle.deployedUnits('slaves') +
				App.Mods.SecExp.battle.deployedUnits('mercs') +
				modifierSF
			);
		morale.total +=
			morale.total *
				(V.SecExp.buildings.barracks
					? V.SecExp.buildings.barracks.luxury * 0.05
					: 1); // barracks bonus
	}
	morale.total *= moraleTroopMod;
	if (inBattle) {
		baseHp =
			(
				App.Mods.SecExp.BaseDroneUnit.hp *
					App.Mods.SecExp.battle.deployedUnits('bots') +
				App.Mods.SecExp.BaseMilitiaUnit.hp *
					App.Mods.SecExp.battle.deployedUnits('militia') +
				App.Mods.SecExp.BaseSlaveUnit.hp *
					App.Mods.SecExp.battle.deployedUnits('slaves') +
				App.Mods.SecExp.BaseMercUnit.hp *
					App.Mods.SecExp.battle.deployedUnits('mercs') +
				App.Mods.SecExp.BaseSpecialForcesUnit.hp *
					V.SecExp.war.deploySF
			) /
			(
				App.Mods.SecExp.battle.deployedUnits('bots') +
				App.Mods.SecExp.battle.deployedUnits('militia') +
				App.Mods.SecExp.battle.deployedUnits('slaves') +
				App.Mods.SecExp.battle.deployedUnits('mercs') +
				V.SecExp.war.deploySF
			);
	} else {
		baseHp =
			(
				App.Mods.SecExp.BaseDroneUnit.hp *
					App.Mods.SecExp.battle.deployedUnits('bots') +
				App.Mods.SecExp.BaseMilitiaUnit.hp *
					App.Mods.SecExp.battle.deployedUnits('militia') +
				App.Mods.SecExp.BaseSlaveUnit.hp *
					App.Mods.SecExp.battle.deployedUnits('slaves') +
				App.Mods.SecExp.BaseMercUnit.hp *
					App.Mods.SecExp.battle.deployedUnits('mercs') +
				App.Mods.SecExp.BaseSpecialForcesUnit.hp *
					modifierSF
			) /
			(
				App.Mods.SecExp.battle.deployedUnits('bots') +
				App.Mods.SecExp.battle.deployedUnits('militia') +
				App.Mods.SecExp.battle.deployedUnits('slaves') +
				App.Mods.SecExp.battle.deployedUnits('mercs') +
				modifierSF
			);
	}

	// calculates opposing army stats
	if (V.week > 30 && V.week <= 60) {
		armyMod = V.SecExp.war.attacker.troops / (inBattle ? 75 : 90);
	} else if (V.week <= 90) {
		armyMod = V.SecExp.war.attacker.troops / (inBattle ? 70 : 80);
	} else if (V.week <= 120) {
		armyMod = V.SecExp.war.attacker.troops / (inBattle ? 65 : 70);
	} else if (V.week > 120) {
		armyMod = V.SecExp.war.attacker.troops / 60;
	}
	armyMod = Math.trunc(armyMod);
	if (isMajorBattle) {
		armyMod *= 2;
	}
	if (inBattle && armyMod <= 0) {
		armyMod = 1;
	}

	if (inRebellion) {
		if (V.SecExp.war.type.includes('Slave')) {
			rebellingSlaves = 1;
			unitData = App.Mods.SecExp.getIrregularUnit(
				'slaves',
				V.SecExp.war.attacker.troops,
				V.SecExp.war.attacker.equip,
			);
		} else {
			rebellingMilitia = 1;
			unitData = App.Mods.SecExp.getIrregularUnit(
				'militia',
				V.SecExp.war.attacker.troops,
				V.SecExp.war.attacker.equip,
			);
		}
		enemyAttack += unitData.attack * armyMod;
		enemyDefense += unitData.defense * armyMod;
		enemyHp += unitData.hp;

		for (const [type] of Array.from(App.Mods.SecExp.unit.list()).slice(1)) {
			if (
				(rebellingSlaves === 1 && type === 'slaves') ||
				(rebellingMilitia === 1 && type === 'militia')
			) {
				for (const unit of V.SecExp.units[type].squads) {
					if (V.SecExp.war.rebellingID.includes(unit.ID)) {
						V.SecExp.war.attacker.troops += unit.troops;
						unit.loyalty = 0;
						unitData = App.Mods.SecExp.getUnit(type, unit);
						enemyAttack += unitData.attack;
						enemyDefense += unitData.defense;
						enemyHp += unitData.hp;
					}
				}
			}
		}
	}

	// calculates opposing army stats
	const enemyMoraleTroopMod = Math.clamp(
		V.SecExp.war.attacker.troops / 100,
		1,
		inBattle ? 5 : 10,
	);
	if (inBattle) {
		unitData = App.Mods.SecExp.getEnemyUnit(
			V.SecExp.war.attacker.type,
			V.SecExp.war.attacker.troops,
			V.SecExp.war.attacker.equip,
		);
		enemyAttack = unitData.attack * armyMod;
		enemyDefense = unitData.defense * armyMod;
		enemyMorale = unitData.morale * morale.enemy * enemyMoraleTroopMod;
		enemyHp = unitData.hp;
		enemyBaseHp = unitData.hp / V.SecExp.war.attacker.troops;
	} else {
		enemyMorale =
			(
				1.5 *
				(
					App.Mods.SecExp.BaseMilitiaUnit.morale *
						rebellingMilitia +
					App.Mods.SecExp.BaseSlaveUnit.morale *
						rebellingSlaves
				)
			) /
			(rebellingMilitia + rebellingSlaves);
		enemyMorale *= enemyMoraleTroopMod;
		enemyBaseHp =
			(
				App.Mods.SecExp.BaseMilitiaUnit.hp *
					rebellingMilitia +
				App.Mods.SecExp.BaseSlaveUnit.hp *
					rebellingSlaves
			) /
			(rebellingMilitia + rebellingSlaves);
	}
	enemyMorale = Math.round(enemyMorale);

	attack.total = Math.round(Object.values(attack).reduce((a, b) => a + b));
	defense.total = Math.round(Object.values(defense).reduce((a, b) => a + b));
	hp.total = Math.round(Object.values(hp).reduce((a, b) => a + b));
	morale.total = Math.round(Object.values(hp).reduce((a, b) => a + b));

	for (const variable of [
		attack.total,
		defense.total,
		hp.total,
		morale.total,
		enemyAttack,
		enemyDefense,
		enemyHp,
		enemyMorale,
		enemyBaseHp,
	]) {
		if (isNaN(variable)) {
			throw Error(
				'Value: A key variable is NaN, please report this along with an affected save.',
			);
		}
	}

	enemyAttack *= V.SecExp.settings.difficulty;
	enemyDefense *= V.SecExp.settings.difficulty;
	enemyMorale *= V.SecExp.settings.difficulty;
	enemyHp *= V.SecExp.settings.difficulty;
	enemyBaseHp *= V.SecExp.settings.difficulty;

	if (showStats) {
		if (inBattle) {
			attack.modifier = Math.round((attack.modifier - 1) * 100);
			defense.modifier = Math.round((defense.modifier - 1) * 100);
			morale.militia = Math.round((morale.militia - 1) * 100);
			morale.mercs = Math.round((morale.mercs - 1) * 100);
			morale.slaves = Math.round((morale.slaves - 1) * 100);
			morale.SF = Math.round((morale.SF - 1) * 100);
			morale.enemy = Math.round((morale.enemy - 1) * 100);
		} else {
			engageMod = Math.round((engageMod - 1) * 100);
		}

		let difficultyText;
		if (V.SecExp.settings.difficulty === 0.5) {
			difficultyText = 'Very easy';
		} else if (V.SecExp.settings.difficulty === 0.75) {
			difficultyText = 'Easy';
		} else if (V.SecExp.settings.difficulty === 1) {
			difficultyText = 'Normal';
		} else if (V.SecExp.settings.difficulty === 1.25) {
			difficultyText = 'Hard';
		} else if (V.SecExp.settings.difficulty === 1.5) {
			difficultyText = 'Very hard';
		} else {
			difficultyText = 'Extremely hard';
		}
		App.UI.DOM.appendNewElement(
			'div',
			node,
			`Difficulty: ${difficultyText}. Modifier: x${V.SecExp.settings.difficulty}`,
		);

		App.UI.DOM.appendNewElement('div', node, 'Army', ['underline']);
		App.UI.DOM.appendNewElement('div', node, `Deployed troops: ${num(App.Mods.SecExp.battle.troopCount())}`);
		App.UI.DOM.appendNewElement('div', node, `Attack: ${num(attack.total)}.`);
		if (attack.SF) {
			App.UI.DOM.appendNewElement('div', node, `Base: ${attack.base}`, ['indent']);
			App.UI.DOM.appendNewElement('div', node, `SF Bonus: ${attack.SF}`, ['indent']);
		}
		App.UI.DOM.appendNewElement(
			'div',
			node,
			`Defense: ${num(Math.round(defense.total))}.`,
		);
		if (defense.SF) {
			App.UI.DOM.appendNewElement('div', node, `Base: ${defense.base}`, ['indent']);
			App.UI.DOM.appendNewElement('div', node, `SF Bonus: ${defense.SF}`, ['indent']);
		}
		if (inRebellion) {
			App.UI.DOM.appendNewElement(
				'div',
				node,
				`Engagement rule modifier: +${engageMod}%`,
			);
		}
		App.UI.DOM.appendNewElement(
			'div',
			node,
			`HP: ${num(Math.round(hp.total))}.`,
		);
		if (hp.SF) {
			App.UI.DOM.appendNewElement('div', node, `Base: ${hp.base}`, ['indent']);
			App.UI.DOM.appendNewElement('div', node, `SF Bonus: ${hp.SF}`, ['indent']);
		}
		App.UI.DOM.appendNewElement(
			'div',
			node,
			`Morale: ${num(Math.round(morale.total))}.`,
		);
		if (inBattle) {
			App.UI.DOM.appendNewElement(
				'div',
				node,
				`Slaves morale modifier: +${morale.slaves}%`,
				['indent'],
			);
			App.UI.DOM.appendNewElement(
				'div',
				node,
				`Militia morale modifier: +${morale.militia}%`,
				['indent'],
			);
			App.UI.DOM.appendNewElement(
				'div',
				node,
				`Mercenaries morale modifier: +${morale.mercs}%`,
				['indent'],
			);
			if (activeSF && V.SecExp.war.deploySF) {
				App.UI.DOM.appendNewElement(
					'div',
					node,
					`Special Force morale modifier: +${morale.SF}%`,
					['indent'],
				);
			}
			if (
				V.SecExp.buildings.barracks &&
				V.SecExp.buildings.barracks.luxury >= 1
			) {
				App.UI.DOM.appendNewElement(
					'div',
					node,
					`Barracks bonus morale modifier: +${V.SecExp.buildings.barracks.luxury * 5}%`,
					['indent'],
				);
			}
		}
		if (inBattle) {
			App.UI.DOM.appendNewElement('div', node, 'Tactics', ['underline']);
			App.UI.DOM.appendNewElement(
				'div',
				node,
				`Chance of success: ${num(Math.round(tacChance * 100))}%. Was successful?: ${V.SecExp.war.tacticsSuccessful ? 'Yes' : 'No'}`,
			);
		}

		App.UI.DOM.appendNewElement('p', node);
		App.UI.DOM.appendNewElement(
			'div',
			node,
			`${inBattle ? 'Enemy' : 'Rebels'}`,
			['underline'],
		);
		App.UI.DOM.appendNewElement(
			'div',
			node,
			`Troops: ${num(Math.round(V.SecExp.war.attacker.troops))}`,
		);
		App.UI.DOM.appendNewElement(
			'div',
			node,
			`Attack: ${num(Math.round(enemyAttack))}`,
		);
		App.UI.DOM.appendNewElement(
			'div',
			node,
			`Defense: ${num(Math.round(enemyDefense))}`,
		);
		App.UI.DOM.appendNewElement(
			'div',
			node,
			`HP: ${num(Math.round(enemyHp))}. Base: ${num(Math.round(enemyBaseHp))}`,
		);
		App.UI.DOM.appendNewElement(
			'div',
			node,
			`Morale: ${num(Math.round(enemyMorale))}.`,
		);
		if (inBattle) {
			App.UI.DOM.appendNewElement(
				'div',
				node,
				// Allowing displaying 'Rebels' is just future-proofing. Currently that branch will never be taken.
				`${inBattle ? 'Enemy' : 'Rebels'} morale modifier: +${morale.enemy}%`,
				['indent'],
			);
			App.UI.DOM.appendNewElement(
				'div',
				node,
				`${inBattle ? 'Enemy' : 'Rebels'} morale extra modifier due to troop numbers: +${Math.round((enemyMoraleTroopMod-1) * 100)}%`,
				['indent'],
			);
		}
	}

	let i = 0; // simulates the combat by pitting attack against defense
	while (i < turns && ![3, -3].includes(V.SecExp.war.result)) {
		App.UI.DOM.appendNewElement('p', node, turnReport());
		i++;
	}

	if (![3, -3].includes(V.SecExp.war.result)) {
		showProgress(`Partial ${morale.total > enemyMorale ? 'victory' : 'defeat'}!`);
		V.SecExp.war.result = morale.total > enemyMorale ? 2 : -2;
	}

	if (V.SecExp.war.result > 3 || V.SecExp.war.result < -3) {
		throw Error('Failed to determine battle result');
	}

	if (inBattle && showStats) {
		App.UI.DOM.appendNewElement(
			'div',
			node,
			`Losses: ${num(Math.trunc(V.SecExp.war.losses))}`,
		);
		App.UI.DOM.appendNewElement(
			'div',
			node,
			`Enemy losses: ${num(Math.trunc(V.SecExp.war.attacker.losses))}`,
		);
	}

	if (
		V.SecExp.war.result === -3 &&
		(
			(isMajorBattle && V.SecExp.settings.battle.major.gameOver === 1) ||
			(inRebellion && V.SecExp.settings.rebellion.gameOver === 1)
		)
	) {
		V.temp.gameover = `${isMajorBattle ? 'major battle' : 'Rebellion'} defeat`;
		atEnd('Gameover');
	} else {
		atEnd('conflictReport');
	}
	return node;
};