code:
:: WC [nobr]

<<set _colonelTalkTensionRuction = $SF.FS.Tension !== -1 ? 5 : 0>>

<<if $SF.Gift === 0 && $SF.MercCon.CanAttend === -1 && ($SF.Colonel.Talk + $SF.Colonel.Fun !== 1)>>
	The Colonel looks down a list on her tablet. "There's some things we can do to help you out, boss.

We've had some good prizes turn up, that's made us some extra money we could turn over. | <<link "Request cash" "Firebase">>
		/*<<set $CashGift = ((Math.ceil((Math.abs($cash)*0.05)*(Math.max(0.99,$SF.Size))))*($arcologies[0].prosperity/100))*_Env>> OLD*/
		<<set $CashGift = Math.ceil(25000*($SF.Size/10)*_Env),$SF.Gift = 1,$choice = 1>>
		<<set $CashGift = ($CashGift > 5000 ? $CashGift : 5000)>>
		<<run cashX($CashGift, "specialForces")>>
	<</link>>
	<<if $rep < 20000>>

If you want we could throw a quick military parade, get the people feeling extra patriotic. | <<link "Request military parade" "Firebase">>
			<<set $GoodWords1 += 50*(Math.ceil($SF.Size*0.03*_Env))>>
			<<set $GoodWords1 = (Number($GoodWords1) ? $GoodWords1 : 500),$SF.Gift = 1>>
			<<set $choice = 2>>
			<<run repX($GoodWords1, "specialForces")>>
		<</link>>
	<</if>>
	<<if $arcologies[0].prosperity < $AProsperityCap>>

Or we could hit some businesses that rival the ones in $arcologies[0].name with some sabotage. | <<link "Request sabotage" "Firebase">>
			<<set $GoodWords2 = _EnvProsp+(Math.ceil($SF.Size/100*_Env)),$SF.Gift = 1,$choice = 3>>
			<<if $arcologies[0].prosperity + $GoodWords2 > $AProsperityCap>>
				<<set $arcologies[0].prosperity = $AProsperityCap>>
			<<else>>
				<<set $arcologies[0].prosperity += $GoodWords2>>
			<</if>>
		<</link>>
	<</if>>
<<elseif $SF.Gift == 0 && ($SF.MercCon.CanAttend > -1 || ($SF.Colonel.Talk + $SF.Colonel.Fun > 0))>>

He looks down a list on his tablet. "<<= properTitle()>>, how can $SF.Lower help you this week?

 <<= App.SF.Caps()>> can spare some profits from our recent operations. | <<link "Request cash" "Firebase">>
		<<set $CashGift = 25000*($SF.Size/10)*_Env,$SF.Gift = 1,$choice = 1>>
		<<set $CashGift = ($CashGift > 5000 ? $CashGift : 5000)>>
		<<run cashX(Math.ceil($CashGift * 0.8), "specialForces")>>
	<</link>>
	<<if $rep < 20000>>

We can set some units aside for a ceremonial march through the arcology. | <<link "Request a parade" "Firebase">>
			<<set $GoodWords1 += 50*(Math.ceil($SF.Size*0.03*_Env))>>
			<<set $GoodWords1 = (Number($GoodWords1) ? $GoodWords1 : 500),$SF.Gift = 1>>
			<<set $choice = 2>>
			<<run repX(Math.ceil($GoodWords1*0.8), "specialForces")>>
		<</link>>
	<</if>>
	<<if $arcologies[0].prosperity < $AProsperityCap>>

Or we can target rival businesses for sabotage. | <<link "Request sabotage" "Firebase">>
			<<set $GoodWords2 = _EnvProsp+(Math.ceil($SF.Size/100*_Env)),$SF.Gift = 1,$choice = 3>>
			<<if $arcologies[0].prosperity + $GoodWords2 * 0.8 > $AProsperityCap>>
				<<set $arcologies[0].prosperity = $AProsperityCap>>
			<<else>>
				<<set $arcologies[0].prosperity += Math.ceil($GoodWords2*0.8)>>
			<</if>>
		<</link>>
	<</if>>
<</if>>

<<if $SF.Colonel.Talk === 0 && $SF.Colonel.Fun === 0 && $SF.MercCon.CanAttend === -1>>

"If you need me for anything else, let me know."
	<<= App.SF.fsIntegration()>>
	<<if $SF.MercCon.History >= 1>>

		<<link "Mercenary meetup report">>
			<<replace "#result0">>

While at the recent merc meetup, The Colonel made @@.yellowgreen;<<print cashFormat(Math.ceil($SF.MercCon.Income))>>@@ selling generic schematics to her friends, <<print num($SF.MercCon.Menials)>> menial slaves were won in a poker game, and <<print num($SF.MercCon.Mercs)>> mercenaries were persuaded to join $SF.Lower.

Total earnings thus far: @@.yellowgreen;<<print cashFormat(Math.ceil($SF.MercCon.Revenue))>>@@ in income, <<print num($SF.MercCon.TotalMenials)>> menial slaves and <<print num($SF.MercCon.TotalMercs)>> mercenaries joined across $SF.MercCon.History meetups.
			<</replace>>
		<</link>>
	<</if>>

	<<if $SF.Colonel.Status >= 25>>

<<link "Walk with The Colonel on the surface">>
			<<replace "#result0">>
			<<set $SF.Colonel.Talk = 1, $SF.Colonel.Status += 2, $SF.FS.Tension -= _colonelTalkTensionRuction>>

You ask The Colonel if she would like to stretch her legs up on the surface. It doesn't take much effort for her to agree.
			<<if $PC.skill.warfare >= 100 && $PC.career == "mercenary">>
				Your mastery of wet work and prior experience in a PMC satisfies The Colonel that between you<<if $Bodyguard != 0>>, $Bodyguard.slaveName,<</if>> and her, there should be little threat to walking around the arcology. Being able to see and interact with the arcology owner directly maintains the false idea that you're just like one of them while also giving them an increased opportunity to try gaining your favor.
				<<run repX(10, "specialForces")>>
				<<run cashX(_EnvCash2, "specialForces")>>
			<<elseif $PC.skill.warfare >= 100>>
				Your mastery of wet work satisfies The Colonel that you only need two soldiers <<if $Bodyguard != 0>> plus $Bodyguard.slaveName<</if>> to walk safely around the arcology. Being able to see and interact with the arcology owner directly maintains the false idea that you're just like one of them while also giving them an increased opportunity to try gaining your favor.
				<<run repX(5, "specialForces")>>
				<<run cashX(_EnvCash3, "specialForces")>>
			<<elseif $PC.skill.warfare >= 60>>
				With some expertise in warfare, The Colonel believes <<if $Bodyguard != 0>>with $Bodyguard.slaveName <</if>>you only need a squad of armed soldiers for a walk through the arcology.
			<<elseif $PC.skill.warfare >= 30>>
				As you have some skill in warfare, The Colonel believes<<if $Bodyguard != 0>> with $Bodyguard.slaveName<</if>> you only need two full squads of armed soldiers for a walk around the arcology.
			<<elseif $PC.skill.warfare >= 10>>
				Your minor skill in warfare convinces The Colonel that <<if $Bodyguard != 0>>in addition to $Bodyguard.slaveName, <</if>>you need two full squads of armed soldiers and an armored car escort for a simple walk around the arcology.
			<<else>>
				Your complete lack of combat skill convinces The Colonel that <<if $Bodyguard != 0>>in addition to $Bodyguard.slaveName, <</if>>you need two full squads of armed soldiers, an armored car escort, and a sniper overwatch for a simple walk around the arcology.
			<</if>>

As you make your way through the arcology you stop at a
			<<if $arcologies[0].FSPaternalist != "unset">>
				paternalist shop, <<if $SF.Colonel.Core == "cruel">>earning a sneer from The Colonel<<else>>helping The Colonel select some luxurious and relaxing slave treatments<</if>>.
			<<elseif $arcologies[0].FSPastoralist != "unset">>
				pastoralist shop, helping The Colonel select a more comfortable breast pump.
			<<else>>
				shop that catches The Colonel's eye.
			<</if>>
			<<if $PC.skill.slaving >= 100 && $PC.career == "slaver">>
				Your mastery and extensive history of slaving allows you to assist The Colonel greatly. The shop owner is so impressed by your understanding of slavery that she asks you for some advice. Before you leave, you manage to pass on a few tips, helping the business with future customers.
				<<if $arcologies[0].prosperity < $AProsperityCap>>
					<<set $arcologies[0].prosperity++>>
				<</if>>
			<<elseif $PC.skill.slaving >= 100>>
				Your mastery and extensive history of slaving allows you to assist The Colonel greatly. The shop owner is so impressed by your understanding of slavery that she asks you for some advice. Before you leave, you manage to pass on a few tips, helping the business with future customers.
				<<if $arcologies[0].prosperity < $AProsperityCap>>
					<<set $arcologies[0].prosperity++>>
				<</if>>
			<<elseif $PC.skill.slaving >= 60>>
				Your expertise in slavery allows you to help The Colonel decide what to buy for her main slave.
			<<elseif $PC.skill.slaving >= 30>>
				Your moderate skill in slavery makes you somewhat helpful to The Colonel in deciding what to buy for her main slave.
			<<elseif $PC.skill.slaving >= 10>>
				Your basic skill level of slavery doesn't allow you to help The Colonel at all.
			<<elseif $PC.skill.slaving < 10>>
				Your total lack of slavery skill (which is very unusual and very concerning for an arcology owner) means that you are of little to no help or even a hindrance. The shopkeeper notices your complete ineptitude, and as soon as you've left the rumor mill begins.
				<<run repX(-20, "PCactions")>>
			<</if>>

Soon the entourage heads back to the HQ of $SF.Lower.
			<<if random(1,100) > 50>>
				Along the route you see a homeless citizen with a serious injury begging for help.
				<<if $PC.skill.medicine >= 100 && $PC.career == "medicine">>
					Your expertise in surgery ensures that the citizen receives the best care they'll ever experience in their life. They are so grateful that they are more than happy to try and compensate your time. Word quickly spreads of the kindly medically trained arcology owner who took the time to heal a citizen, providing confidence to the rest of the citizens.
					<<run repX(10, "specialForces")>>
					<<run cashX(_EnvCash4, "specialForces")>>
				<<elseif $PC.skill.medicine >= 100>>
					Your expertise in surgery ensures that the citizen receives the best care they'll ever experience in their life. Word quickly spreads of the kindly arcology owner who took the time to heal a citizen.
					<<run repX(5, "specialForces")>>
				<<elseif $PC.skill.medicine >= 60>>
					Your proficiency in surgery allows you to properly close their wound with minimal trauma to the patient.
				<<elseif $PC.skill.medicine >= 30>>
					Your moderate surgical skill ensures that you can close the citizen's wound, though not without likely scarring.
				<<elseif $PC.skill.medicine >= 10>>
					Your basic surgical skill in medicine is sufficient only to stabilize the citizen's wounds before medical assistance arrives.
				<<else>>
					Your total lack of surgical skill causes the death of the citizen through repeated medical blunders.
					<<set $arcologies[0].prosperity -= .25>>
				<</if>>
			<</if>>
			<</replace>>
		<</link>>
	<</if>>

<<link "Talk in $SF.Lower's HQ">>
		<<replace "#result0">>

What do you want to do with The Colonel in the HQ?

<<link "Talk">>
				<<replace "#result1">>
				<<set $SF.Colonel.Talk = 1, $SF.Colonel.Status += 3, $SF.FS.Tension -= _colonelTalkTensionRuction>>

You and The Colonel talk over some $PC.refreshment, where she ends up talking about her past. You learn a little more about her.
				<<switch random(1,6)>>
				<<case 1>>
					<<= IncreasePCSkills(['medicine', 'trading', 'slaving'], 1)>>
				<<case 2>>
					<<= IncreasePCSkills(['trading', 'slaving', 'engineering'], 1)>>
				<<case 3>>
					<<= IncreasePCSkills(['slaving', 'engineering', 'hacking'], 1)>>
				<<case 4>>
					<<= IncreasePCSkills(['engineering', 'hacking', 'warfare'], 1)>>
				<<case 5>>
					<<= IncreasePCSkills(['hacking', 'warfare', 'medicine'], 1)>>
				<<case 6>>
					<<= IncreasePCSkills(['warfare', 'medicine', 'trading'], 1)>>
				<</switch>>
				<</replace>>
			<</link>>

<<link "Learn">>
				<<replace "#result1">>
				<<set $SF.Colonel.Talk = 1,$SF.Colonel.Status += 1, $SF.FS.Tension -= _colonelTalkTensionRuction>>

"Sure boss, I can use a break from all of this," she laughs. The Colonel tells a story about one time when she
				<<switch random(1,6)>>
				<<case 1>>
					used field medicine to save another merc's life, teaching you some medical procedures in the process.
					<<= IncreasePCSkills('medicine', 5)>>
				<<case 2>>
					haggled for necessary gear with a stingy quartermaster, teaching you how to get what you want from traders.
					<<= IncreasePCSkills('trading', 5)>>
				<<case 3>>
					found a load of human chattel in a raid and had to manage them before they could later be unloaded, teaching you how to better care for your slaves.
					<<= IncreasePCSkills('slaving', 5)>>
				<<case 4>>
					was responsible for rebuilding a fort she had seized, teaching you how to better manage construction in your arcology.
					<<= IncreasePCSkills('engineering', 5)>>
				<<case 5>>
					was forced to hack her way out of a trap, teaching you how to better penetrate digital security.
					<<= IncreasePCSkills('hacking', 5)>>
				<<case 6>>
					fought off an entire battalion with only a small squad, teaching you how to think tactically in battle.
					<<= IncreasePCSkills('warfare', 5)>>
				<</switch>>
				<</replace>>
			<</link>>

			<<if $SF.Colonel.Status >= 45>>

<<link "Have some fun">>
					<<replace "#result1">>

						<<set $SF.Colonel.Fun = 1,$SF.Colonel.Talk = 1,$SF.Colonel.Status += 3, $SF.FS.Tension -= _colonelTalkTensionRuction>>

Where should this fun take place?
						<<link "Go back" "Firebase">>
							<<set $SF.Colonel.Fun = 0, $SF.Colonel.Talk = 0,$SF.Colonel.Status -= 3, $SF.FS.Tension += _colonelTalkTensionRuction>>
						<</link>>

<<link "In private">>
							<<replace "#result4">>

Which orifice do you wish to target?
								<<link "Go back" "Firebase">>
								<</link>>

<<link "Pussy">>
									<<replace "#result6">>
									<<include "SFColonelSexDec">>
									<</replace>>
									<<set $SF.Colonel.Fun += 1>>
								<</link>>

<<link "Ass">>
									<<replace "#result6">>
									<<include "SFColonelSexDec">>
									<</replace>>
									<<set $SF.Colonel.Fun += 1>>
								<</link>>

<<link "Both pussy and ass">>
									<<replace "#result6">>
									<<include "SFColonelSexDec">>
									<</replace>>
									<<set $SF.Colonel.Fun += 2>>
								<</link>>

<<link "Mouth">>
									<<replace "#result6">>
									<<include "SFColonelSexDec">>
									<</replace>>
									<<set $SF.Colonel.Fun += 1>>
								<</link>>

<<link "All three holes">>
									<<replace "#result6">>
									<<include "SFColonelSexDec">>
									<</replace>>
									<<set $SF.Colonel.Fun += 3>>
								<</link>>

							<</replace>>
						<</link>>

<<link "On The Colonel's throne">>
							<<replace "#result4">>

Which orifice do you wish to target?
								<<link "Go back" "Firebase">>
								<</link>>

<<link "Pussy">>
									<<replace "#result6">>
									<<include "SFColonelSexDec">>
									<</replace>>
									<<set $SF.Colonel.Fun += 1>>
								<</link>>

<<link "Ass">>
									<<replace "#result6">>
									<<include "SFColonelSexDec">>
									<</replace>>
									<<set $SF.Colonel.Fun += 1>>
								<</link>>

<<link "Both pussy and ass">>
									<<replace "#result6">>
									<<include "SFColonelSexDec">>
									<</replace>>
									<<set $SF.Colonel.Fun += 2>>
								<</link>>

<<link "Mouth">>
									<<replace "#result6">>
									<<include "SFColonelSexDec">>
									<</replace>>
									<<set $SF.Colonel.Fun += 1>>
								<</link>>

<<link "All three holes">>
									<<replace "#result6">>
									<<include "SFColonelSexDec">>
									<</replace>>
									<<set $SF.Colonel.Fun += 3>>
								<</link>>

							<</replace>>
						<</link>>

					<</replace>>
				<</link>>
			<</if>> /*Closes fun*/

		<</replace>>
		<</link>> /*Closes talk*/

<</if>> /*Closes spend time with The Colonel*/


code:
App.Arcology.Cell.Apartment = class extends App.Arcology.Cell.BaseCell {
	/**
	 * @param {number} owner
	 * @param {number} type
	 */
	constructor(owner, type = 2) {
		super(owner);
		this.type = type;
	}

	static get cellName() {
		return "Apartments";
	}

	/**
	 * @returns {string}
	 */
	get colorClass() {
		switch (this.type) {
			case 1:
				return "luxuryApartments";
			case 2:
				return "apartments";
			case 3:
				return "denseApartments";
			default:
				return super.colorClass;
		}
	}

	/**
	 * @param {Array<number>} path
	 * @returns {Node}
	 */
	cellContent(path) {
		let message = "";
		switch (this.type) {
			case 1:
				message = "Luxury Apartments";
				break;
			case 2:
				message = "Apartments";
				break;
			case 3:
				message = "Dense Apartments";
				break;
		}
		return App.Arcology.getCellLink(path, message);
	}

	/**
	 * @returns {string|Node}
	 * @private
	 */
	_setting() {
		let r = "";

		switch (this.type) {
			case 1:
				r = "improved for occupancy by the Free Cities' wealthiest citizens";
				break;
			case 2:
				r = "occupied by citizens of varying wealth and social standing";
				break;
			case 3:
				r = "upgraded for dense occupancy by as many citizens as possible";
				break;
		}

		if (this.owner === 1) {
			r = `This is a sector of the arcology's living areas, ${r}. You control this part of the arcology and all these tenants pay you rent.`;
		} else {
			r = `This is a privately-owned sector of the arcology's living areas, ${r}.`;
		}

		return r;
	}

	/**
	 * @returns {Node}
	 * @private
	 */
	_body() {
		const fragment = document.createDocumentFragment();

		const cost = 10000 * V.upgradeMultiplierArcology;

		if (this.type !== 3) {
			fragment.append(this._makeUpgrade(
				"Upgrade this sector of apartments for dense occupancy by as many citizens as possible.",
				() => { this.type = 3; }, cost));
		}

		if (this.type !== 1) {
			fragment.append(this._makeUpgrade(
				"Improve this sector of apartments for occupancy by the Free Cities' wealthiest citizens.",
				() => { this.type = 1; }, cost));
		}

		if (this.type !== 2) {
			fragment.append(this._makeUpgrade(
				"Return this sector to standard, mixed housing.",
				() => { this.type = 2; }, cost));
		}

		return fragment;
	}

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

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

	clone() {
		return (new App.Arcology.Cell.Apartment())._init(this);
	}

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

repo_name: KnightBoulegard/fc

license: mit


code:
/**
 * @param {Array<number>} path
 * @param {string} message
 * @returns {Node}
 */
App.Arcology.getCellLink = function(path, message) {
	return App.UI.DOM.passageLink(message, "Cell", () => { V.cellPath = path; });
};

/**
 * Upgrades all instances of cellClass and cellType to newType.
 * Intended for use on guaranteed single hits.
 *
 * @param {App.Arcology.Building} building
 * @param {class} cellClass
 * @param {*} cellType
 * @param {*} newType
 * @param {string} [key="type"]
 */
App.Arcology.cellUpgrade = function(building, cellClass, cellType, newType, key = "type") {
	building.findCells(cell => cell instanceof cellClass && cell[key] === cellType)
		.forEach(cell => { cell[key] = newType; });
};

/**
 * Updates V.arcologies[0].ownership.
 */
App.Arcology.updateOwnership = function() {
	const allCells = V.building.findCells(cell => !(cell instanceof App.Arcology.Cell.Filler));
	const ownedCells = allCells.filter(cell => cell.owner === 1);

	const ratio = ownedCells.length / allCells.length;

	V.arcologies[0].ownership = Math.round(100 * ratio);
};

/**
 * @param {string} [terrain="default"]
 * @returns {App.Arcology.Building}
 */
App.Arcology.defaultBuilding = function(terrain = "default") {
	/**
	 * @type {arcologyEnvironment}
	 */
	const env = {terrain: terrain, established: false, fs: ""};
	const candidates = App.Arcology.presets.filter(preset => preset.isAllowed(env));
	return candidates.pluck().construct(env).building;
};

/**
 * Order of the building sections. All possible sections have to be added here.
 *
 * @type {string[]}
 */
App.Arcology.sectionOrder = ["fountain", "penthouse", "spire", "shops", "ravine-markets", "apartments", "markets", "manufacturing"];

/**
 * @param {App.Entity.Facilities.Facility} facility
 * @param {string} [passageName]
 * @param {string} [statsStr]
 * @returns {Node}
 */
App.Arcology.facilityCellContent = function(facility, passageName, statsStr) {
	const res = document.createDocumentFragment();
	res.append(App.UI.DOM.passageLink(facility.UIName, passageName || facility.genericName));
	const report = facility.occupancyReport(false);
	if (report !== "") {
		const stats = document.createElement("span");
		stats.style.whiteSpace = "nowrap";
		stats.textContent = ` (${report})`;
		res.append(stats);
	}
	return res;
};

App.Arcology.Section = class extends App.Entity.Serializable {
	/**
	 * @param {string} id unique ID
	 * @param {Array<Array<App.Arcology.Cell.BaseCell>>} rows
	 * @param {boolean} [groundLevel=false] if the section the ground level, all lower layers are automatically in the
	 *     basement
	 */
	constructor(id, rows, groundLevel = false) {
		super();
		/**
		 * @type {string}
		 */
		this.id = id;
		/**
		 * @type {Array<Array<App.Arcology.Cell.BaseCell>>}
		 */
		this.rows = rows;
		this.groundLevel = groundLevel;
	}

	get width() {
		let maxWidth = 0;

		this.rows.forEach(cells => {
			let width = 0;
			cells.forEach(cell => {
				width += cell.width;
			});
			maxWidth = Math.max(maxWidth, width);
		});
		return maxWidth;
	}

	/**
	 * @param {number} elementWidth in %
	 * @param {number} index
	 * @returns {DocumentFragment}
	 */
	render(elementWidth, index) {
		/**
		 * @param {App.Arcology.Cell.BaseCell} cell
		 * @param {number} rowIndex
		 * @param {number} cellIndex
		 * @returns {HTMLDivElement}
		 */
		function createCell(cell, rowIndex, cellIndex) {
			return cell.renderCell([index, rowIndex, cellIndex], elementWidth);
		}

		/**
		 * @param {Array<App.Arcology.Cell.BaseCell>} row
		 * @param {number} rowIndex
		 * @returns {HTMLDivElement}
		 */
		function createRow(row, rowIndex) {
			const div = document.createElement("div");
			div.classList.add("row");

			row.forEach((cell, cellIndex) => {
				div.append(createCell(cell, rowIndex, cellIndex));
			});

			return div;
		}

		const fragment = document.createDocumentFragment();

		this.rows.forEach((row, rowIndex) => {
			fragment.append(createRow(row, rowIndex));
		});

		return fragment;
	}

	/**
	 * @param {Array<number>} path of the form [i, j]
	 * @param {App.Arcology.Building} containingBuilding
	 * @returns {Node}
	 */
	renderCell(path, containingBuilding) {
		return this.rows[path[0]][path[1]].cellPassage(containingBuilding);
	}

	/**
	 * @param {function(App.Arcology.Cell.BaseCell): boolean} predicate
	 * @returns {Array<App.Arcology.Cell.BaseCell>}
	 */
	findCells(predicate) {
		const cells = [];
		for (const row of this.rows) {
			for (const cell of row) {
				if (predicate(cell)) {
					cells.push(cell);
				}
			}
		}
		return cells;
	}

	/**
	 * Replaces the first occurrence of oldCell with newCell
	 *
	 * @param {App.Arcology.Cell.BaseCell} oldCell
	 * @param {App.Arcology.Cell.BaseCell} newCell
	 * @returns {boolean}
	 */
	replaceCell(oldCell, newCell) {
		for (const row of this.rows) {
			for (let i = 0; i < row.length; i++) {
				if (row[i] === oldCell) {
					row[i] = newCell;
					return true;
				}
			}
		}
		return false;
	}

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

	clone() {
		return (new App.Arcology.Section())._init(this);
	}

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

App.Arcology.Building = class extends App.Entity.Serializable {
	/**
	 * @param {string} layout reference to the used layout so we now how to do layout specific actions. (upgrading)
	 * @param {Array<App.Arcology.Section>} sections
	 */
	constructor(layout, sections) {
		super();
		/**
		 * @type {string}
		 */
		this.layout = layout;
		/**
		 * List of all already taken upgrades.
		 * @type {Array<string>}
		 */
		this.usedUpgrades = [];
		/**
		 * @type {Array<App.Arcology.Section>}
		 */
		this.sections = sections;
	}

	/**
	 * @returns {DocumentFragment}
	 */
	render() {
		const fragment = document.createDocumentFragment();

		let maxWidth = 0;
		this.sections.forEach(section => {
			maxWidth = Math.max(section.width, maxWidth);
		});
		const elementWidth = 100 / maxWidth;

		const upperLevels = document.createElement("div");
		upperLevels.classList.add("building");
		const basement = document.createElement("div");
		basement.classList.add("building", "basement");

		let wrapper = upperLevels;
		sortArrayByArray(App.Arcology.sectionOrder, this.sections, "id")
			.forEach((section, index) => {
				wrapper.append(section.render(elementWidth, index));
				if (section.groundLevel) {
					// if there are multiple sections that are ground level the first (highest) section wins and all
					// others are in the basement.
					wrapper = basement;
				}
			});

		fragment.append(upperLevels, basement);
		return fragment;
	}

	/**
	 * @param {Array<*>} path
	 * @returns {Node}
	 */
	renderCell(path) {
		return this.sections[path[0]].renderCell(path.slice(1), this);
	}

	/**
	 * @param {function(App.Arcology.Cell.BaseCell): boolean} predicate
	 * @returns {Array<App.Arcology.Cell.BaseCell>}
	 */
	findCells(predicate) {
		return this.sections.reduce(
			(cells, section) => {
				cells.push(...section.findCells(predicate));
				return cells;
			}, []);
	}

	/**
	 * @param {App.Arcology.Cell.BaseCell} oldCell
	 * @param {App.Arcology.Cell.BaseCell} newCell
	 * @returns {boolean}
	 */
	replaceCell(oldCell, newCell) {
		for (let section of this.sections) {
			if (section.replaceCell(oldCell, newCell)) {
				return true;
			}
		}
		return false;
	}

	static _cleanupConfigScheme(config) {
		super._cleanupConfigScheme(config);
		if (config.layout === undefined) {
			config.layout = V.terrain;
		}
	}

	clone() {
		return (new App.Arcology.Building())._init(this);
	}

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


code:
:: Cell [nobr jump-from-safe no-history]

<<set $nextButton = "Back", $nextLink = "Main">>

<<includeDOM V.building.renderCell(V.cellPath)>>


code:
App.Arcology.Cell.Decorative = class extends App.Arcology.Cell.BaseCell {
	/**
	 * @param {number} width in cells
	 * @param {number} [xOffset=0] in %
	 * @param {number} [yOffset=0] in px
	 * @param {number} [rotation=0] in deg
	 * @param {number} [cellHeight=0] in px, if default height is not enough to stop stuff going offscreen
	 * @param {number} [absoluteWidth=0] 0/1, if width and xOffset should be read as absolute pixels
	 */
	constructor({width, xOffset = 0, yOffset = 0, rotation = 0, cellHeight = 0, absoluteWidth = 0} = {}) {
		super(1);
		this._width = width;
		this._xOffset = xOffset;
		this._yOffset = yOffset;
		this._rotation = rotation;
		this._cellHeight = cellHeight;
		this._absoluteWidth = absoluteWidth;
	}

	get width() {
		return this._absoluteWidth ? 0 : this._width;
	}

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

		if (this._cellHeight > 0) {
			outerCell.style.height = `${this._cellHeight}px`;
		}

		const innerCell = document.createElement("div");
		innerCell.classList.add("decorative");
		if (this._absoluteWidth) {
			innerCell.style.width = `${this._width}px`;
		}
		innerCell.style.transform = `translate(${this._xOffset}${this._absoluteWidth ? "px" : "%"}, ${this._yOffset}px) rotate(${this._rotation}deg)`;
		outerCell.append(innerCell);

		return outerCell;
	}

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

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

	clone() {
		return (new App.Arcology.Cell.Decorative())._init(this);
	}
};


code:
App.Arcology.Cell.Filler = class extends App.Arcology.Cell.BaseCell {
	constructor(width) {
		super(1);
		this._width = width;
	}

	get width() {
		return this._width;
	}

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

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

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

	clone() {
		return (new App.Arcology.Cell.Filler())._init(this);
	}
};


code:
App.Arcology.Cell.Manufacturing = class extends App.Arcology.Cell.BaseCell {
	/**
	 * @param {number} owner
	 * @param {string} type
	 */
	constructor(owner, type = "Manufacturing") {
		super(owner);
		this.type = type;
	}

	static get cellName() {
		return "Manufacturing";
	}

	/**
	 * @returns {string}
	 */
	get colorClass() {
		switch (this.type) {
			case "Manufacturing":
				return "manufacturing";
			case "Dairy":
				return "diary";
			case "Farmyard":
				return "farmyard";
			case "Barracks":
				return "barracks";
			case "Weapon Manufacturing":
				return "weaponsManufacturing";
			case "Pens":
				return "pens";
			case "Sweatshops":
				return "sweatshops";
			default:
				return super.colorClass;
		}
	}

	/**
	 * @returns {boolean}
	 */
	isBaseType() {
		return this.type === "Manufacturing";
	}

	/**
	 * @param {Array<number>} path
	 * @returns {Node}
	 */
	cellContent(path) {
		if (this.type === "Dairy") {
			return App.Arcology.facilityCellContent(App.Entity.facilities.dairy);
		}
		if (this.type === "Farmyard") {
			return App.Arcology.facilityCellContent(App.Entity.facilities.farmyard);
		}
		switch (this.type) {
			case "Manufacturing":
			case "Pens":
			case "Sweatshops":
				return App.Arcology.getCellLink(path, this.type);
			case "Barracks":
				return App.UI.DOM.passageLink("Barracks", "Barracks");
			case "Weapon Manufacturing":
				return App.UI.DOM.passageLink("Weapons Manufacturing", "weaponsManufacturing");
			default:
				return App.UI.DOM.makeElement("span", `ERROR: invalid type: ${this.type}`, "error");
		}
	}

	/**
	 * @returns {string|Node}
	 * @private
	 */
	_setting() {
		let r = "";

		switch (this.type) {
			case "Manufacturing":
				r = "rented to a variety of tenants for manufacturing purposes";
				if (this.owner === 1) {
					r += ". You control this part of the arcology and all these tenants pay you rent";
				}
				break;
			case "Sweatshops":
				if (this.owner === 1) {
					r = "designed for labor intensive manufacturing by menial slaves";
				} else {
					r = "rented to a variety of tenants for manufacturing purposes";
				}
				break;
			case "Pens":
				r = "designed to house hundreds of slaves for paying owners";
				if (this.owner === 1) {
					r += ". You control this part of the arcology. If you own menial slaves, they will be kept here; otherwise, unused space will be rented to other slave brokers";
				}
				break;
		}

		if (this.owner === 1) {
			return `This is a space in the arcology's service areas, ${r}.`;
		}
		return `This is a privately-owned space in the arcology's service areas, ${r}.`;
	}

	/**
	 * @returns {Node}
	 * @private
	 */
	_body() {
		const fragment = document.createDocumentFragment();

		const pensDOM = pens(this);
		if (pensDOM !== null) {
			console.log("if");
			fragment.append(pensDOM);

			const p = document.createElement("p");
			p.append(upgrades(this));
			console.log("up");
			fragment.append(p);
		} else {
			console.log("bor");
			fragment.append(upgrades(this));
		}

		return fragment;

		/**
		 * @param {App.Arcology.Cell.Manufacturing} thisCell this is apparently undefined inside???
		 * @returns {null|HTMLParagraphElement}
		 */
		function pens(thisCell) {
			if (thisCell.type !== "Pens") {
				return null;
			}

			const paragraph = document.createElement("p");

			paragraph.append(App.UI.DOM.makeElement("div", MenialPopCap()),
				App.UI.DOM.makeElement("div", `In total you are able to personally house a total of ${num(V.PopCap)} menial slaves.`));

			if (V.menials + V.menialBioreactors + V.fuckdolls > 0) {
				let r = "You own ";
				let list = [];
				if (V.menials > 0) {
					list.push(numberWithPluralOne(V.menials, "menial slave"));
				}
				if (V.menialBioreactors > 0) {
					list.push(numberWithPluralOne(V.menialBioreactors, "standard bioreactor"));
				}
				if (V.fuckdolls > 0) {
					list.push(numberWithPluralOne(V.fuckdolls, "standard Fuckdoll"));
				}

				r += `${list.toStringExt()}, `;

				if (V.menials + V.menialBioreactors + V.fuckdolls > 500) {
					r += "partially ";
				}

				r += "housed in this sector.";

				if (V.menials > 0 && V.Sweatshops > 0) {
					r += ` The simple labor slaves toil in ${V.arcologies[0].name}'s sweatshops, and only return here to sleep.`;
				}
				if (V.fuckdolls > 0 && V.arcade > 0) {
					r += ` The menial Fuckdolls are endlessly cycled through ${V.arcadeName}. They're restrained there and used by the public until their holes are no longer appealing, and then cycled back down here to rest until they've tightened up again.`;
				}
				if (V.menialBioreactors && V.dairyUpgradeMenials) {
					r += ` Whenever there's space in ${V.dairyName}, menial Bioreactors are taken out of storage here and restrained there, with ${V.dairyName}'s powerful hookups draining them of their useful fluids and feeding them generously so they can produce more.`;
				}

				paragraph.append(r);
			}

			return paragraph;
		}

		/**
		 * @param {App.Arcology.Cell.Manufacturing} thisCell
		 * @returns {DocumentFragment}
		 */
		function upgrades(thisCell) {
			const fragment = document.createDocumentFragment();
			const cost = Math.trunc(10000 * V.upgradeMultiplierArcology);

			if (V.dairy === 0) {
				fragment.append(thisCell._makeUpgrade(
					"Construct a dairy to milk slaves on an industrial scale",
					() => {
						V.dairy = 5;
						thisCell.type = "Dairy";
					}, cost, "and will incur upkeep costs"
				));
			}

			if (V.experimental.food === 1) {
				if (V.farmyard === 0) {
					fragment.append(thisCell._makeUpgrade(
						"Construct a farming facility to grow food for your arcology and house animals",
						() => {
							V.farmyard = 5;
							thisCell.type = "Farmyard";
						}, cost, "and will incur upkeep costs",
						// this only exists for the farmyard, remove feature once that is out of alpha
						App.UI.DOM.makeElement("span", "Alpha Content!", "warning")
					));
				}
			}

			if (V.mercenaries) {
				if (V.barracks !== 1) {
					fragment.append(thisCell._makeUpgrade(
						"Build an armory to properly house your mercenaries",
						() => {
							V.barracks = 1;
							thisCell.type = "Barracks";
						}, cost, "but will reduce mercenary upkeep"
					));
				}
			}

			if (V.secExpEnabled === 1) {
				if (V.weapManu !== 1) {
					fragment.append(thisCell._makeUpgrade(
						"Convert this sector to weapons manufacturing",
						() => {
							V.weapManu = 1;
							thisCell.type = "Weapon Manufacturing";
						}, cost, "but will provide a weekly income and will unlock upgrades for our troops"
					));
				}
			}

			if (thisCell.type !== "Pens") {
				fragment.append(thisCell._makeUpgrade(
					"Convert to pens to increase the number of menial slaves you can house",
					() => {
						thisCell.type = "Pens";
					}, cost
				));
			}

			if (thisCell.type !== "Sweatshops") {
				fragment.append(thisCell._makeUpgrade(
					"Convert these facilities to use the labor of menial slaves",
					() => {
						thisCell.type = "Sweatshops";
					}, cost
				));
			}

			if (thisCell.type !== "Manufacturing") {
				fragment.append(thisCell._makeUpgrade(
					"Return this sector to standard manufacturing",
					() => {
						thisCell.type = "Manufacturing";
					}, cost
				));
			}

			return fragment;
		}
	}

	/**
	 * @returns {boolean}
	 */
	canBeSold() {
		return ["Manufacturing", "Sweatshops", "Pens"].includes(this.type);
	}

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

	clone() {
		return (new App.Arcology.Cell.Manufacturing())._init(this);
	}

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


code:
App.Arcology.Cell.Market = class extends App.Arcology.Cell.BaseCell {
	/**
	 * @param {number} owner
	 * @param {string} type
	 */
	constructor(owner, type = "Markets") {
		super(owner);
		this.type = type;
	}

	static get cellName() {
		return "Markets";
	}

	/**
	 * @returns {string}
	 */
	get colorClass() {
		switch (this.type) {
			case "Markets":
				return "markets";
			case "Arcade":
				return "arcade";
			case "Pit":
				return "pit";
			case "Transport Hub":
				return "transportHub";
			case "Corporate Market":
				return "corporateMarket";
			default:
				return super.colorClass;
		}
	}

	isBaseType() {
		return this.type === "Markets";
	}

	/**
	 * @param {Array<number>} path
	 * @returns {Node}
	 */
	cellContent(path) {
		switch (this.type) {
			case "Arcade":
				return App.Arcology.facilityCellContent(App.Entity.facilities.arcade);
			case "Pit":
				return App.Arcology.facilityCellContent(App.Entity.facilities.pit);
			case "Markets":
				return App.Arcology.getCellLink(path, "Markets");
			case "Transport Hub":
				return App.UI.DOM.passageLink("Transport Hub", "transportHub");
			case "Corporate Market":
				return App.UI.DOM.passageLink("Corporate Market", "Corporate Market");
			default:
				return App.UI.DOM.makeElement("span", "ERROR: invalid type: " + this.type, "error");
		}
	}

	/**
	 * @returns {string|Node}
	 * @private
	 */
	_setting() {
		/* no need to check type, since you can only get here with the basic type */
		let r = "area of the concourse occupied by large stores and markets, many of which sell slaves";

		if (this.owner === 1) {
			return `This is an ${r}. You control this part of the arcology and all these tenants pay you rent.`;
		}
		return `This is a privately-owned ${r}.`;
	}

	/**
	 * @returns {Node}
	 * @private
	 */
	_body() {
		const fragment = document.createDocumentFragment();

		const cost = Math.trunc(10000 * V.upgradeMultiplierArcology);
		if (V.arcade === 0) {
			fragment.append(this._makeUpgrade(
				"Construct a sex arcade to present slaves' holes for public use",
				() => {
					this.type = "Arcade";
					V.arcade = 10;
				}, cost, "and will incur upkeep costs"
			));
		}

		if (V.pit === 0) {
			fragment.append(this._makeUpgrade(
				"Build a pit to host proper slave fights",
				() => {
					this.type = "Pit";
					V.pit = 1;
				}, cost
			));
		}

		if (V.secExpEnabled === 1 && V.transportHub === 0) {
			fragment.append(this._makeUpgrade(
				"Centralize and modernize the transport hub",
				() => {
					this.type = "Transport Hub";
					V.transportHub = 1;
					V.docks = 1;
					V.railway = 1;
					V.airport = 1;
				}, cost
			));
		}

		const corpCost = Math.trunc(10000 * V.upgradeMultiplierArcology);
		if (V.corp.Market === 0 && V.corp.Incorporated === 1) {
			fragment.append(this._makeUpgrade(
				"Create a flagship slave market for your corporation here",
				() => {
					this.type = "Corporate Market";
					V.corp.Market = 1;
					V.corp.Cash -= corpCost;
				}, 0, `Costs ${cashFormat(corpCost)} of the corporation's money`
			));
		}

		return fragment;
	}

	/**
	 * @returns {boolean}
	 */
	canBeSold() {
		return this.type === "Markets";
	}

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

	clone() {
		return (new App.Arcology.Cell.Market())._init(this);
	}

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


code:
App.Arcology.Cell.Penthouse = class extends App.Arcology.Cell.BaseCell {
	constructor() {
		super(1);
	}

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

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

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

		const link = App.UI.DOM.passageLink("Penthouse", "Manage Penthouse");
		const hotkey = App.UI.DOM.makeElement("span", "[P]", "hotkey");
		if (V.verticalizeArcologyLinks === 0) {
			const div = document.createElement("div");
			div.append(link, " ", hotkey);
			fragment.append(div);
		} else {
			fragment.append(link, " ", hotkey);
		}

		const wrapper = getWrapper(fragment);
		const fcs = App.Entity.facilities;

		/**
		 * @param {App.Entity.Facilities.Facility} facility
		 * @param {string} [passageName]
		 */
		function addFacility(facility, passageName) {
			if (facility.established) {
				const report = facility.occupancyReport(false);
				wrapper.append(createFacilityDiv(
					App.UI.DOM.passageLink(facility.UIName, passageName || facility.genericName),
					report ? `(${report})` : null
				));
			}
		}

		addFacility(fcs.masterSuite);
		addFacility(fcs.headGirlSuite, "Head Girl Suite");
		addFacility(fcs.armory, "BG Select");
		addFacility(fcs.servantsQuarters);
		addFacility(fcs.spa);
		addFacility(fcs.nursery);
		addFacility(fcs.clinic);
		addFacility(fcs.schoolroom);
		addFacility(fcs.cellblock);

		if (V.incubator) {
			const inc = App.Entity.facilities.incubator;
			const link = App.UI.DOM.passageLink(inc.UIName, "Incubator");
			const desc = `(${numberWithPluralOne(inc.capacity - V.tanks.length, "empty tank")})`;

			if (V.readySlaves > 0) {
				wrapper.append(createFacilityDiv(link, desc, App.UI.DOM.makeElement("span", "[!]", "noteworthy")));
			} else {
				wrapper.append(createFacilityDiv(link, desc));
			}
		}

		if (V.researchLab.level > 0) {
			wrapper.append(createFacilityDiv(App.UI.DOM.passageLink("Prosthetic Lab", "Prosthetic Lab")));
		}

		return fragment;

		/**
		 * @returns {HTMLDivElement}
		 */
		function getWrapper(outer) {
			const wrapper = document.createElement("div");
			if (V.verticalizeArcologyLinks !== 0) {
				let gridClass = `grid${V.verticalizeArcologyLinks}`;
				wrapper.classList.add("gridWrapper", gridClass);
			}
			outer.append(wrapper);
			return wrapper;
		}

		/**
		 *
		 * @param {HTMLElement} link
		 * @param {(Node|string)} [content]
		 * @returns {HTMLDivElement}
		 */
		function createFacilityDiv(link, content) {
			const div = document.createElement("div");
			div.append(link);
			// in collapsed mode additional information needs to be in it's own div to stop linebreaks at weird places
			if (V.verticalizeArcologyLinks === 0) {
				div.classList.add("collapsed");
				if (content) {
					div.append(" ", content);
				}
			} else if (content) {
				const innerDiv = document.createElement("div");
				innerDiv.append(content);
				div.append(" ", innerDiv);
			}
			return div;
		}
	}

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

	clone() {
		return (new App.Arcology.Cell.Penthouse())._init(this);
	}

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


code:
/**
 * @typedef {object} arcologyEnvironment
 * @property {string} terrain
 * @property {boolean} established
 * @property {string} fs
 */
/**
 * @typedef {object} buildingPreset
 * @property {function(arcologyEnvironment):boolean} isAllowed
 * @property {function(arcologyEnvironment):{building: App.Arcology.Building, apply: function()}} construct
 */

/**
 * @type {Array<buildingPreset>}
 */
App.Arcology.presets = (function() {
	/**
	 * @typedef {object} sectionTemplate
	 * @property {string} id
	 * @property {Array<string>} rows
	 * @property {boolean} [ground]
	 */
	/**
	 * @typedef {Array<sectionTemplate|string>} buildingTemplate first entry is template name
	 */

	/*
	 NOTE: test new templates, broken templates WILL explode
	 t is markets; () is possible values for a sector, first is default;
	 [number] is for filler space, must be parsable by Number(), 1 equals the width of a normal cell
	 */
	const templates = {
		default: ["default",
			{id: "penthouse", rows: ["p"]},
			{id: "shops", rows: ["sss"]},
			{id: "apartments", rows: ["aaaa", "aaaa", "aaaa"]},
			{id: "markets", rows: ["ttttt"], ground: true},
			{id: "manufacturing", rows: ["mmmmm"]}],
		urban: ["urban",
			{id: "penthouse", rows: ["p"]},
			{id: "shops", rows: ["sss"]},
			{id: "apartments", rows: ["aaaa", "aaaa", "dddd"]},
			{id: "markets", rows: ["(dt)tt(dt)", "t(tm)(tm)t"], ground: true},
			{id: "manufacturing", rows: ["mmmm"]}],
		rural: ["rural",
			{id: "penthouse", rows: ["p"]},
			{id: "shops", rows: ["sss"]},
			{id: "apartments", rows: ["aaaaa", "aaaaa"]},
			{id: "markets", rows: ["tt(mt)(mt)tt"], ground: true},
			{id: "manufacturing", rows: ["mmmmm"]}],
		ravine: ["ravine",
			{id: "penthouse", rows: ["p"]},
			{id: "shops", rows: ["sss"]},
			{id: "ravine-markets", rows: ["ttttt"], ground: true},
			{id: "apartments", rows: ["aaaa", "aaaa", "aaaa"]},
			{id: "manufacturing", rows: ["mmmmm"]}],
		marine: ["marine",
			{id: "penthouse", rows: ["p"]},
			{id: "shops", rows: ["ssss"]},
			{id: "apartments", rows: ["laal", "aaaa", "aaaa"]},
			{id: "markets", rows: ["tt(mt)tt"], ground: true},
			{id: "manufacturing", rows: ["(tm)mmm(tm)"]}],
		oceanic: ["oceanic",
			{id: "penthouse", rows: ["p"]},
			{id: "shops", rows: ["sss"]},
			{id: "apartments", rows: ["llll", "aaaa", "aaaa"]},
			{id: "markets", rows: ["ttttt"], ground: true},
			{id: "manufacturing", rows: ["mmmmm"]}],
		dick: ["dick", // yes, this is a giant dick
			{id: "penthouse", rows: ["l", "p"]},
			{id: "apartments", rows: ["a", "a", "a", "d[0.75]a[0.75]d", "dd[0.25]s[0.25]dd"]},
			{id: "markets", rows: ["ttsstt"], ground: true},
			{id: "manufacturing", rows: ["tm[0.25]m[0.25]mt", "m[0.75]m[0.75]m"]}],
	};

	/**
	 * @param {buildingTemplate} template
	 * @returns {App.Arcology.Building}
	 */
	function templateToBuilding(template) {
		const sections = [];
		const layout = template[0];
		for (let i = 1; i < template.length; i++) {
			sections.push(getSection(template[i]));
		}
		return new App.Arcology.Building(layout, sections);

		/**
		 * @param {sectionTemplate} section
		 * @returns {App.Arcology.Section}
		 */
		function getSection(section) {
			const rows = [];
			for (const row of section.rows) {
				rows.push(getRow(row));
			}
			return new App.Arcology.Section(section.id, rows, section.ground === true /* to ensure no undefined gets through */);
		}

		/**
		 * @param {string} rowTemplate
		 * @returns {[App.Arcology.Cell.BaseCell]}
		 */
		function getRow(rowTemplate) {
			const cells = [];
			const iter = rowTemplate[Symbol.iterator]();

			let next = iter.next();
			while (!next.done) {
				if (next.value === "(") {
					const cell = charToCell(iter.next().value).cell;
					next = iter.next();
					while (next.value !== ")") {
						cell.allowedConversions.push(charToCell(next.value).code);
						next = iter.next();
					}
					cells.push(cell);
				} else if (next.value === "[") {
					let number = "";
					next = iter.next();
					while (next.value !== "]") {
						number += next.value;
						next = iter.next();
					}
					cells.push(new App.Arcology.Cell.Filler(Number(number)));
				} else {
					cells.push(charToCell(next.value).cell);
				}
				next = iter.next();
			}

			return cells;
		}

		/**
		 * @param {string} char
		 * @returns {{cell: App.Arcology.Cell.BaseCell, code:string}}
		 */
		function charToCell(char) {
			switch (char) {
				case "p":
					return {cell: new App.Arcology.Cell.Penthouse(), code: "Penthouse"};
				case "s":
					return {cell: new App.Arcology.Cell.Shop(1), code: "Shop"};
				case "l":
					return {cell: new App.Arcology.Cell.Apartment(1, 1), code: "Apartment"};
				case "a":
					return {cell: new App.Arcology.Cell.Apartment(1), code: "Apartment"};
				case "d":
					return {cell: new App.Arcology.Cell.Apartment(1, 3), code: "Apartment"};
				case "t":
					return {cell: new App.Arcology.Cell.Market(1), code: "Market"};
				case "m":
					return {cell: new App.Arcology.Cell.Manufacturing(1), code: "Manufacturing"};
				default:
					return {cell: new App.Arcology.Cell.BaseCell(1), code: "BaseCell"};
			}
		}
	}

	/**
	 * @param {App.Arcology.Building} building
	 * @param {arcologyEnvironment} environment
	 * @returns {{building: App.Arcology.Building, apply: function()}}
	 */
	function randomizeBuilding(building, environment) {
		const apply = [];
		if (Math.random() < 0.5) {
			apply.push(addFsShop(building, environment.fs));
		}
		return {building: building, apply: () => { apply.forEach(a => { a(); }); }};
	}

	/**
	 * @param {App.Arcology.Building} building
	 * @param {string} fs
	 * @returns {function():undefined}
	 */
	function addFsShop(building, fs) {
		function randomShop() {
			return building.findCells(cell => cell instanceof App.Arcology.Cell.Shop).random();
		}

		switch (fs) {
			case "Supremacist":
				randomShop().type = "Supremacist";
				return () => { V.FSPromenade.Supremacist = 1; };
			case "Subjugationist":
				randomShop().type = "Subjugationist";
				return () => { V.FSPromenade.Subjugationist = 1; };
			case "GenderRadicalist":
				randomShop().type = "Gender Radicalist";
				return () => { V.FSPromenade.GenderRadicalist = 1; };
			case "GenderFundamentalist":
				randomShop().type = "Gender Fundamentalist";
				return () => { V.FSPromenade.GenderFundamentalist = 1; };
			case "Paternalist":
				randomShop().type = "Paternalist";
				return () => { V.FSPromenade.Paternalist = 1; };
			case "Degradationist":
				randomShop().type = "Degradationist";
				return () => { V.FSPromenade.Degradationist = 1; };
			case "AssetExpansionist":
				randomShop().type = "Asset Expansionist";
				return () => { V.FSPromenade.AssetExpansionist = 1; };
			case "SlimnessEnthusiast":
				randomShop().type = "Slimness Enthusiast";
				return () => { V.FSPromenade.SlimnessEnthusiast = 1; };
			case "TransformationFetishist":
				randomShop().type = "Transformation Fetishist";
				return () => { V.FSPromenade.TransformationFetishist = 1; };
			case "BodyPurist":
				randomShop().type = "Body Purist";
				return () => { V.FSPromenade.BodyPurist = 1; };
			case "MaturityPreferentialist":
				randomShop().type = "Maturity Preferentialist";
				return () => { V.FSPromenade.MaturityPreferentialist = 1; };
			case "YouthPreferentialist":
				randomShop().type = "Youth Preferentialist";
				return () => { V.FSPromenade.YouthPreferentialist = 1; };
			case "Pastoralist":
				randomShop().type = "Pastoralist";
				return () => { V.FSPromenade.Pastoralist = 1; };
			case "PhysicalIdealist":
				randomShop().type = "Physical Idealist";
				return () => { V.FSPromenade.PhysicalIdealist = 1; };
			case "ChattelReligionist":
				randomShop().type = "Chattel Religionist";
				return () => { V.FSPromenade.ChattelReligionist = 1; };
			case "RomanRevivalist":
				randomShop().type = "Roman Revivalist";
				return () => { V.FSPromenade.RomanRevivalist = 1; };
			case "AztecRevivalist":
				randomShop().type = "Aztec Revivalist";
				return () => { V.FSPromenade.AztecRevivalist = 1; };
			case "EgyptianRevivalist":
				randomShop().type = "Egyptian Revivalist";
				return () => { V.FSPromenade.EgyptianRevivalist = 1; };
			case "EdoRevivalist":
				randomShop().type = "Edo Revivalist";
				return () => { V.FSPromenade.EdoRevivalist = 1; };
			case "ArabianRevivalist":
				randomShop().type = "Arabian Revivalist";
				return () => { V.FSPromenade.ArabianRevivalist = 1; };
			case "ChineseRevivalist":
				randomShop().type = "Chinese Revivalist";
				return () => { V.FSPromenade.Degradationist = 1; };
			case "Repopulationist":
				randomShop().type = "Repopulationist";
				return () => { V.FSPromenade.Repopulationist = 1; };
			case "Eugenics":
				randomShop().type = "Eugenics";
				return () => { V.FSPromenade.Eugenics = 1; };
			case "HedonisticDecadence":
				randomShop().type = "Hedonistic Decadence";
				return () => { V.FSPromenade.HedonisticDecadence = 1; };
			case "IntellectualDependency":
				randomShop().type = "Intellectual Dependency";
				return () => { V.FSPromenade.IntellectualDependency = 1; };
			case "SlaveProfessionalism":
				randomShop().type = "Slave Professionalism";
				return () => { V.FSPromenade.SlaveProfessionalism = 1; };
			case "PetiteAdmiration":
				randomShop().type = "Petite Admiration";
				return () => { V.FSPromenade.PetiteAdmiration = 1; };
			case "StatuesqueGlorification":
				randomShop().type = "Statuesque Glorification";
				return () => { V.FSPromenade.StatuesqueGlorification = 1; };
			default:
				return () => {};
		}
	}

	return [
		/* basic types for controlled start */
		{
			isAllowed: env => !env.established && env.terrain === "default",
			construct: () => { return {building: templateToBuilding(templates.default), apply: () => {}}; }
		}, {
			isAllowed: env => !env.established && env.terrain === "urban",
			construct: () => { return {building: templateToBuilding(templates.urban), apply: () => {}}; }
		}, {
			isAllowed: env => !env.established && env.terrain === "rural",
			construct: () => { return {building: templateToBuilding(templates.rural), apply: () => {}}; }
		}, {
			isAllowed: env => !env.established && env.terrain === "ravine",
			construct: () => { return {building: templateToBuilding(templates.ravine), apply: () => {}}; }
		}, {
			isAllowed: env => !env.established && env.terrain === "marine",
			construct: () => { return {building: templateToBuilding(templates.marine), apply: () => {}}; }
		}, {
			isAllowed: env => !env.established && env.terrain === "oceanic",
			construct: () => { return {building: templateToBuilding(templates.oceanic), apply: () => {}}; }
		},
		/* crazy presets for established arcologies TODO */
		{
			isAllowed: env => env.established && env.terrain === "default",
			construct: env => { return randomizeBuilding(templateToBuilding(templates.default), env); }
		}, {
			isAllowed: env => env.established && env.terrain === "urban",
			construct: env => { return randomizeBuilding(templateToBuilding(templates.urban), env); }
		}, {
			isAllowed: env => env.established && env.terrain === "rural",
			construct: env => { return randomizeBuilding(templateToBuilding(templates.rural), env); }
		}, {
			isAllowed: env => env.established && env.terrain === "ravine",
			construct: env => { return randomizeBuilding(templateToBuilding(templates.ravine), env); }
		}, {
			isAllowed: env => env.established && env.terrain === "marine",
			construct: env => { return randomizeBuilding(templateToBuilding(templates.marine), env); }
		}, {
			isAllowed: env => env.established && env.terrain === "oceanic",
			construct: env => { return randomizeBuilding(templateToBuilding(templates.oceanic), env); }
		}, {
			isAllowed: env => env.established && Math.random() < 0.1,
			construct: env => { return randomizeBuilding(templateToBuilding(templates.dick), env); }
		},
	];
}());

/**
 * @param {arcologyEnvironment} environment
 * @returns {buildingPreset}
 */
App.Arcology.randomPreset = function(environment) {
	return App.Arcology.presets.filter(p => p.isAllowed(environment)).random();
};

/**
 * Shows all possible upgrades for a given building.
 * @param {App.Arcology.Building} building
 * @returns {HTMLElement}
 */
App.Arcology.upgrades = function(building) {
	const outerDiv = document.createElement("div");

	/**
	 * @typedef upgrade
	 * @property {string} id used to identify already build upgrades, unique!
	 * @property {Array<string>} layouts
	 * @property {Array<string>} exclusive any upgrade is always exclusive to itself. NOTE: keep in mind that
	 *     exclusiveness has to be added to both upgrades!
	 * @property {string} name as shown to player.
	 * @property {string} desc Fits in a sentence like this: The next major upgrade needed is "desc"
	 * @property {number} cost
	 * @property {function():undefined} apply
	 */
	/** @type {Array<upgrade>} */
	const upgrades = [
		{
			id: "spire",
			layouts: ["default", "urban", "rural", "ravine", "marine", "oceanic"],
			exclusive: [], name: "spire",
			desc: "the addition of a spire at the top of the arcology to increase the space available for the wealthiest citizens to own whole floors",
			cost: 250000, apply: () => {
				building.sections.push(new App.Arcology.Section("spire", [
					[new App.Arcology.Cell.Apartment(1, 1), new App.Arcology.Cell.Apartment(1, 1)],
					[new App.Arcology.Cell.Apartment(1, 1), new App.Arcology.Cell.Apartment(1, 1)]
				]));
				V.spire = 1;
			}
		}, {
			id: "fountain",
			layouts: ["dick"],
			exclusive: [], name: "fountain",
			desc: "a monumental fountain on top of the arcology using the latest advancements in building technology making your arcology known far and wide as a great supporter of modern architecture",
			cost: 250000, apply: () => {
				V.building.sections.push(new App.Arcology.Section("fountain", [[
					new App.Arcology.Cell.Decorative({width: 170, rotation: 70, xOffset:-130, yOffset: 220, absoluteWidth:1, cellHeight: 330}),
					new App.Arcology.Cell.Decorative({width: 250, rotation: 95, xOffset:-110, yOffset: 80, absoluteWidth:1}),
					new App.Arcology.Cell.Decorative({width: 190, rotation: 115, xOffset:-30, yOffset: 190, absoluteWidth:1}),
				]]));
				V.spire = 1;
			}
		}
	];

	let count = 0;
	for (let upgrade of upgrades) {
		if (upgrade.layouts.includes(building.layout)
			&& !building.usedUpgrades.includesAny(upgrade.id, ...upgrade.exclusive)) {
			const div = document.createElement("div");
			div.classList.add("choices");
			const cost = upgrade.cost * V.upgradeMultiplierArcology;
			div.append(`...${upgrade.desc}. `, App.UI.DOM.makeElement("span", `This huge project will cost ${cashFormat(cost)} `, "note"),
				App.UI.DOM.passageLink(`Build ${upgrade.name}`, passage(), () => {
					if (building.usedUpgrades.length === 0) {
						// the first major upgrade gives skill, successive ones not, mainly because there might be a
						// different number depending on layout.
						V.PC.skill.engineering++;
					}
					building.usedUpgrades.push(upgrade.id);
					upgrade.apply();
					cashX(-cost, "capEx");
				}));
			outerDiv.append(div);
			count++;
		}
	}

	if (count === 0) {
		return App.UI.DOM.makeElement("span", "The arcology structure is fully upgraded.", "note");
	} else {
		if (count === 1) {
			outerDiv.prepend("The next major upgrade is...");
		} else {
			outerDiv.prepend("Possible major upgrades to the arcology are ...");
		}
		return outerDiv;
	}
};