code:
App.Events.RETSSiblingTussle = class RETSSiblingTussle extends App.Events.BaseEvent {
	eventPrerequisites() {
		return [
			() => V.minimumSlaveAge < 18
		];
	}

	actorPrerequisites() {
		return [
			[ // event slave
				s => s.fetish !== "mindbroken",
				canStand,
				hasAnyArms,
				hasBothLegs,
				s => s.actualAge < 18,
				s => s.sisters > 0
			],
			[ // and her sibling
				s => s.fetish !== "mindbroken",
				canStand,
				hasAnyArms,
				hasBothLegs,
				isSlaveAvailable,
				s => s.actualAge < 18,
				s => areSisters(getSlave(this.actors[0]), s) > 0, // sibling check
				s => App.Utils.jobForAssignment(getSlave(this.actors[0]).assignment).facility.employeesIDs().has(s.ID), // lives in the same facility
			]
		];
	}

	execute(node) {
		/** @type {Array<App.Entity.SlaveState>} */
		let [sib1, sib2] = this.actors.map(a => getSlave(a));
		const {
			he, his, him
		} = getPronouns(sib1);
		const {
			He2, he2, his2, him2
		} = getPronouns(sib2).appendSuffix("2");
		const hostFacility = App.Utils.jobForAssignment(getSlave(this.actors[0]).assignment).facility;

		V.nextLink = "Next Week";

		App.Events.drawEventArt(node, [sib1, sib2], "no clothing");

		let t = [];
		t.push(`As you're conducting your morning rounds, you hear a commotion coming from the shower and dressing area of ${hostFacility.name}.`);
		App.Events.addParagraph(node, t);

		t = [];
		t.push(`Like most siblings who haven't yet developed mature conflict-resolution skills,`);
		t.push(App.UI.DOM.slaveDescriptionDialog(sib1));
		t.push(`and`);
		t.push(contextualIntro(sib1, sib2, "DOM"));
		t.push(`sometimes get physical when they annoy one another. Today a tussle has broken out over some minor slight.`);
		App.Events.addParagraph(node, t);

		t = [];
		t.push(`The other slaves around them are mostly ignoring them in their haste to prepare for their workday, but the still wet and naked ${sib1.slaveName} and ${sib2.slaveName} sure manage to get your attention as they hit, kick, and wrestle with each other. You doubt they'll do any lasting damage, and maybe getting some of that hostile energy out will make them more compliant as the day goes on, but you also consider whether imposing some discipline might be a good idea.`);
		App.Events.addParagraph(node, t);

		App.Events.addResponses(node, [
			new App.Events.Result("Make them kiss and make up", kissAndMakeUp), // don't think there's a need for an incest guard here, they'll break off early if necessary
			new App.Events.Result("Punish them together", punish),
			new App.Events.Result("Just watch and see what happens", watch)
		]);

		function kissAndMakeUp() {
			const frag = document.createDocumentFragment();

			t = [];
			t.push(`You walk into the dressing area and clear your throat loudly. The other slaves pause what they're doing and turn to look at you, either from devotion or fear.`);
			t.push(`After a few seconds, ${sib2.slaveName} ${canHear(sib2) ? "realizes" : "manages to realize"} that things have gone strangely quiet and scrambles to stand and pay attention to you, and ${sib1.slaveName} follows suit when ${he} realizes that the nature of their fight has changed.`);
			App.Events.addParagraph(frag, t);

			t = [];
			t.push(`You tell the rest of the slaves to go back to what they were doing while you talk to ${sib1.slaveName} and ${sib2.slaveName}. You explain to the two of them that they have responsibilities to attend to, and that doesn't include fighting with each other, and order them to apologize to one another and kiss. When ${sib2.slaveName} tries to pin the blame for the fight on ${sib1.slaveName}, you cut ${him2} off and tell ${him2} that you're not interested in who started it, just how they're going to resolve it, and that ${he2} won't like what happens if you think ${he2}'s backtalking you.`);
			App.Events.addParagraph(frag, t);

			t = [];
			t.push(`${He2} gets the idea, and apologizes (with somewhat questionable sincerity) to ${sib1.slaveName} for ${his2} part in things. You turn to ${sib1.slaveName} expectantly, and ${he} also apologizes to ${his} ${relativeTerm(sib1, sib2)}, and then leans in for a ${V.seeIncest === 0 ? "chaste" : ""} kiss, as directed.`);
			App.Events.addParagraph(frag, t);

			if (V.seeIncest > 0 && (sib1.energy > 60 || sib2.energy > 60)) {
				if (App.Utils.sexAllowed(sib1, sib2)) {
					t = [];
					t.push(`The kiss begins to get a little more heated and they glance at you for permission to continue instead of getting back to work immediately. While you know you should really tell them to get back to work, the rules you've set allow them to go further, and they're at least getting along. And it's pretty hot, so you're keen to watch.`);
					App.Events.addParagraph(frag, t);

					t = [];
					t.push(`As soon as they realize you aren't going to stop them, the kiss gets deeper and the groping starts in earnest.`);
					if (sib1.dick > 0) {
						t.push(`${sib2.slaveName} immediately reaches for ${his2} ${relativeTerm(sib2, sib1)}'s dick, fondling and tugging on it to make sure it's hard for what they're about to do.`);
					} else if (sib1.vagina >= 0) {
						t.push(`${sib2.slaveName} immediately reaches for ${his2} ${relativeTerm(sib2, sib1)}'s soft slit, rubbing and massaging it to make sure ${he}'s good and wet.`);
					} else if (sib1.boobs >= 300) {
						t.push(`${sib2.slaveName} moves ${his2}'s hands to ${his2} ${relativeTerm(sib2, sib1)}'s tits, pawing at them and playing with ${his} ${sib1.nipples} nipples.`);
					} else { // null with no tits? c'mon...
						t.push(`${sib2.slaveName} slides ${his2} hand between ${his2} ${relativeTerm(sib2, sib1)}'s thighs, massaging the soft sensitive skin of ${his} crotch.`);
					}
					// reverse order and add a couple extra options to try to avoid similar text
					t.push(`${sib1.slaveName}, meanwhile,`);
					if (sib2.boobs >= 300) {
						t.push(`moves ${his}'s hands to ${his} ${relativeTerm(sib1, sib2)}'s tits, pawing at them and playing with ${his2} ${sib1.nipples} nipples.`);
					} else if (sib2.balls > 0 && sib1.dick > 0) { // avoid double dick groping by going for the balls
						t.push(`grabs ${his} ${relativeTerm(sib1, sib2)}'s balls, fondling them to make sure ${he2}'s going to make plenty of cum.`);
					} else if (sib2.dick > 0) {
						t.push(`grabs ${his} ${relativeTerm(sib1, sib2)}'s dick, fondling and tugging on it to make sure it's hard for what they're about to do.`);
					} else if (sib2.clit > 0 && sib1.vagina >= 0) { // avoid double vagina groping by going for the clit
						t.push(`reaches for ${his} ${relativeTerm(sib1, sib2)}'s clit, rubbing and flicking it to make sure ${he2}'s ready for action.`);
					} else if (sib2.vagina >= 0) {
						t.push(`reaches for ${his} ${relativeTerm(sib1, sib2)}'s soft slit, rubbing and massaging it to make sure ${he2}'s good and wet.`);
					} else {
						t.push(`slides ${his} hand between ${his} ${relativeTerm(sib1, sib2)}'s thighs, massaging the soft sensitive skin of ${his2} crotch.`);
					}
					t.push(`Once they're both good and ready, they get on a dressing bench in a classic 69 position, eagerly bringing each other to orgasm orally while you watch.`);
					seX(sib1, "oral", sib2, "oral");
					App.Events.addParagraph(frag, t);
				} else {
					t = [];
					t.push(`The kiss begins to get a little more heated and you clearly see the lust growing in ${sib1.energy > sib2.energy ? sib1.slaveName : sib2.slaveName}'s eyes, but they know the rules and pull apart before they go too far.`);
					App.Events.addParagraph(frag, t);
				}
			}

			t = [];
			t.push(`They feel better, and they've both learned to <span class="trust inc">trust</span> and <span class="devotion inc">rely on</span> your judgement.`);
			App.Events.addParagraph(frag, t);

			sib1.trust += 2;
			sib1.devotion += 2;
			sib2.trust += 2;
			sib2.devotion += 2;

			return frag;
		}

		function punish() {
			const frag = document.createDocumentFragment();

			t = [];
			t.push(`You step between the two siblings and take a few weak blows before they realize you're there; you see the fear grow on their faces immediately when they realize how much trouble they're in.`);
			t.push(`You grab ${sib2.slaveName} by the throat and throw ${him2} to the ground, ordering ${him2} to stay there until you're ready for ${him2}.`);
			t.push(`Then you push ${sib1.slaveName} into a kneeling position and force your ${V.PC.dick > 0 ? "dick" : "strap-on"} down ${his} throat, facefucking ${him} brutally as you explain that both of them are your property and that every time they fight, they are risking damage to your property.`);
			App.Events.addParagraph(frag, t);

			t = [];
			t.push(`Once you think your message has gotten through, you pull ${sib1.slaveName} off your ${V.PC.dick > 0 ? "dick" : "strap-on"} with a pop, pull ${sib2.slaveName} to ${his2} knees and repeat your chosen punishment on ${him2}.`);
			t.push(`${sib1.slaveName} kneels obediently, crying quietly as you abuse ${his} ${relativeTerm(sib1, sib2)}.`);
			t.push(`When you've finished, you're sure both siblings are more likely to <span class="trust dec">respect the rules,</span> out of fear if nothing else.`);
			App.Events.addParagraph(frag, t);

			sib1.trust -= 5;
			sib2.trust -= 5;

			return frag;
		}

		function watch() {
			const dead1 = deadliness(sib1);
			const dead2 = deadliness(sib2);
			if (Math.abs(dead1 - dead2) <= 1) {
				// fight between equals
				t = [];
				t.push(`The two siblings are fairly closely matched in size and skill, and the nude brawl carries on for a few minutes as the other slaves finish preparing for their day.  Eventually, ${sib1.slaveName} notices that they're going to be late to work, and they agree to a truce.`);
				t.push(`As they dress quickly, they see you standing at the door, and a quick flash of <span class="trust dec">fear</span> passes over their faces as they realize you've probably just seen the whole thing.`);
				t.push(`The fear is replaced with relief when they realize that if you'd wanted to punish them, you probably would have done so already, but they'll leave with the lasting impression that you're always watching.`);

				sib1.trust -= 2;
				sib2.trust -= 2;

				return t;
			} else {
				// there's a clear winner
				const winner = dead1 > dead2 ? sib1 : sib2;
				const loser = dead1 > dead2 ? sib2 : sib1;
				const {
					HeW, HisW, heW, himW, hisW, himselfW
				} = getPronouns(winner).appendSuffix("W");
				const {
					heL, himL, hisL
				} = getPronouns(loser).appendSuffix("L");

				t = [];
				t.push(`${winner.slaveName} has a clear advantage over ${hisW} ${relativeTerm(winner, loser)} and quickly subdues ${himL}.`);
				if (V.seeIncest && App.Utils.sexAllowed(winner, loser) && (winner.energy > 40 || winner.fetish === "dom")) {
					t.push(`Excited by ${hisW} dominance of ${hisW} weaker ${relativeTerm(winner, loser)}, ${heW} quickly begins to take advantage of ${himL}.`);
					let sexType = "oral";
					if (canPenetrate(winner)) {
						// no taking of virginities here
						if (canDoVaginal(loser) && loser.vagina > 0) {
							t.push(`${HeW} flips ${himL} onto ${hisL} back, spreads ${hisL} legs and positions ${himselfW} to penetrate ${himL}.`);
							sexType = "vaginal";
						} else if (canDoAnal(loser) && loser.anus > 0) {
							t.push(`${HeW} flips ${himL} onto ${hisL} belly, spreads ${hisL} buttocks and positions ${himselfW} to penetrate ${hisL} ass.`);
							sexType = "anal";
						}
					}
					if (sexType === "oral") {
						t.push(`${HeW} moves over ${loser.slaveName}'s prone body until ${heW}'s above ${hisL} mouth, and orders ${hisW} ${relativeTerm(winner, loser)} to orally service ${himW}.`);
					}
					if (loser.fetish === "submissive") {
						t.push(`${loser.slaveName} enjoys being dominated anyway, so this situation is good for ${himL} too.`);
					} else if (V.universalRulesConsent === 1) {
						t.push(`${loser.slaveName} knows he's lost; technically ${heL} could reject ${winner.slaveName}'s advances, but ${heL} isn't exactly opposed to this anyway.`);
					} else {
						t.push(`Whether ${loser.slaveName} is OK with this or just accepts that it's happening to ${himL} is unclear, but it doesn't really matter.`);
					}
					seX(winner, "penetrative", loser, sexType);
					if (sexType === "vaginal") {
						t.push(`${winner.slaveName} slides ${hisW} ${winner.dick > 3 ? "stiff prick" : "hard little dick"} into ${hisW} ${relativeTerm(winner, loser)}'s ${loser.vagina < 2 ? "tight slit" : "cunt"} and starts pounding away.`);
						if (canImpreg(loser, winner)) {
							knockMeUp(loser, 10, 0, winner.ID);
						}
					} else if (sexType === "anal") {
						t.push(`${winner.slaveName} slides ${hisW} ${winner.dick > 3 ? "stiff prick" : "hard little dick"} into ${hisW} ${relativeTerm(winner, loser)}'s ${loser.anus < 2 ? "tight ass" : "well-used ass"} and starts pounding away.`);
						if (canImpreg(loser, winner)) {
							knockMeUp(loser, 10, 1, winner.ID);
						}
					} else {
						t.push(`${winner.slaveName} encourages ${loser.slaveName} to ${winner.dick > 0 ? `suck ${himW} off` : `eat ${himW} out`} quickly, knowing that they both still have to get to work.`);
					}
					if (loser.fetish !== "submissive") {
						t.push(`It's not long before ${heW} comes, and the two slaves separate to finish getting ready for work. ${winner.slaveName} had fun this morning, but is still clearly up for more.`);
						if (fetishChangeChance(loser) > jsRandom(0, 100)) {
							loser.fetishKnown = 1;
							loser.fetish = "submissive";
							loser.fetishStrength = 20;
							t.push(`${loser.slaveName}, meanwhile, really enjoyed the feeling of being dominated by ${hisL} ${relativeTerm(loser, winner)}, and has become <span class="fetish gain">submissive</span>.`);
						}
						winner.energy += 4;
					} else {
						t.push(`${HisW} ${relativeTerm(winner, loser)} really gets off on ${hisL} submissive position and comes before ${heW} does, and when they're finished they both leave satisfied, heightening their sex drives.`);
						sib1.energy += 4;
						sib2.energy += 4;
					}
				} else {
					t.push(`${HeW} quickly extracts a promise from ${loser.slaveName} to help ${himW} with ${hisW} work before letting him up, and they resume dressing for work without further incident.`);
					// no stat changes
				}
				return t;
			}
		}
	}
};


code:
App.Events.RETSSimpleAssault = class RETSSimpleAssault extends App.Events.BaseEvent {
	eventPrerequisites() {
		return [
			() => V.universalRulesConsent === 0
		];
	}

	actorPrerequisites() {
		return [
			[ // event slave
				s => s.fetish !== "mindbroken",
				s => s.rules.release.slaves !== 0,
				hasAnyArms,
				canStand,
				canTalk,
				canPenetrate,
				s => s.devotion > 50
			],
			[ // and her sub
				s => s.fetish !== "mindbroken",
				canStand,
				canTalk,
				isSlaveAvailable,
				s => s.relationship <= 2 || s.relationshipTarget !== this.actors[0],
				s => (canDoAnal(s) && s.anus !== 0) || (canDoVaginal(s) && s.vagina !== 0),
				s => s.devotion < 20,
				s => s.belly < 2000,
				s => s.skill.combat === 0,
				s => s.muscles <= 30
			]
		];
	}

	execute(node) {
		/** @type {Array<App.Entity.SlaveState>} */
		let [domSlave, subSlave] = this.actors.map(a => getSlave(a));
		const {
			He, he, his, him, himself
		} = getPronouns(domSlave);
		const {
			He2, he2, His2, his2, him2
		} = getPronouns(subSlave).appendSuffix("2");
		const {say, title: Master} = getEnunciation(domSlave);
		const {title: Master2} = getEnunciation(subSlave);
		const desc = SlaveTitle(domSlave);

		/** @type {App.Entity.PlayerState} */
		let PC = V.PC;

		const vaginal = subSlave.vagina !== 0 && canDoVaginal(subSlave);

		V.nextLink = "Next Week";

		App.Events.drawEventArt(node, [domSlave, subSlave], ["no clothing", subSlave.clothes]);

		let t = [];

		t.push(`You round a corner and almost trip over`);
		t.push(App.UI.DOM.combineNodes(App.UI.DOM.slaveDescriptionDialog(domSlave), "."));
		t.push(`${He}'s on top of another slave, humping away; ${his}`);
		if (domSlave.butt > 8) {
			t.push(`monstrous, naked ass jiggles lewdly`);
		} else if (domSlave.anus > 2) {
			t.push(`loose asspussy winks lewdly`);
		} else if (domSlave.muscles > 30) {
			t.push(`heavily muscled butt flexes powerfully`);
		} else if (domSlave.butt > 3) {
			t.push(`big butt pumps energetically`);
		} else {
			t.push(`nice little butt flexes cutely`);
		}
		t.push(`as ${he} thrusts. You can't see much of the slave ${vaginal ? `lying on ${his2} back` : "face-down"} underneath ${domSlave.slaveName}, but you recognize ${him2} as `);
		t.push(contextualIntro(domSlave, subSlave, "DOM"));
		t.push(`by ${his2} sobbing. ${He2}'s struggling a little, but ${domSlave.slaveName} has ${him2} pinned to the floor by ${his2} ${hasBothArms(subSlave) ? "wrists" : "wrist"}, and ${domSlave.slaveName} is quickly raping the resistance out of the ${SlaveTitle(subSlave)}.`);
		App.Events.addParagraph(node, t);
		t = [];

		t.push(`${domSlave.slaveName} senses your presence above and behind ${him}, and twists ${his} ${domSlave.hColor} head around to ${canSee(domSlave) ? `see who it is. ${He} sees that it's you` : `discern who it is. ${He} realizes that it's you`}, and greets you cheerfully. "${Spoken(domSlave, `Hi, ${Master}`)}," ${he} trills.`);
		if (domSlave.fetishKnown && domSlave.fetish === "sadist") {
			t.push(`"${Spoken(domSlave, `This is so awesome`)}," the sadistic ${desc} gushes. "${Spoken(domSlave, `The crying, the struggling. Thank you for letting us do this`)}."`);
		} else if (domSlave.fetishKnown && domSlave.fetish === "dom") {
			t.push(`"${Spoken(domSlave, `The crying almost makes me feel bad, but fucking a bitch feels so, so good`)}," the dominant ${desc} admits conversationally.`);
		} else if (domSlave.fetishKnown && domSlave.fetish === "pregnancy" && vaginal && canImpreg(subSlave, domSlave)) {
			t.push(`"${Spoken(domSlave, `I couldn't help myself`)}," the ${desc} admits. "${Spoken(domSlave, `${He2}'d look so pretty with a pregnant belly and I just couldn't resist giving ${him2} one. ${He2} tried to say ${he2} didn't want to be a mother, so...`)}"`);
		} else if (domSlave.energy > 95) {
			t.push(`"${Spoken(domSlave, `I can't help myself`)}," the nymphomaniac ${desc} admits breathlessly. "${Spoken(domSlave, `Thank you for letting me take what I need from the other girls`)}."`);
		} else if (domSlave.energy > 60) {
			t.push(`"${Spoken(domSlave, `I couldn't help myself`)}," the ${desc} admits. "${Spoken(domSlave, `I was really, really horny and ${he2} was just, um, there. And ${he2} tried to say no`)}."`);
		} else {
			t.push(`"${Spoken(domSlave, `I know it's not like me`)}," the ${desc} admits. "${Spoken(domSlave, `But I asked ${him2}, like, mostly joking, and ${he2} tried to say no`)}."`);
		}
		App.Events.addParagraph(node, t);
		t = [];

		t.push(`${subSlave.slaveName} ${vaginal ? `looks out from under ${domSlave.slaveName}` : `turns ${his2} head`} and ${canSee(subSlave) ? "looks at" : "faces"} you too. "${Spoken(subSlave, `${Master2}, please`)}," ${he2} begs. "${Spoken(subSlave, `P-please, make ${him} s-stop — mhhh —`)}" ${domSlave.slaveName} shuts ${him2} up by ${vaginal ? `kissing ${his2} unwilling mouth` : `shoving ${his2} face back against the floor`}. Once ${he} has ${subSlave.slaveName} back under control, ${domSlave.slaveName} slows ${his} thrusting, reaches around behind ${himself}, and ${domSlave.vagina !== 0 && canDoVaginal(domSlave) ? `spreads ${his} futa pussy for you` : `pulls one asscheek aside to offer you ${his} anus. To make the offer extra clear, ${he} starts winking it lewdly`}.`);
		App.Events.addParagraph(node, t);
		t = [];

		t.push(`"${Spoken(domSlave, `Please fuck me while I rape ${him2}, ${Master}`)}," ${domSlave.slaveName} ${say}s in a mockery of ${subSlave.slaveName}'s `);
		if (subSlave.voice > 2) {
			t.push(`high-pitched whining.`);
		} else if (subSlave.voice > 1) {
			t.push(`begging.`);
		} else {
			t.push(`deep-voiced begging.`);
		}
		t.push(`"${Spoken(domSlave, `Ooh, or, please, ${Master}, may I flip ${him2} over? I'd love to feel ${PC.dick ? `your cock inside ${him2} alongside mine` : `that strap-on you use inside ${him2} alongside my cock`}`)}!"`);
		App.Events.addParagraph(node, t);
		t = [];

		t.push(`"${Spoken(subSlave, `Please, no`)}," sobs ${subSlave.slaveName}.`);
		App.Events.addParagraph(node, t);
		t = [];

		App.Events.addResponses(node, [
			((canDoAnal(domSlave) && domSlave.anus !== 0) || (canDoVaginal(domSlave) && domSlave.vagina !== 0)
				? new App.Events.Result("Slide in behind", behind)
				: new App.Events.Result()
			),
			new App.Events.Result("Slide in alongside", alongside),
			new App.Events.Result("Put a stop to it", stop)
		]);

		function behind() {
			t = [];

			t.push(`You order ${domSlave.slaveName} to go back to what ${he} was doing. ${He}'s a little disappointed you're not joining in, but ${he} obeys, pounding the crying ${subSlave.slaveName} without mercy. Then ${domSlave.slaveName} feels the head of ${PC.dick ? "your dick" : "a strap-on"} brush ${his} butt. "Ooh!" ${he} squeals, pleased ${he} was wrong after all. "${Spoken(domSlave, `Yes, thank you, ${Master}! Fuck me! Fuck me while I rape ${him2}!`)}" Underneath ${him}, ${subSlave.slaveName} cries harder, even though ${domSlave.slaveName} has to stop ${his} thrusting for a moment to let you inside. In fact, you reflect as you hammer ${domSlave.slaveName}'s`);
			if (domSlave.vagina !== 0 && canDoVaginal(domSlave)) {
				if (domSlave.vagina > 2) {
					t.push(`roomy`);
				} else if (domSlave.vagina > 1) {
					t.push(`delectable`);
				} else {
					t.push(`tight little`);
				}
				t.push(`cunt,`);
			} else {
				if (domSlave.anus > 2) {
					t.push(`gaping`);
				} else if (domSlave.anus > 1) {
					t.push(`relaxed`);
				} else {
					t.push(`poor little`);
				}
				t.push(`asspussy,`);
			}
			t.push(`it's a little strange that ${subSlave.slaveName} seems to think this is worse than just being raped by ${domSlave.slaveName}. After all, having your ${PC.dick ? "turgid cock" : "formidable strap-on"} sliding energetically in and out of ${his} ${domSlave.vagina !== 0 && canDoVaginal(domSlave) ? "womanhood" : "rectum"} is cramping ${domSlave.slaveName}'s style a bit. Maybe it's that ${subSlave.slaveName} is a little squashed under there.`);

			domSlave.devotion += 4;
			subSlave.trust -= 4;
			if (domSlave.vagina !== 0 && canDoVaginal(domSlave)) {
				seX(domSlave, "vaginal", PC, "penetrative");
				if (canImpreg(domSlave, PC)) {
					t.push(knockMeUp(domSlave, 5, 0, -1, true));
				}
			} else {
				seX(domSlave, "anal", PC, "penetrative");
				if (canImpreg(domSlave, PC)) {
					t.push(knockMeUp(domSlave, 5, 1, -1, true));
				}
			}
			if (vaginal) {
				seX(subSlave, "vaginal", domSlave, "penetrative");
				if (canPenetrate(domSlave) && canImpreg(subSlave, domSlave)) {
					t.push(knockMeUp(subSlave, 5, 0, domSlave.ID, true));
				}
			} else {
				seX(subSlave, "anal", domSlave, "penetrative");
				if (canPenetrate(domSlave) && canImpreg(subSlave, domSlave)) {
					t.push(knockMeUp(subSlave, 5, 1, domSlave.ID, true));
				}
			}
			return t;
		}

		function alongside() {
			const fit = vaginal ? subSlave.vagina > 2 : subSlave.anus > 2;
			t = [];

			t.push(`You order ${domSlave.slaveName} to flip ${subSlave.slaveName} over and let you in too. Just as you expected, ${domSlave.slaveName} responds with a vicious giggle, and ${subSlave.slaveName} cries even harder. "${Spoken(subSlave, `Please!`)}" ${he2} screams. "${Spoken(subSlave, `${Master2}, it'll hurt! Please don't!`)}"`);
			if (fit) {
				t.push(`It's not clear what ${he2}'s so worked up about. ${His2} cavernous ${vaginal ? "cunt" : "asshole"} should be able to take two dicks without trouble.`);
			} else {
				if (domSlave.dick < 5) {
					t.push(`It's not clear what ${he2}'s so worked up about. ${PC.dick ? "You're quite large" : "You use a big strap-on"}, but ${domSlave.slaveName}'s penis is reasonably sized. It's not like ${subSlave.slaveName}'s ${vaginal ? "cunt" : "asshole"} is going to be permanently damaged or anything.`);
				} else {
					t.push(`${He2}'s right to be concerned. ${PC.dick ? "You're quite large" : "You use a big strap-on"}, and ${domSlave.slaveName}'s penis is huge too. ${subSlave.slaveName}'s ${vaginal ? "cunt" : "asshole"} is in serious peril.`);
				}
			}
			t.push(`${domSlave.slaveName} pulls out, sits ${his} bare butt down on the floor, and hauls a struggling ${subSlave.slaveName} onto ${his} lap, shoving ${his} stiff prick back where it belongs. Then ${domSlave.slaveName} hauls ${subSlave.slaveName}'s legs back, offering you ${his2} already-occupied hole. ${subSlave.vagina !== 0 && vaginal ? `${subSlave.slaveName} has another hole, and ${he2} tearfully begs you to use it, but in vain.` : ""}`);
			t.push(`You jam yourself inside, enjoying ${subSlave.slaveName}'s ${!fit ? `wriggling and the extreme tightness of ${his2} overfilled insides. ${He2} spasms with pain as you force your way inside ${him2}` : "wriggling"}. ${domSlave.slaveName} can't thrust much from where ${he} is, and serves mostly to tighten ${subSlave.slaveName} for you, but ${he} ${canSee(domSlave) ? "stares into your eyes lovingly" : "lovingly smiles at you"}. Playing such an equal sexual role with you definitely builds ${his} trust in ${his} role. For ${his2} part, ${subSlave.slaveName} is thoroughly degraded, ${fit ? "but physically unhurt." : `and <span class="orange">stretched out.</span>`}`);

			domSlave.trust += 4;
			subSlave.trust -= 4;
			if (vaginal) {
				seX(subSlave, "vaginal", domSlave, "penetrative");
				if (canImpreg(domSlave, PC)) {
					t.push(knockMeUp(domSlave, 5, 0, -1, true));
				}
				if (canPenetrate(domSlave) && canImpreg(subSlave, domSlave)) {
					t.push(knockMeUp(subSlave, 5, 0, domSlave.ID, true));
				}
			} else {
				seX(subSlave, "anal", domSlave, "penetrative");
				if (canImpreg(domSlave, PC)) {
					t.push(knockMeUp(domSlave, 5, 1, -1, true));
				}
				if (canPenetrate(domSlave) && canImpreg(subSlave, domSlave)) {
					t.push(knockMeUp(subSlave, 5, 1, domSlave.ID, true));
				}
			}
			if (!fit) {
				if (vaginal) {
					subSlave.vagina++;
				} else {
					subSlave.anus++;
				}
			}
			return t;
		}

		function stop() {
			t = [];

			t.push(`You order ${domSlave.slaveName} to stop raping ${subSlave.slaveName}.
			"${Spoken(domSlave, `Yes, ${Master}`)}," ${he} ${say}s automatically, and gets up, pulling ${his} dick out of ${subSlave.slaveName}'s poor ${vaginal ? "pussy" : "asshole"}. ${domSlave.slaveName} doesn't understand, and ${his} prick softens quickly with ${his} confusion. ${He} thought ${he} didn't need consent to fuck other slaves, and ${he} doubts ${himself}.`);
			App.Events.addParagraph(node, t);
			t = [];

			t.push(`${subSlave.slaveName} gets to ${his2} feet too, using a hand to massage ${his2} outraged hole. "${Spoken(subSlave, `Thank you, ${Master2}, thank you`)}," ${he2} repeats over and over, <span class="mediumaquamarine">weeping with relief.</span>`);
			App.Events.addParagraph(node, t);
			t = [];

			domSlave.trust -= 4;
			subSlave.trust += 4;
			if (vaginal) {
				seX(subSlave, "vaginal", domSlave, "penetrative");
			} else {
				seX(subSlave, "anal", domSlave, "penetrative");
			}
			return t;
		}
	}
};

repo_name: Welptard/fc

license: mit


code:
/** @param {string} eventName
 * @returns {DocumentFragment}
 */
App.Events.debugEvent = function(eventName) {
	const frag = document.createDocumentFragment();
	/** @type {App.Events.BaseEvent} */
	const event = eval(`new ${eventName}`);

	function makeCastList() {
		const cast = document.createDocumentFragment();
		const actorReqs = event.actorPrerequisites();
		let missingCast = false;
		for (let i = 0; i < actorReqs.length; ++i) {
			if (!event.actors[i]) {
				missingCast = true;
			}
			App.UI.DOM.appendNewElement("div", cast, `Actor ${i}: ${event.actors[i] ? SlaveFullName(getSlave(event.actors[i])) : 'not yet cast'}`);
		}
		if (!missingCast) {
			App.UI.DOM.appendNewElement("div", cast, App.UI.DOM.link('Run event now', (evt) => { V.event = evt; }, [event], "JS Random Event"));
		} else {
			App.UI.DOM.appendNewElement("div", cast, "All actors must be cast to run event.", "note");
		}
		return cast;
	}

	function makeActorList() {
		const actors = document.createDocumentFragment();
		const actorReqs = event.actorPrerequisites();
		for (let i = 0; i < actorReqs.length; ++i) {
			let tab = App.UI.DOM.appendNewElement("div", actors); // TODO: put these in tabs?
			for (const slave of V.slaves) {
				let slaveDiv = App.UI.DOM.appendNewElement("div", tab, App.UI.DOM.makeElement("span", SlaveFullName(slave), "slave-name"));
				let slaveFails = false;
				for (const p of actorReqs[i]) {
					let passed = testPredicate(slaveDiv, p, slave);
					slaveFails = slaveFails || !passed;
					App.UI.DOM.appendNewElement("div", slaveDiv, p.name || p.toString(), [passed ? "green" : "red", "indent"]);
				}
				if (!slaveFails) {
					App.UI.DOM.appendNewElement("div", slaveDiv, App.UI.DOM.link("Choose this slave", castSlave, [slave, i]), "indent");
				}
				App.UI.DOM.appendNewElement("hr", tab);
			}
		}
		return actors;
	}

	function castSlave(slave, index) {
		event.actors[index] = slave.ID;
		$('#castList').empty().append(makeCastList());
		$('#actorList').empty().append(makeActorList());
	}

	function testPredicate(outDiv, p, ...args) {
		let passed = false;
		try {
			passed = p(...args);
		} catch (ex) {
			App.UI.DOM.appendNewElement("div", outDiv, p.name || p.toString() + ": Exception: " + ex.toString(), "major-warning");
		}
		return passed;
	}

	const prereqs = App.UI.DOM.appendNewElement("div", frag);
	App.UI.DOM.appendNewElement("span", prereqs, `${eventName} - Global Prerequisites:`, "note");
	if (event instanceof App.Events.BaseEvent) {
		let anyFailed = false;
		for (const p of event.eventPrerequisites()) {
			let passed = testPredicate(prereqs, p);
			anyFailed = anyFailed || !passed;
			App.UI.DOM.appendNewElement("div", prereqs, p.name || p.toString(), passed ? "green" : "red");
		}
		if (!anyFailed) { // actor casting
			App.UI.DOM.appendNewElement("div", frag, "All global prerequisites passed, proceeding to casting...", "green");
			App.UI.DOM.appendNewElement("hr", frag);
			const castList = App.UI.DOM.appendNewElement("div", frag, makeCastList());
			castList.id = "castList";
			App.UI.DOM.appendNewElement("hr", frag);
			const actorList = App.UI.DOM.appendNewElement("div", frag, makeActorList());
			actorList.id = "actorList";
		}
	} else {
		App.UI.DOM.appendNewElement("div", prereqs, "Specified name does not resolve to an event.", "major-warning");
	}
	return frag;
};


code:
App.Events.drawEventArt = (function() {
	const validSingleOutfits = App.Data.slaveWear.niceClothes.map(c => c.value).concat(App.Data.slaveWear.harshClothes.map(c => c.value));

	/** draw event art, with the option to dress the slave in a particular way
	 * @param {Node} node - DOM node to attach art to
	 * @param {App.Entity.SlaveState|App.Entity.SlaveState[]} slaves - one or several slaves to draw art for
	 * @param {string|string[]} [clothesMode] - if the slaves' clothing should be overridden, what should they be wearing?
	 * @param {object|object[]} [extraClothes] - if other parts of the slaves' clothing should be overridden, what should they be wearing?  For slave.vaginalAccessory, use [{"vaginalAccessory": "dildo"}]
	 */
	function draw(node, slaves, clothesMode, extraClothes) {
		// do nothing if the player doesn't want images
		if (!V.seeImages) {
			return;
		}

		// ensure that slaves is an array
		if (!Array.isArray(slaves)) {
			slaves = [slaves];
		}

		// if we were asked to change the slave's clothing, do it now
		let originalClothes = [];
		if (clothesMode || extraClothes) {
			// if clothesMode is just a single string (or null/undefined), apply the same clothes to all the slaves
			if (!Array.isArray(clothesMode)) {
				clothesMode = new Array(slaves.length).fill(clothesMode);
			}
			extraClothes = extraClothes || {}; // must be defined
			if (!Array.isArray(extraClothes)) {
				extraClothes = new Array(slaves.length).fill(extraClothes);
			}

			// if the arrays are not the right length now, throw.  it's all or nothing.
			if (clothesMode.length !== slaves.length || extraClothes.length !== slaves.length) {
				throw "Incorrect number of outfits specified for slaves in event art";
			}

			// clothes have been specified - copy the slaves and change their clothing (a bit slow, but means we don't need housekeeping to change them back)
			slaves.forEach((s, i) => {
				// if there are "themes" of clothes that multiple events want to use ("swimwear", "athletic", "casual", etc), they can be added as special cases here instead of duplicating the logic in every event
				if (validSingleOutfits.includes(clothesMode[i])) {
					extraClothes[i].clothes = clothesMode[i];
				} else if (!clothesMode[i]) {
					// no change of outfit, leave them dressed as they were
				} else {
					// unrecognized outfit - leave them dressed as they were, but error
					console.error(`Unrecognized clothes mode for event art: ${clothesMode[i]}`);
				}
				originalClothes[i] = equipClothing(s, extraClothes[i]);
			});
		}

		// actually draw the art - large if single slave, medium column if multiple slaves
		let artSpan = document.createElement("span");
		artSpan.id = "art-frame";
		if (slaves.length === 1) {
			let refDiv = document.createElement("div");
			refDiv.classList.add("imageRef", V.imageChoice === 1 ? "lrgVector" : "lrgRender");
			let maskDiv = document.createElement("div");
			maskDiv.classList.add("mask");
			maskDiv.appendChild(document.createTextNode("\u00a0"));
			refDiv.appendChild(maskDiv);
			refDiv.appendChild(App.Art.SlaveArtElement(slaves[0], 2, 0));
			artSpan.appendChild(refDiv);
		} else {
			let colDiv = document.createElement("div");
			colDiv.classList.add("imageColumn");
			for (const slave of slaves) {
				let refDiv = document.createElement("div");
				refDiv.classList.add("imageRef", "medImg");
				refDiv.appendChild(App.Art.SlaveArtElement(slave, 2, 0));
				colDiv.appendChild(refDiv);
			}
			artSpan.appendChild(colDiv);
		}
		node.appendChild(artSpan);

		// change clothing back, if necessary
		if (originalClothes.length > 0) {
			slaves.forEach((s, i) => {
				Object.assign(s, originalClothes[i]);
			});
		}
	}

	return draw;

	/**
	 * @param {App.Entity.SlaveState} s - one or several slaves to draw art for
	 * @param {object} newClothes
	 */
	function equipClothing(s, newClothes) {
		let oldClothes = {};
		if (typeof newClothes === "object") {
			for (const extra in newClothes) {
				oldClothes[extra] = s[extra];
				s[extra] = newClothes[extra];
			}
		} else {
			throw "Extra clothes must be in the form of an object.";
		}
		return oldClothes;
	}
})();

/** intelligently adds spaces to an array of mixed strings and DOM nodes, merging consecutive strings in the process
 * @param {Array<string|HTMLElement|DocumentFragment>} sentences
 * @returns {Array<string|HTMLElement|DocumentFragment>}
 */
App.Events.spaceSentences = function(sentences) {
	if (sentences.length <= 1) {
		return sentences;
	}
	return sentences.reduce((res, cur) => {
		if (res.length === 0) {
			res.push(cur);
		} else if (typeof (res[res.length - 1]) === "string") {
			if (typeof (cur) === "string") {
				res[res.length - 1] += " " + cur;
			} else {
				res[res.length - 1] += " ";
				res.push(cur);
			}
		} else {
			if (typeof (cur) === "string") {
				res.push(" " + cur);
			} else {
				res.push(" ");
				res.push(cur);
			}
		}
		return res;
	}, []);
};

/** assemble a DOM paragraph from an array of DOM nodes, sentences or sentence fragments (which may contain HTML)
 * @param {Node} node
 * @param {Array<string|HTMLElement|DocumentFragment>} sentences
 */
App.Events.addParagraph = function(node, sentences) {
	let para = document.createElement("p");
	$(para).append(...App.Events.spaceSentences(sentences));
	node.appendChild(para);
};

/** assemble an element from an array of DOM nodes, sentences or sentence fragments (which may contain HTML)
 * @param {Node} node
 * @param {Array<string|HTMLElement|DocumentFragment>} sentences
 * @param {string} [element]
 */
App.Events.addNode = function(node, sentences, element) {
	const el = (element) ? document.createElement(element) : new DocumentFragment();
	$(el).append(...App.Events.spaceSentences(sentences));
	node.appendChild(el);
};

/** result handler callback - process the result and return an array of mixed strings and DOM nodes, or a single string or DOM node
 * @callback resultHandler
 * @returns {Array<string|HTMLElement|DocumentFragment>|string|HTMLElement|DocumentFragment}
 */
/** a response to an event, and its result */
App.Events.Result = class {
	/** @param {string} [text] - the link text for the response
	 *  @param {resultHandler} [handler] - the function to call to generate the result when the link is clicked
	 *  @param {string} [note] - a note to provide alongside the link (for example, a cost or virginity loss warning)
	 *  To mark an option as disabled, construct the result with only the note.
	 */
	constructor(text, handler, note) {
		this.text = text;
		this.handler = handler;
		this.note = note;
	}

	/** call the result handler, replacing the contents of the given span ID
	 * @param {string} resultSpanID
	 */
	handle(resultSpanID) {
		let frag = document.createDocumentFragment();
		let contents = this.handler();
		if (Array.isArray(contents)) {
			$(frag).append(...App.Events.spaceSentences(contents));
		} else {
			$(frag).append(contents);
		}
		App.UI.DOM.replace(`#${resultSpanID}`, frag);
	}

	/** build the response DOM (for use by addResponses)
	 * @param {HTMLElement} node
	 * @returns {boolean} - true if something was written, false if not
	 */
	makeResponse(node) {
		let wrote = false;
		if (this.text && this.handler) {
			node.appendChild(App.UI.DOM.link(this.text, () => this.handle(node.id)));
			wrote = true;
		}
		if (wrote && this.note) {
			$(node).append(" ");
		}
		if (this.note) {
			node.appendChild(App.UI.DOM.makeElement("span", this.note, "detail"));
			wrote = true;
		}
		return wrote;
	}
};

/** add a list of results for an event
 * @param {Node} node
 * @param {Array<App.Events.Result>} results
 * @param {string} [elementID="result"]
 */
App.Events.addResponses = function(node, results, elementID = "result") {
	let resultSpan = document.createElement("span");
	resultSpan.id = elementID;
	for (const result of results) {
		if (result.makeResponse(resultSpan)) {
			resultSpan.appendChild(document.createElement("br"));
		}
	}
	node.appendChild(resultSpan);
};
/**
 *
 * @param {App.Entity.SlaveState} slave
 * @param {string} fetish
 * @returns {Node}
 */
App.Events.eventFetish = function(slave, fetish) {
	const el = new DocumentFragment();
	const {his, him, he} = getPronouns(slave);
	let text = "";
	if (slave.fetish === fetish) {
		el.append(`This experience `);
		if (slave.fetishKnown === 1) {
			if (slave.fetishStrength <= 95) {
				switch (slave.fetish) {
					case "submissive":
						text = `deepens ${his} sexual need to submit.`;
						break;
					case "cumslut":
						text = `increases ${his} appetite for oral sex.`;
						break;
					case "humiliation":
						text = `makes ${him} even more eager to be humiliated.`;
						break;
					case "buttslut":
						text = `encourages ${him} to focus on anal sex.`;
						break;
					case "boobs":
						text = `focuses ${his} sexuality on ${his} breasts and nipples.`;
						break;
					case "sadist":
						text = `titillates the sadistic part of ${him}.`;
						break;
					case "masochist":
						text = `helps conflate pain and sexual pleasure for ${him}.`;
						break;
					case "dom":
						text = `strengthens ${his} desire to top other slaves.`;
						break;
					case "pregnancy":
						text = `further confuses lust and pregnancy in ${his} mind.`;
						break;
					default:
						throw `ERROR: bad fetish "${slave.fetish}"`;
				}
				App.UI.DOM.appendNewElement("span", el, text, "lightsalmon");
				slave.fetishStrength += 10;
			} else {
				el.append(`is `);
				App.UI.DOM.appendNewElement("span", el, `especially enjoyable for ${him},`, "hotpink");
				el.append(` since `);
				switch (slave.fetish) {
					case "submissive":
						el.append(`${he}'s an utterly submissive little slut.`);
						break;
					case "cumslut":
						el.append(`${his} `);
						if (slave.vagina > -1) {
							el.append(`clit`);
						} else {
							el.append(`frenulum`);
						}
						el.append(` might as well be located in ${his} throat.`);
						break;
					case "humiliation":
						el.append(`${he} has a hopeless need to be humiliated and degraded.`);
						break;
					case "buttslut":
						el.append(`${he}'s completely in love with having ${his} ass fucked.`);
						break;
					case "boobs":
						el.append(`${he} loves breast play more than any other kind of sex.`);
						break;
					case "sadist":
						el.append(`nothing is as titillating to ${him} as another slave's pain can be.`);
						break;
					case "masochist":
						el.append(`${he} loves pain more than ${he} likes pleasure.`);
						break;
					case "dom":
						el.append(`${he} lives for the times ${he}'s allowed to fuck someone.`);
						break;
					case "pregnancy":
						el.append(`all ${his} deepest sexual desires involve `);
						if (slave.vagina > -1 || slave.mpreg === 1) {
							el.append(`pregnancy.`);
						} else {
							el.append(`impregnation.`);
						}
						break;
					default:
						throw `ERROR: bad fetish "${slave.fetish}"`;
				}
				slave.devotion += 3;
			}
		} else {
			el.append(`was surprisingly appealing for ${him}, revealing that ${he}'s a `);
			switch (slave.fetish) {
				case "submissive":
					text = `sexual submissive!`;
					break;
				case "humiliation":
					text = `humiliation slut!`;
					break;
				case "boobs":
					text = `slut for breast play!`;
					break;
				case "pregnancy":
					text = `pregslut!`;
					break;
				default:
					text = `slave!`;
			}
			App.UI.DOM.appendNewElement("span", el, text, "lightsalmon");
			slave.fetishKnown = 1;
		}
	}
	return el;
};


code:
App.Intro.generateEstablishedArcologies = function() {
	/* setup */
	let allowedFS = ["ArabianRevivalist", "AssetExpansionist", "AztecRevivalist", "BodyPurist", "ChattelReligionist", "ChineseRevivalist", "EdoRevivalist", "EgyptianRevivalist", "Eugenics", "HedonisticDecadence", "IntellectualDependency", "MaturityPreferentialist", "Multiculturalist", "Pastoralist", "Paternalist", "PetiteAdmiration", "PhysicalIdealist", "Repopulationist", "RomanRevivalist", "SlaveProfessionalism", "SlimnessEnthusiast", "StatuesqueGlorification", "Subjugationist", "Supremacist", "TransformationFetishist", "YouthPreferentialist", "NeoImperialist"];
	if (V.seeDicks !== 0) {
		allowedFS.push("GenderRadicalist");
	}
	if (V.seeDicks !== 100) {
		allowedFS.push("GenderFundamentalist");
	}
	if (V.seeExtreme !== 0) {
		allowedFS.push("Degradationist");
	}
	const terrainTypes = ["marine", "marine", "oceanic", "ravine", "rural", "rural", "rural", "urban", "urban"];
	const continents = ["Africa", "Asia", "Asia", "Australia", "Western Europe", "Southern Europe", "Central Europe", "Eastern Europe", "Scandinavia", "Japan", "North America", "North America", "South America", "the Middle East"];
	const races = ["amerindian", "asian", "black", "indo-aryan", "latina", "malay", "middle eastern", "mixed race", "pacific islander", "semitic", "southern european", "white"];

	let targets = 4;
	if (V.PC.career === "arcology owner") {
		targets += 2;
	}

	/* generation */
	const fragment = document.createDocumentFragment();
	for (let i = 0; i < targets; i++) {
		fragment.append(arcologyCard());
	}
	return fragment;

	function arcologyCard() {
		const arcology = generateArcology();
		const div = document.createElement("div");
		div.classList.add("card");

		div.append(App.UI.DOM.passageLink(arcology.name, "Intro Summary", () => {
			V.targetArcology = arcology;
			V.terrain = arcology.terrain;
			V.continent = arcology.continent;
			V.language = arcology.language;
			arcology.apply();
		}));

		div.append(" is an established arcology located in a Free City ");
		if (arcology.terrain === "urban") {
			div.append(`carved out of an urban area of ${arcology.continent}.`);
		} else if (arcology.terrain === "rural") {
			div.append(`built in a rural area of ${arcology.continent}.`);
		} else if (arcology.terrain === "marine") {
			div.append(`constructed just offshore of ${arcology.continent}.`);
		} else if (arcology.terrain === "ravine") {
			div.append(`constructed in a large canyon of ${arcology.continent}.`);
		} else {
			div.append(`in the middle of the ocean.`);
		}

		function newLine(...content) {
			const line = document.createElement("div");
			line.classList.add("indent");
			line.append(...content);
			div.append(line);
		}

		if (arcology.prosperity >= 60) {
			newLine("It is unusually prosperous for a vulnerable arcology.");
		} else if (arcology.prosperity <= 40) {
			newLine("It has little economic prosperity and is vulnerable.");
		}

		if (arcology.citizens > 0) {
			newLine("It has an unusually high ratio of citizens to sex slaves, increasing demand for sexual services.");
		} else if (arcology.citizens < 0) {
			newLine("It has an unusually low ratio of citizens to sex slaves, reducing demand for sexual services.");
		}

		let innerDiv = document.createElement("div");
		innerDiv.classList.add("indent");
		div.append(innerDiv);

		innerDiv.append("Its society ");
		if (arcology.FSProgress >= 50) {
			innerDiv.append("has advanced towards");
		} else if (arcology.FSProgress >= 30) {
			innerDiv.append("has devoted resources to");
		} else {
			innerDiv.append("has just begun to adopt");
		}
		innerDiv.append(" ");
		switch (arcology.fs) {
			case "Supremacist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Supremacy of the ${arcology.race} ${arcology.race !== "mixed race" ? "race" : ""}.`, ["intro", "question"]));
				break;
			case "Subjugationist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Subjugation of the ${arcology.race} ${arcology.race !== "mixed race" ? "race" : ""}.`, ["intro", "question"]));
				break;
			case "GenderRadicalist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Gender Radicalism,`, ["intro", "question"]), " a movement that supports feminization of slavegirls with dicks.");
				break;
			case "GenderFundamentalist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Gender Fundamentalism,`, ["intro", "question"]), " a reaction to modern libertinism that seeks to reinforce gender roles.");
				break;
			case "Paternalist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Paternalism,`, ["intro", "question"]), " an optimistic strain of slavery that protects and improves slaves.");
				break;
			case "Degradationist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Degradationism,`, ["intro", "question"]), " an extreme branch of modern slavery that treats slaves as subhuman.");
				break;
			case "AssetExpansionist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Asset Expansionism,`, ["intro", "question"]), " a societal preoccupation with expansion of body parts, especially breasts.");
				break;
			case "SlimnessEnthusiast":
				innerDiv.append(App.UI.DOM.makeElement("span", `Slimness Enthusiasm,`, ["intro", "question"]), " an aesthetic movement that fetishizes the lithe female form.");
				break;
			case "TransformationFetishist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Transformation Fetishism,`, ["intro", "question"]), " a focus on implants and other kinds of surgical alteration.");
				break;
			case "BodyPurist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Body Purism,`, ["intro", "question"]), " a reaction to extreme surgical fetishism that prefers bodies grown biologically.");
				break;
			case "MaturityPreferentialist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Maturity Preferentialism,`, ["intro", "question"]), " an appetite for mature slaves based on MILF fetishism.");
				break;
			case "YouthPreferentialist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Youth Preferentialism,`, ["intro", "question"]), " which focuses on youth and virginity in slaves.");
				break;
			case "Pastoralist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Pastoralism,`, ["intro", "question"]), " an appetite for products of the human body, especially milk.");
				break;
			case "PhysicalIdealist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Physical Idealism,`, ["intro", "question"]), " an aspirational movement which fetishizes muscle and physical fitness.");
				break;
			case "ChattelReligionist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Chattel Religionism,`, ["intro", "question"]), " a religious revival in the context of modern slavery.");
				break;
			case "RomanRevivalist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Roman Revivalism,`, ["intro", "question"]), " which seeks to recreate the glory that was ancient Rome.");
				innerDiv.append(App.UI.DOM.makeElement("div", "It has an established lingua franca: Latin."));
				break;
			case "NeoImperialist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Neo-Imperialism,`, ["intro", "question"]), "adopting old world customs and remodeling them under an absolutist Imperial fist.");
				break;
			case "AztecRevivalist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Aztec Revivalism,`, ["intro", "question"]), " which aspires to reach the heights of the Aztec Empire at it's peak.");
				innerDiv.append(App.UI.DOM.makeElement("div", "It has an established lingua franca: Nahuatl."));
				break;
			case "EgyptianRevivalist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Egyptian Revivalism,`, ["intro", "question"]), " a movement to rebuild the monuments and greatness of ancient Egypt.");
				innerDiv.append(App.UI.DOM.makeElement("div", "It has an established lingua franca: Ancient Egyptian."));
				break;
			case "EdoRevivalist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Edo Revivalism,`, ["intro", "question"]), " an insular movement with a focus on the cultural superiority of old Japan.");
				innerDiv.append(App.UI.DOM.makeElement("div", "It has an established lingua franca: Japanese."));
				break;
			case "ArabianRevivalist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Arabian Revivalism,`, ["intro", "question"]), " a melding of Arabian history and recent mythology of the Near East.");
				innerDiv.append(App.UI.DOM.makeElement("div", "It has an established lingua franca: Arabic."));
				break;
			case "ChineseRevivalist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Chinese Revivalism,`, ["intro", "question"]), " which modernizes the assumed superiority of the Middle Kingdom.");
				innerDiv.append(App.UI.DOM.makeElement("div", "It has an established lingua franca: Chinese."));
				break;
			case "Repopulationist":
				innerDiv.append(App.UI.DOM.makeElement("span", `Repopulationism,`, ["intro", "question"]), " the belief that the key to humanity's survival is a child in every fertile womb.");
				break;
			case "Eugenics":
				innerDiv.append(App.UI.DOM.makeElement("span", `Eugenics,`, ["intro", "question"]), " the belief that the world's failings were caused by rampant breeding of the inferior, and as such, only society's best should reproduce.");
				break;
			case "HedonisticDecadence":
				innerDiv.append(App.UI.DOM.makeElement("span", `Decadent Hedonism,`, ["intro", "question"]), " a movement to embody life's pleasures, particularly eating and sex.");
				break;
			case "IntellectualDependency":
				innerDiv.append(App.UI.DOM.makeElement("span", `Intellectual Dependency,`, ["intro", "question"]), " an appetite for horny, stupid slaves based on bimbo fetishism.");
				break;
			case "SlaveProfessionalism":
				innerDiv.append(App.UI.DOM.makeElement("span", `Slave Professionalism,`, ["intro", "question"]), " a strain of slavery that seeks smart, skilled, elegant slaves to hone to perfection.");
				break;
			case "PetiteAdmiration":
				innerDiv.append(App.UI.DOM.makeElement("span", `Petite Admiration,`, ["intro", "question"]), " which prefers its slaves to stand heads and shoulders shorter than their masters.");
				break;
			case "StatuesqueGlorification":
				innerDiv.append(App.UI.DOM.makeElement("span", `Statuesque Glorification,`, ["intro", "question"]), " an obsession, both sexual and otherwise, over height.");
				break;
			default:
				innerDiv.append(App.UI.DOM.makeElement("span", `Multiculturalism,`, ["intro", "question"]), " a celebration of the total liberty that was the original purpose of the Free Cities.");
		}
		div.append(innerDiv);
		div.append(App.UI.DOM.makeElement("span", arcology.building.render(), "intro"));
		return div;
	}

	function generateArcology() {
		const arcology = {};
		arcology.fs = getFS();
		arcology.name = getName();
		arcology.FSProgress = either(10, 30, 50);
		arcology.prosperity = either(40, 50, 60);
		arcology.citizens = random(-1, 1);
		arcology.terrain = terrainTypes.random();
		arcology.continent = continents.random();
		arcology.language = getLanguage();

		const env = {terrain: arcology.terrain, established: true, fs: arcology.fs};
		const preset = App.Arcology.randomPreset(env).construct(env);
		arcology.building = preset.building;
		arcology.apply = preset.apply;

		return arcology;

		function getFS() {
			const type = allowedFS.pluck();
			if (type === "Supremacist" || type === "Subjugationist") {
				arcology.race = races.random();
			}
			return type;
		}

		function getName() {
			switch (arcology.fs) {
				case "Supremacist":
					switch (arcology.race) {
						case "white":
							return setup.ArcologyNamesSupremacistWhite.random();
						case "asian":
							return setup.ArcologyNamesSupremacistAsian.random();
						case "latina":
							return setup.ArcologyNamesSupremacistLatina.random();
						case "middle eastern":
							return setup.ArcologyNamesSupremacistMiddleEastern.random();
						case "black":
							return setup.ArcologyNamesSupremacistBlack.random();
						case "indo-aryan":
							return setup.ArcologyNamesSupremacistIndoAryan.random();
						case "pacific islander":
							return setup.ArcologyNamesSupremacistPacificIslander.random();
						case "malay":
							return setup.ArcologyNamesSupremacistMalay.random();
						case "amerindian":
							return setup.ArcologyNamesSupremacistAmerindian.random();
						case "southern european":
							return setup.ArcologyNamesSupremacistSouthernEuropean.random();
						case "semitic":
							return setup.ArcologyNamesSupremacistSemitic.random();
						default:
							return setup.ArcologyNamesSupremacistMixedRace.random();
					}
				case "Subjugationist":
					switch (arcology.race) {
						case "white":
							return setup.ArcologyNamesSubjugationistWhite.random();
						case "asian":
							return setup.ArcologyNamesSubjugationistAsian.random();
						case "latina":
							return setup.ArcologyNamesSubjugationistLatina.random();
						case "middle eastern":
							return setup.ArcologyNamesSubjugationistMiddleEastern.random();
						case "black":
							return setup.ArcologyNamesSubjugationistBlack.random();
						case "indo-aryan":
							return setup.ArcologyNamesSubjugationistIndoAryan.random();
						case "pacific islander":
							return setup.ArcologyNamesSubjugationistPacificIslander.random();
						case "malay":
							return setup.ArcologyNamesSubjugationistMalay.random();
						case "amerindian":
							return setup.ArcologyNamesSubjugationistAmerindian.random();
						case "southern european":
							return setup.ArcologyNamesSubjugationistSouthernEuropean.random();
						case "semitic":
							return setup.ArcologyNamesSubjugationistSemitic.random();
						default:
							return setup.ArcologyNamesSubjugationistMixedRace.random();
					}
				case "GenderRadicalist":
					return setup.ArcologyNamesGenderRadicalist.random();
				case "GenderFundamentalist":
					return setup.ArcologyNamesGenderFundamentalist.random();
				case "Paternalist":
					return setup.ArcologyNamesPaternalist.random();
				case "Degradationist":
					return setup.ArcologyNamesDegradationist.random();
				case "AssetExpansionist":
					return setup.ArcologyNamesAssetExpansionist.random();
				case "SlimnessEnthusiast":
					return setup.ArcologyNamesSlimnessEnthusiast.random();
				case "TransformationFetishist":
					return setup.ArcologyNamesTransformationFetishist.random();
				case "BodyPurist":
					return setup.ArcologyNamesBodyPurist.random();
				case "MaturityPreferentialist":
					return setup.ArcologyNamesMaturityPreferentialist.random();
				case "YouthPreferentialist":
					if (V.pedo_mode === 1 || V.minimumSlaveAge < 6) {
						return setup.ArcologyNamesYouthPreferentialistLow.random();
					} else if (V.minimumSlaveAge < 14) {
						return setup.ArcologyNamesYouthPreferentialist.concat(setup.ArcologyNamesYouthPreferentialistLow).random();
					} else {
						return setup.ArcologyNamesYouthPreferentialist.random();
					}
				case "Pastoralist":
					return setup.ArcologyNamesPastoralist.random();
				case "PhysicalIdealist":
					return setup.ArcologyNamesPhysicalIdealist.random();
				case "ChattelReligionist":
					return setup.ArcologyNamesChattelReligionist.random();
				case "RomanRevivalist":
					return setup.ArcologyNamesRomanRevivalist.random();
				case "NeoImperialist":
					return setup.ArcologyNamesNeoImperialist.random();
				case "AztecRevivalist":
					return setup.ArcologyNamesAztecRevivalist.random();
				case "EgyptianRevivalist":
					return setup.ArcologyNamesEgyptianRevivalist.random();
				case "EdoRevivalist":
					return setup.ArcologyNamesEdoRevivalist.random();
				case "ArabianRevivalist":
					return setup.ArcologyNamesArabianRevivalist.random();
				case "ChineseRevivalist":
					return setup.ArcologyNamesChineseRevivalist.random();
				case "Repopulationist":
					return setup.ArcologyNamesRepopulationist.random();
				case "Eugenics":
					return setup.ArcologyNamesEugenics.random();
				case "HedonisticDecadence":
					return setup.ArcologyNamesHedonisticDecadence.random();
				case "IntellectualDependency":
					return setup.ArcologyNamesIntellectualDependency.random();
				case "SlaveProfessionalism":
					return setup.ArcologyNamesSlaveProfessionalism.random();
				case "PetiteAdmiration":
					return setup.ArcologyNamesPetiteAdmiration.random();
				case "StatuesqueGlorification":
					return setup.ArcologyNamesStatuesqueGlorification.random();
				default:
					return "Arcology X-4";
			}
		}

		function getLanguage() {
			switch (arcology.fs) {
				case "RomanRevivalist":
					return "Latin";
				case "AztecRevivalist":
					return "Nahuatl";
				case "EgyptianRevivalist":
					return "Ancient Egyptian";
				case "EdoRevivalist":
					return "Japanese";
				case "ArabianRevivalist":
					return "Arabic";
				case "ChineseRevivalist":
					return "Chinese";
				default:
					switch (arcology.terrain) {
						case "South America":
							return "Spanish";
						case "Brazil":
							return "Portuguese";
						case "the Middle East":
						case "Africa": /* shouldn't that be portuguese, spanish or something? */
							return "Arabic";
						case "Asia":
							return "Chinese";
						case "Central Europe":
							return "German";
						case "Eastern Europe":
							return "Russian";
						case "Western Europe":
							return "English";
						case "Southern Europe":
							return "Italian";
						case "Scandinavia":
							return "Norwegian";
						case "Japan":
							return "Japanese";
						case "oceanic":
						case "North America":
						case "Australia":
						default:
							return "English";
					}
			}
		}
	}
};


code:
.customize-slave-trade-ul {
    list-style-type: none;
    content: '';
    width: 100%;
    display: inline-block;
}

.customize-slave-trade-li-container {
	float: right;
	width: 5em;
	text-align: left;
}

.customize-slave-trade-li {
    text-align: center;
    display: inline-block;
    padding: 2px;
    width: 15em;
}

span.plusButton {
	display: inline-block;
	line-height: 25px;
	width: 20px;
	text-align: center;
	border: 1px solid rgba(0, 139, 0, 0.3);
	background: rgba(0, 139, 0, 0.2);
	margin: 2px;
}

span.minusButton {
	display: inline-block;
	line-height: 25px;
	width: 20px;
	text-align: center;
	border: 1px solid rgba(184, 0, 0, 0.3);
	background: rgba(184, 0, 0, 0.2);
	margin: 2px;
}

span.zeroButton {
	display: inline-block;
	line-height: 25px;
	width: 20px;
	text-align: center;
	border: 1px solid rgba(0, 0, 255, 0.3);
	background: rgba(0, 0, 255, 0.2);
	margin: 2px;
}

span.plusButton:hover { background: rgba(0, 139, 0, 0.4); }
span.minusButton:hover { background: rgba(184, 0, 0, 0.4); }
span.zeroButton:hover { background: rgba(0, 0, 255, 0.4); }

span.plusButton > a { display: block; }
span.minusButton > a { display: block; }
span.zeroButton > a { display: block; }

span.plusButton > a:hover { text-decoration: none; }
span.minusButton > a:hover { text-decoration: none; }
span.zeroButton > a:hover { text-decoration: none; }


code:
App.UI.CustomSlaveTrade = function() {
	const frag = new DocumentFragment();
	let span = document.createElement("span");
	let baseControlsFilter = "all";
	App.UI.DOM.appendNewElement("p", frag, `When civilization turned upon itself, some countries readily took to enslaving their own. Others were raided by their neighbors for their desirable, and profitable, citizens. Which nationalities were most affected by the booming slave trade, and thus, likely to appear in your local slave markets?`);
	App.UI.DOM.appendNewElement("p", frag, `+ to increase pop. – to reduce pop. 0 to remove entirely`, "bold");

	span.id = "pop-control";
	span.append(baseNationalitiesControls());
	frag.append(span);

	return frag;

	function baseNationalitiesControls() {
		const frag = new DocumentFragment();
		let p = document.createElement("p");
		let destination;
		let div;
		let array;
		let list;
		let span;
		const nationalitiesCheck = App.UI.nationalitiesCheck();

		if (hashSum(V.nationalities) < 1) {
			App.UI.DOM.appendNewElement("div", p, `You cannot be a slaveowner without a slave trade. Please add nationalities to continue.`, "note");
		} else {
			destination = (V.customWA === 0) ? "Intro Summary" : "Extreme Intro";
			App.UI.DOM.appendNewElement(
				"div",
				p,
				App.UI.DOM.passageLink(
					"Confirm customization",
					destination,
					() => baseControlsFilter = undefined
				)
			);
		}
		frag.append(p);

		App.UI.DOM.appendNewElement("p", frag, App.UI.nationalitiesDisplay());

		p = document.createElement("p");

		/* Fine control tweaking of populations */
		App.UI.DOM.appendNewElement("div", p, `Adjust slave populations:`);

		p.append(sectionBreak());

		/* Filter controls */
		div = document.createElement("div");
		App.UI.DOM.appendNewElement("span", div, `Filter by Race: `);
		array = [];
		for (let i = 0; i < setup.filterRaces.length; i++) {
			const race = setup.filterRaces[i];
			if (baseControlsFilter === uncapFirstChar(race).replace(/[ -]/g, '')) {
				array.push(
					App.UI.DOM.disabledLink(
						race,
						["currently selected race"]
					)
				);
			} else {
				array.push(
					App.UI.DOM.link(
						race,
						() => {
							baseControlsFilter = uncapFirstChar(setup.filterRaces[i]).replace(/[ -]/g, '');
							refresh();
						}
					)
				);
			}
		}
		div.append(App.UI.DOM.generateLinksStrip(array));
		p.append(div);

		div = document.createElement("div");
		App.UI.DOM.appendNewElement("span", div, `Filter by Region: `);
		array = [];
		for (let i = 0; i < setup.filterRegions.length; i++) {
			const region = setup.filterRegions[i];
			if (baseControlsFilter === uncapFirstChar(region).replace(/[ -]/g, '')) {
				array.push(
					App.UI.DOM.disabledLink(
						region,
						["currently selected region"]
					)
				);
			} else {
				array.push(
					App.UI.DOM.link(
						region,
						() => {
							baseControlsFilter = uncapFirstChar(setup.filterRegions[i]).replace(/[ -]/g, '');
							refresh();
						}
					)
				);
			}
		}
		div.append(App.UI.DOM.generateLinksStrip(array));
		p.append(div);

		p.append(sectionBreak());

		div = document.createElement("div");
		div.style.whiteSpace = "nowrap";
		div.style.float = "left";

		/* Unfiltered pop controls */
		list = document.createElement("UL");
		list.classList.add("customize-slave-trade-ul");
		if (baseControlsFilter === "all") {
			for (let i = 0; i < setup.baseNationalities.length; i++) {
				const nation = setup.baseNationalities[i];
				const li = document.createElement("LI");
				li.classList.add("customize-slave-trade-li");
				li.append(nation);

				span = document.createElement("span");
				span.classList.add("customize-slave-trade-li-container");
				App.UI.DOM.appendNewElement(
					"span",
					span,
					App.UI.DOM.link(
						`+`,
						() => {
							hashPush(V.nationalities, setup.baseNationalities[i]);
							refresh();
						}
					),
					"plusButton"
				);
				if (nationalitiesCheck[nation]) {
					App.UI.DOM.appendNewElement(
						"span",
						span,
						App.UI.DOM.link(
							`-`,
							() => {
								V.nationalities[setup.baseNationalities[i]] -= 1;
								if (V.nationalities[setup.baseNationalities[i]] <= 0) {
									delete V.nationalities[setup.baseNationalities[i]];
								}
								refresh();
							}
						),
						"minusButton"
					);
				}
				if (V.nationalities[nation] > 1) {
					App.UI.DOM.appendNewElement(
						"span",
						span,
						App.UI.DOM.link(
							`0`,
							() => {
								delete V.nationalities[setup.baseNationalities[i]];
								refresh();
							}
						),
						"zeroButton"
					);
				}
				li.append(span);
				list.append(li);
			}
			p.append(list);
			App.UI.DOM.appendNewElement("div", p, `By dominant race/ethnicity (hover over the name to see the nationalities affected):`);
			list = document.createElement("UL");
			list.classList.add("customize-slave-trade-ul");
			for (const race of setup.filterRaces) {
				const racialNationalities = setup.baseNationalities.filter(function(n) {
					let races = setup.raceSelector[n] || setup.raceSelector[''];
					return races[uncapFirstChar(race)] * 3.5 > hashSum(races);
				});

				if (racialNationalities.length > 0) {
					const li = document.createElement("LI");
					li.classList.add("customize-slave-trade-li");
					li.title = racialNationalities.length > 0 ? racialNationalities.join(", ") : "(none)";
					li.append(race);
					span = document.createElement("span");
					span.classList.add("customize-slave-trade-li-container");
					App.UI.DOM.appendNewElement(
						"span",
						span,
						App.UI.DOM.link(
							`+`,
							() => {
								setup.baseNationalities.filter(function(n) {
									let races = setup.raceSelector[n] || setup.raceSelector[''];
									return races[uncapFirstChar(race)] * 3.5 > hashSum(races);
								}).forEach(function(n) { hashPush(V.nationalities, n); });
								refresh();
							}
						),
						"plusButton"
					);
					App.UI.DOM.appendNewElement(
						"span",
						span,
						App.UI.DOM.link(
							`0`,
							() => {
								setup.baseNationalities.filter(function(n) {
									let races = setup.raceSelector[n] || setup.raceSelector[''];
									return races[uncapFirstChar(race)] * 3.5 > hashSum(races);
								}).forEach(function(n) { delete V.nationalities[n]; });

								refresh();
							}
						),
						"zeroButton"
					);
					li.append(span);
					list.append(li);
				}
			}
		} else {
			/* Filtered pop controls */
			const controlsNationality = setup[baseControlsFilter + 'Nationalities'];
			const keys = Object.keys(controlsNationality);
			for (let i = 0; i < keys.length; i++) {
				const li = document.createElement("LI");
				li.classList.add("customize-slave-trade-li");
				const nation = keys[i];
				li.append(nation);
				span = document.createElement("span");
				span.classList.add("customize-slave-trade-li-container");
				App.UI.DOM.appendNewElement(
					"span",
					span,
					App.UI.DOM.link(
						`+`,
						() => {
							hashPush(V.nationalities, keys[i]);
							refresh();
						}
					),
					"plusButton"
				);

				if (nationalitiesCheck[nation]) {
					App.UI.DOM.appendNewElement(
						"span",
						span,
						App.UI.DOM.link(
							`-`,
							() => {
								V.nationalities[keys[i]] -= 1;
								if (V.nationalities[keys[i]] <= 0) {
									delete V.nationalities[keys[i]];
								}
								refresh();
							}
						),
						"minusButton"
					);
				}
				if (V.nationalities[nation] > 1) {
					App.UI.DOM.appendNewElement(
						"span",
						span,
						App.UI.DOM.link(
							`0`,
							() => {
								delete V.nationalities[keys[i]];
								refresh();
							}
						),
						"zeroButton"
					);
				}
				li.append(span);
				list.append(li);
			}
		}
		p.append(list);
		frag.append(p);

		div = document.createElement("div");

		div.append(
			App.UI.DOM.link(
				"Reset filters",
				() => {
					baseControlsFilter = "all";
					refresh();
				}
			)
		);

		div.append(" | ");

		div.append(
			App.UI.DOM.link(
				"Clear all nationalities",
				() => {
					V.nationalities = {};
					refresh();
				}
			)
		);

		frag.append(div);

		frag.append(sectionBreak());

		div = document.createElement("div");
		div.append(`Vanilla presets: `);
		div.append(generatePresetLinks("Vanilla"));
		frag.append(div);

		div = document.createElement("div");
		div.append(`Mod presets: `);
		div.append(generatePresetLinks("Mod"));
		frag.append(div);

		p = document.createElement("p");
		App.UI.DOM.appendNewElement(
			"span",
			p,
			App.UI.DOM.link(
				"Export Settings",
				() => {
					settingsExport();
				}
			)
		);
		p.append(" | ");
		App.UI.DOM.appendNewElement(
			"span",
			p,
			App.UI.DOM.link(
				"Import Settings",
				() => {
					settingsImport();
				}
			)
		);
		frag.append(p);
		div = document.createElement("div");
		div.id = "importExportArea";
		frag.append(div);

		return frag;
	}

	function sectionBreak() {
		const hr = document.createElement("hr");
		hr.style.margin = "0";
		return hr;
	}

	function refresh() {
		return jQuery('#pop-control').empty().append(baseNationalitiesControls());
	}

	function settingsExport() {
		let textArea = document.createElement("textarea");
		textArea.value = JSON.stringify(V.nationalities);
		$("#importExportArea").html(textArea);
	}

	function settingsImport() {
		let textArea = document.createElement("textarea");
		let button = document.createElement("button");
		button.name = "Load";
		button.innerHTML = "Load";
		button.onclick = () => {
			V.nationalities = JSON.parse(textArea.value);
			State.display(State.passage);
		};

		$("#importExportArea").html("").append(textArea).append(button);
	}

	function generatePresetLinks(group) {
		let links = [];
		for (const [name, nationalities] of App.Data.NationalityPresets[group]) {
			links.push(
				App.UI.DOM.link(
					name,
					() => {
						V.nationalities = nationalities;
						refresh();
					}
				)
			);
		}
		return App.UI.DOM.generateLinksStrip(links);
	}
};

App.UI.nationalitiesDisplay = function() {
	const p = document.createElement("p");

	/* Generates cloned array of V.nationalities, removing duplicates and then sorting */
	const nationalitiesCheck = App.UI.nationalitiesCheck();

	/* Prints distribution of V.nationalities, using nationalitiesCheck to render array */
	let percentPerPoint = 100.0 / hashSum(V.nationalities);
	let len = Object.keys(nationalitiesCheck).length;
	let j = 0;
	for (const nation in nationalitiesCheck) {
		const span = document.createElement("span");
		span.append(`${nation} `);
		App.UI.DOM.appendNewElement("span", span, (V.nationalities[nation] * percentPerPoint).toFixed(2), "orange");
		j++;
		if (j < len) {
			span.append(` | `);
		}
		p.append(span);
	}
	return p;
};

App.UI.nationalitiesCheck = function() {
	return Object.assign(
		{
			// Player can add custom nations here.
		},
		V.nationalities);
};


code:
:: Customize Slave Trade [nobr]

<<if ndef $nationalities>>
	<<set $nationalities = {}>>
<</if>>
<<if ndef $customWA>>
	<<set $customWA = 0>>
<</if>>
<<includeDOM App.UI.CustomSlaveTrade()>>


code:
:: Economy Intro [nobr]

<<if $PC.career == "arcology owner" || $saveImported == 1>>
	<<goto "Takeover Target">>
<<else>>

		It is the year 2037, and the past 21 years have not been kind. The world is starting to fall apart. The climate is deteriorating, resources are being exhausted, and there are more people to feed every year. Technology is advancing, but not fast enough to save everyone. Exactly how bad is the situation?

		<<if $baseDifficulty == 1>>

					Not truly dire. Not yet.

					Very Easy

				[[Harder|Economy Intro][$economy = 125, $baseDifficulty = 2]]

		<<elseif $baseDifficulty == 2>>

					Getting a touch dire.

					Easy

				[[Harder|Economy Intro][$economy = 100, $baseDifficulty = 3]] | [[Easier|Economy Intro][$economy = 200, $baseDifficulty = 1]]

		<<elseif $baseDifficulty == 3>>

					Very serious.

					Default Difficulty

				[[Harder|Economy Intro][$economy = 80, $baseDifficulty = 4]] | [[Easier|Economy Intro][$economy = 125, $baseDifficulty = 2]]

		<<elseif $baseDifficulty == 4>>

					It won't be pretty.

					Hard

				[[Harder|Economy Intro][$economy = 67, $baseDifficulty = 5]] | [[Easier|Economy Intro][$economy = 100, $baseDifficulty = 3]]

		<<else>>

					This is the last dance.

					Very Hard

				[[Easier|Economy Intro][$economy = 80, $baseDifficulty = 4]]

		<</if>>

	/*Economy settings*/

		How fast is it crumbling?

		Some economic content requires this to be set to harder than vanilla

	<<if $difficultySwitch == 0>>

				Barely noticeable, for now.

				Vanilla

			[[Harder|Economy Intro][$econRate = 1, $difficultySwitch = 1]]

	<<elseif $econRate == 1>>

				A slow decline.

				Easy

			[[Harder|Economy Intro][$econRate = 2]] | [[Easier|Economy Intro][$difficultySwitch = 0]]

	<<elseif $econRate == 2>>

				It's visibly deteriorating before your eyes.

				Default Difficulty

			[[Harder|Economy Intro][$econRate = 4]] | [[Easier|Economy Intro][$econRate = 1]]

	<<else>>

				It's going to hell in a handbasket.

				Hard

			[[Easier|Economy Intro][$econRate = 2]]

	<</if>>

	/* Not functional yet, place $incomeRate = 1 in storyInit when ready for use

		All the things you need to run your arcology are getting more expensive
		<<if $incomeMod == 0>>
			while all forms of income
			remain static. [[Easier|Economy Intro][$incomeRate = 1]]
		<<elseif $incomeMod == 1>>
			while all forms of income rise but cannot keep pace. [[Easier|Economy Intro][$incomeRate = 2]] | [[Harder|Economy Intro][$incomeRate = 0]]
		<<else>>
			but luckily all forms of income rise in lockstep. [[Harder|Economy Intro][$incomeRate = 1]]
		<</if>>

		[[Play with static economy|Economy Intro][$difficultySwitch = 0]]

	*/

		[[Next|Trade Intro]]

		[[Skip Intro|Intro Summary]]

			This will preclude you from taking over an established arcology.

<</if>>


code:
:: Extreme Intro [nobr]

	The early Free Cities were wild places where the writ of law did not run. In some of the most depraved, slaves' bodies, minds and even lives were playthings of the wealthy and powerful. Though modern Free Cities are tremendously varied, a majority of the new communities made a choice about whether extreme practices were a flaw in a lawless society or one of its benefits.

		How did most Free Cities react to the excesses of the early days?

	[[They drew back from them.|Gender Intro][$seeExtreme = 0]]

		Extreme content such as amputation and castration will not appear.

	[[They reveled in them.|Gender Intro][$seeExtreme = 1]]

		Extreme content will appear.

	[[They drew back from them, but remained creative.|Gender Intro][$seeExtreme = 0,$seeHyperPreg = 1]]

		Extreme content will not appear, but hyper-pregnancy related content might appear.

	[[They reveled in them and were particularly inventive.|Gender Intro][$seeExtreme = 1,$seeHyperPreg = 1]]

		Extreme content will appear, including hyper-pregnancy related content


code:
:: Gender Intro [nobr]

	The Free Cities are sexually libertine places, and sexual slavery is ubiquitous. Some of the early Free Cities upheld or even strengthened traditional gender roles, expecting men to be men and women to be women. Others subscribed to an interesting refinement of those gender roles, considering any sex slave female, regardless of her biology. A small minority even went so far as to strongly favor societal feminization of slaves born male; in these, biologically female slaves were a rare sight.

		Which kind of Free City came to predominate?

	[[Free Cities that were open-minded about who could be a slave girl.|Slave Age Intro][$seeDicks = 25]]

		Default setting. The majority of slaves will be biologically female, and all content will be available.

	[[Free Cities that understood that girls are girls.|Slave Age Intro][$seeDicks = 0]]

		Almost all slaves will be biologically female, restricting some content.

	[[Free Cities that understood that girls are girls with some exceptions.|Slave Age Intro][$makeDicks = 1, $seeDicks = 0]]

		This option will make almost all generated slaves female, but will allow for the attachment of a certain organ through surgery.

	[[Free Cities that preferred girls with dicks.|Slave Age Intro][$seeDicks = 100]]

		Almost all slaves will be biologically male, restricting some content.


code:
App.Intro.initNationalities = function() {
	function initSecExp() {
		/* base vars */
		App.SecExp.generalInit();
		V.fort = {
			reactor: 0,
			waterway: 0,
			assistant:0
		};
		V.garrison = {
			penthouse: 0,
			reactor: 0,
			assistant: 0,
			waterway: 0,
			reactorTime: 0,
			assistantTime: 0,
			waterwayTime: 0
		};
		if (V.wasToggledBefore === 0) {
			if (V.mercenaries === 1) {
				V.mercFreeManpower = jsRandom(5, 20);
			} else if (V.mercenaries > 1) {
				V.mercFreeManpower = jsRandom(10, 30);
			}
		}
	}

	function applyPCQualities() {
		resetEyeColor(V.PC);
		generatePlayerPronouns(V.PC);

		if (V.PC.career === "wealth") {
			V.trinkets.push("a collection of diplomas from expensive schools");
			cashX(10000, "personalBusiness");
		} else if (V.PC.career === "capitalist") {
			V.trinkets.push("a framed low denomination piece of paper money from your native country");
		} else if (V.PC.career === "mercenary") {
			V.trinkets.push("a battered old assault rifle");
		} else if (V.PC.career === "slaver") {
			V.trinkets.push("a framed picture of a slave with her sale price scrawled across the bottom");
		} else if (V.PC.career === "engineer") {
			V.trinkets.push("an artist's impression of an early arcology design");
			V.arcologyUpgrade.drones = 1;
			V.arcologyUpgrade.hydro = 1;
			if (V.secExpEnabled > 0) {
				V.secBots = App.SecExp.generateUnit("bots");
			}
		} else if (V.PC.career === "medicine") {
			V.trinkets.push("a framed postsurgical x-ray");
			V.surgeryCost = Math.trunc(V.surgeryCost/2);
		} else if (V.PC.career === "celebrity") {
			V.trinkets.push("a framed copy of the first news story featuring yourself");
			repX(4000, "event");
		} else if (V.PC.career === "arcology owner") {
			V.trinkets.push("a miniature model of your first arcology");
			repX(2000, "event");
		} else if (V.PC.career === "escort") {
			V.trinkets.push("a copy of the first porno you starred in");
		} else if (V.PC.career === "servant") {
			V.trinkets.push("a framed picture of your late Master");
		} else if (V.PC.career === "gang") {
			V.trinkets.push("your favorite handgun, whose sight has instilled fear in many");
		} else if (V.PC.career === "BlackHat") {
			V.trinkets.push("a news clipping of your first successful live hack");
		}

		if (V.PC.rumor === "wealth") {
			cashX(10000, "personalBusiness");
		} else if (V.PC.rumor === "social engineering") {
			V.FSAnnounced = 1;
			V.FSGotRepCredits = 1;
		} else if (V.PC.rumor === "luck") {
			repX(4000, "event");
		}

		if (V.PC.visualAge >= 50) {
			repX(2000, "event");
		} else if (V.PC.visualAge < 35) {
			if (V.rep >= 2000) {
				repX(-2000, "event");
			} else {
				/* set rep to zero */
				repX(forceNeg(Math.abs(V.rep)), "event");
			}
		}
	}

	function initArcologies() {
		const seed = ["east", "north", "northeast", "northwest", "south", "southeast", "southwest", "west"];
		const govtypes = ["a committee", "a corporation", "an individual", "an oligarchy", "direct democracy", "elected officials"];

		V.neighboringArcologies = variableAsNumber(V.neighboringArcologies, 0, 8, 3);
		for (let i = 0; i <= V.neighboringArcologies; i++) {
			/** @type {FC.ArcologyState} */
			const newArcology = {
				name: "",
				direction: "north",
				government: "an individual",
				leaderID: 0,
				honeymoon: 0,
				prosperity: 50,
				ownership: 50,
				minority: 20,
				PCminority: 0,
				demandFactor: 0,
				FSSupremacist: "unset",
				FSSupremacistRace: 0,
				FSSubjugationist: "unset",
				FSSubjugationistRace: 0,
				FSGenderRadicalist: "unset",
				FSGenderFundamentalist: "unset",
				FSPaternalist: "unset",
				FSDegradationist: "unset",
				FSBodyPurist: "unset",
				FSTransformationFetishist: "unset",
				FSYouthPreferentialist: "unset",
				FSMaturityPreferentialist: "unset",
				FSSlimnessEnthusiast: "unset",
				FSAssetExpansionist: "unset",
				FSPastoralist: "unset",
				FSPhysicalIdealist: "unset",
				FSChattelReligionist: "unset",
				FSRomanRevivalist: "unset",
				FSAztecRevivalist: "unset",
				FSEgyptianRevivalist: "unset",
				FSEdoRevivalist: "unset",
				FSNeoImperialist: "unset",
				FSArabianRevivalist: "unset",
				FSChineseRevivalist: "unset",
				FSNull: "unset",
				embargo: 1,
				embargoTarget: -1,
				influenceTarget: -1,
				influenceBonus: 0,
				CyberEconomic: 1,
				CyberEconomicTarget: -1,
				CyberReputation: 1,
				CyberReputationTarget: -1,
				rival: 0,
				FSRestart: "unset",
				FSRepopulationFocus: "unset",
				FSHedonisticDecadence: "unset",
				FSIntellectualDependency: "unset",
				FSSlaveProfessionalism: "unset",
				FSPetiteAdmiration: "unset",
				FSStatuesqueGlorification: "unset",
				FSCummunism: "unset",
				FSIncestFetishist: "unset",
				FSGenderRadicalistResearch: 0,
				FSGenderFundamentalistResearch: 0,
				FSPaternalistResearch: 0,
				FSDegradationistResearch: 0,
				FSBodyPuristResearch: 0,
				FSTransformationFetishistResearch: 0,
				FSYouthPreferentialistResearch: 0,
				FSMaturityPreferentialistResearch: 0,
				FSSlimnessEnthusiastResearch: 0,
				FSAssetExpansionistResearch: 0,
				FSPastoralistResearch: 0,
				FSPhysicalIdealistResearch: 0,
				FSRepopulationFocusResearch: 0,
				FSRestartResearch: 0,
				FSHedonisticDecadenceResearch: 0,
				FSHedonisticDecadenceDietResearch: 0,
				FSIntellectualDependencyResearch: 0,
				FSSlaveProfessionalismResearch: 0,
				FSPetiteAdmirationResearch: 0,
				FSStatuesqueGlorificationResearch: 0,
				FSCummunismResearch: 0,
				FSIncestFetishistResearch: 0
			};
			if (i === 0) {
				newArcology.direction = 0;
				newArcology.name = "Arcology X-4";
				newArcology.FSSupremacistDecoration = 20;
				newArcology.FSSubjugationistDecoration = 20;
				newArcology.FSGenderRadicalistDecoration = 20;
				newArcology.FSGenderFundamentalistDecoration = 20;
				newArcology.FSPaternalistDecoration = 20;
				newArcology.FSDegradationistDecoration = 20;
				newArcology.FSBodyPuristDecoration = 20;
				newArcology.FSTransformationFetishistDecoration = 20;
				newArcology.FSYouthPreferentialistDecoration = 20;
				newArcology.FSMaturityPreferentialistDecoration = 20;
				newArcology.FSSlimnessEnthusiastDecoration = 20;
				newArcology.FSAssetExpansionistDecoration = 20;
				newArcology.FSPastoralistDecoration = 20;
				newArcology.FSPhysicalIdealistDecoration = 20;
				newArcology.FSChattelReligionistDecoration = 20;
				newArcology.FSRomanRevivalistDecoration = 20;
				newArcology.FSNeoImperialistDecoration = 20;
				newArcology.FSAztecRevivalistDecoration = 20;
				newArcology.FSEgyptianRevivalistDecoration = 20;
				newArcology.FSEdoRevivalistDecoration = 20;
				newArcology.FSArabianRevivalistDecoration = 20;
				newArcology.FSChineseRevivalistDecoration = 20;
				newArcology.FSRepopulationFocusDecoration = 20;
				newArcology.FSRestartDecoration = 20;
				newArcology.FSHedonisticDecadenceDecoration = 20;
				newArcology.FSIntellectualDependencyDecoration = 20;
				newArcology.FSSlaveProfessionalismDecoration = 20;
				newArcology.FSPetiteAdmirationDecoration = 20;
				newArcology.FSStatuesqueGlorificationDecoration = 20;
				newArcology.FSCummunismDecoration = 20;
				newArcology.FSIncestFetishistDecoration = 20;
				if (V.targetArcology.fs !== "New") {
					V.FSAnnounced = 1;
					V.FSGotRepCredits = 1;
					newArcology.name = V.targetArcology.name;
					newArcology.prosperity = V.targetArcology.prosperity;
					V.ACitizens += V.targetArcology.citizens*500;
					const decoration = V.targetArcology.FSProgress + 10;
					switch (V.targetArcology.fs) {
						case "Supremacist":
							newArcology.FSSupremacist = V.targetArcology.FSProgress;
							newArcology.FSSupremacistDecoration = decoration;
							newArcology.FSSupremacistRace = V.targetArcology.race;
							break;
						case "Subjugationist":
							newArcology.FSSubjugationist = V.targetArcology.FSProgress;
							newArcology.FSSubjugationistDecoration = decoration;
							newArcology.FSSubjugationistRace = V.targetArcology.race;
							break;
						case "GenderRadicalist":
							newArcology.FSGenderRadicalist = V.targetArcology.FSProgress;
							newArcology.FSGenderRadicalistDecoration = decoration;
							break;
						case "GenderFundamentalist":
							newArcology.FSGenderFundamentalist = V.targetArcology.FSProgress;
							newArcology.FSGenderFundamentalistDecoration = decoration;
							break;
						case "Paternalist":
							newArcology.FSPaternalist = V.targetArcology.FSProgress;
							newArcology.FSPaternalistDecoration = decoration;
							break;
						case "Degradationist":
							newArcology.FSDegradationist = V.targetArcology.FSProgress;
							newArcology.FSDegradationistDecoration = decoration;
							break;
						case "AssetExpansionist":
							newArcology.FSAssetExpansionist = V.targetArcology.FSProgress;
							newArcology.FSAssetExpansionistDecoration = decoration;
							break;
						case "SlimnessEnthusiast":
							newArcology.FSSlimnessEnthusiast = V.targetArcology.FSProgress;
							newArcology.FSSlimnessEnthusiastDecoration = decoration;
							break;
						case "TransformationFetishist":
							newArcology.FSTransformationFetishist = V.targetArcology.FSProgress;
							newArcology.FSTransformationFetishistDecoration = decoration;
							break;
						case "BodyPurist":
							newArcology.FSBodyPurist = V.targetArcology.FSProgress;
							newArcology.FSBodyPuristDecoration = decoration;
							break;
						case "MaturityPreferentialist":
							newArcology.FSMaturityPreferentialist = V.targetArcology.FSProgress;
							newArcology.FSMaturityPreferentialistDecoration = decoration;
							break;
						case "YouthPreferentialist":
							newArcology.FSYouthPreferentialist = V.targetArcology.FSProgress;
							newArcology.FSYouthPreferentialistDecoration = decoration;
							break;
						case "Pastoralist":
							newArcology.FSPastoralist = V.targetArcology.FSProgress;
							newArcology.FSPastoralistDecoration = decoration;
							break;
						case "PhysicalIdealist":
							newArcology.FSPhysicalIdealist = V.targetArcology.FSProgress;
							newArcology.FSPhysicalIdealistDecoration = decoration;
							break;
						case "ChattelReligionist":
							newArcology.FSChattelReligionist = V.targetArcology.FSProgress;
							newArcology.FSChattelReligionistDecoration = decoration;
							break;
						case "RomanRevivalist":
							newArcology.FSRomanRevivalist = V.targetArcology.FSProgress;
							newArcology.FSRomanRevivalistDecoration = decoration;
							break;
						case "NeoImperialist":
							newArcology.FSNeoImperialist = V.targetArcology.FSProgress;
							newArcology.FSNeoImperialistDecoration = decoration;
							break;
						case "AztecRevivalist":
							newArcology.FSAztecRevivalist = V.targetArcology.FSProgress;
							newArcology.FSAztecRevivalistDecoration = decoration;
							break;
						case "EgyptianRevivalist":
							newArcology.FSEgyptianRevivalist = V.targetArcology.FSProgress;
							newArcology.FSEgyptianRevivalistDecoration = decoration;
							break;
						case "EdoRevivalist":
							newArcology.FSEdoRevivalist = V.targetArcology.FSProgress;
							newArcology.FSEdoRevivalistDecoration = decoration;
							break;
						case "ArabianRevivalist":
							newArcology.FSArabianRevivalist = V.targetArcology.FSProgress;
							newArcology.FSArabianRevivalistDecoration = decoration;
							break;
						case "ChineseRevivalist":
							newArcology.FSChineseRevivalist = V.targetArcology.FSProgress;
							newArcology.FSChineseRevivalistDecoration = decoration;
							break;
						case "Eugenics":
							newArcology.FSRestart = V.targetArcology.FSProgress;
							newArcology.FSRestartDecoration = decoration;
							break;
						case "Repopulationist":
							newArcology.FSRepopulationFocus = V.targetArcology.FSProgress;
							newArcology.FSRepopulationFocusDecoration = decoration;
							break;
						case "HedonisticDecadence":
							newArcology.FSHedonisticDecadence = V.targetArcology.FSProgress;
							newArcology.FSHedonisticDecadenceDecoration = decoration;
							break;
						case "IntellectualDependency":
							newArcology.FSIntellectualDependency = V.targetArcology.FSProgress;
							newArcology.FSIntellectualDependencyDecoration = decoration;
							break;
						case "SlaveProfessionalism":
							newArcology.FSSlaveProfessionalism = V.targetArcology.FSProgress;
							newArcology.FSSlaveProfessionalismDecoration = decoration;
							break;
						case "PetiteAdmiration":
							newArcology.FSPetiteAdmiration = V.targetArcology.FSProgress;
							newArcology.FSPetiteAdmirationDecoration = decoration;
							break;
						case "StatuesqueGlorification":
							newArcology.FSStatuesqueGlorification = V.targetArcology.FSProgress;
							newArcology.FSStatuesqueGlorificationDecoration = decoration;
							break;
						default:
							newArcology.FSNull = 20;
					}
					if (V.PC.rumor === "social engineering") {
						V.FSGotRepCredits += 1;
					}
				} else {
					newArcology.honeymoon = 20;
				}
			} else {
				if (i < 4) {
					/* X-4 is reserved for player's arcology, so X-1 is available */
					newArcology.name = `Arcology X-${i}`;
				} else {
					newArcology.name = `Arcology X-${i+1}`;
				}
				newArcology.direction = seed.pluck();
				newArcology.government = govtypes.random();
				newArcology.prosperity += jsRandom(-20, 20);
				newArcology.ownership += jsRandom(-10, 0);
				newArcology.minority += jsRandom(-5, 5);
			}
			V.arcologies.push(newArcology);
		}

		V.arcologies[0].FSSupremacistLawME = 0;
		V.arcologies[0].FSSupremacistSMR = 0;
		V.arcologies[0].FSSubjugationistLawME = 0;
		V.arcologies[0].FSSubjugationistSMR = 0;
		V.arcologies[0].FSGenderRadicalistLawFuta = 0;
		V.arcologies[0].FSGenderRadicalistLawBeauty = 0;
		V.arcologies[0].FSGenderFundamentalistLawBimbo = 0;
		V.arcologies[0].FSGenderFundamentalistSMR = 0;
		V.arcologies[0].FSGenderFundamentalistLawBeauty = 0;
		V.arcologies[0].FSPaternalistLaw = 0;
		V.arcologies[0].FSPaternalistSMR = 0;
		V.arcologies[0].FSDegradationistLaw = 0;
		V.arcologies[0].FSDegradationistSMR = 0;
		V.arcologies[0].FSBodyPuristLaw = 0;
		V.arcologies[0].FSBodyPuristSMR = 0;
		V.arcologies[0].FSTransformationFetishistSMR = 0;
		V.arcologies[0].FSYouthPreferentialistLaw = 0;
		V.arcologies[0].FSYouthPreferentialistSMR = 0;
		V.arcologies[0].FSMaturityPreferentialistLaw = 0;
		V.arcologies[0].FSMaturityPreferentialistSMR = 0;
		V.arcologies[0].FSSlimnessEnthusiastSMR = 0;
		V.arcologies[0].FSSlimnessEnthusiastLaw = 0;
		V.arcologies[0].FSAssetExpansionistSMR = 0;
		V.arcologies[0].FSPastoralistLaw = 0;
		V.arcologies[0].FSPastoralistSMR = 0;
		V.arcologies[0].FSPhysicalIdealistSMR = 0;
		V.arcologies[0].FSPhysicalIdealistLaw = 0;
		V.arcologies[0].FSPhysicalIdealistStrongFat = 0;
		V.arcologies[0].FSChattelReligionistLaw = 0;
		V.arcologies[0].FSChattelReligionistSMR = 0;
		V.arcologies[0].FSChattelReligionistCreed = 0;
		V.arcologies[0].FSRomanRevivalistLaw = 0;
		V.arcologies[0].FSRomanRevivalistSMR = 0;
		V.arcologies[0].FSNeoImperialistSMR = 0;
		V.arcologies[0].FSNeoImperialistLaw1 = 0;
		V.arcologies[0].FSNeoImperialistLaw2 = 0;
		V.arcologies[0].FSAztecRevivalistLaw = 0;
		V.arcologies[0].FSAztecRevivalistSMR = 0;
		V.arcologies[0].FSEgyptianRevivalistLaw = 0;
		V.arcologies[0].FSEgyptianRevivalistSMR = 0;
		V.arcologies[0].FSEdoRevivalistLaw = 0;
		V.arcologies[0].FSEdoRevivalistSMR = 0;
		V.arcologies[0].FSArabianRevivalistLaw = 0;
		V.arcologies[0].FSArabianRevivalistSMR = 0;
		V.arcologies[0].FSChineseRevivalistLaw = 0;
		V.arcologies[0].FSChineseRevivalistSMR = 0;
		V.arcologies[0].FSRepopulationFocusLaw = 0;
		V.arcologies[0].FSRepopulationFocusSMR = 0;
		V.arcologies[0].FSRestartLaw = 0;
		V.arcologies[0].FSRestartSMR = 0;
		V.arcologies[0].FSHedonisticDecadenceLaw = 0;
		V.arcologies[0].FSHedonisticDecadenceLaw2 = 0;
		V.arcologies[0].FSHedonisticDecadenceStrongFat = 0;
		V.arcologies[0].FSHedonisticDecadenceSMR = 0;
		V.arcologies[0].FSIntellectualDependencyLaw = 0;
		V.arcologies[0].FSIntellectualDependencyLawBeauty = 0;
		V.arcologies[0].FSIntellectualDependencySMR = 0;
		V.arcologies[0].FSSlaveProfessionalismLaw = 0;
		V.arcologies[0].FSSlaveProfessionalismSMR = 0;
		V.arcologies[0].FSPetiteAdmirationLaw = 0;
		V.arcologies[0].FSPetiteAdmirationLaw2 = 0;
		V.arcologies[0].FSPetiteAdmirationSMR = 0;
		V.arcologies[0].FSStatuesqueGlorificationLaw = 0;
		V.arcologies[0].FSStatuesqueGlorificationLaw2 = 0;
		V.arcologies[0].FSStatuesqueGlorificationSMR = 0;

		V.arcologies[0].FSGenderRadicalistResearch = 0;
		V.arcologies[0].FSGenderFundamentalistResearch = 0;
		V.arcologies[0].FSPaternalistResearch = 0;
		V.arcologies[0].FSDegradationistResearch = 0;
		V.arcologies[0].FSBodyPuristResearch = 0;
		V.arcologies[0].FSTransformationFetishistResearch = 0;
		V.arcologies[0].FSYouthPreferentialistResearch = 0;
		V.arcologies[0].FSMaturityPreferentialistResearch = 0;
		V.arcologies[0].FSSlimnessEnthusiastResearch = 0;
		V.arcologies[0].FSAssetExpansionistResearch = 0;
		V.arcologies[0].FSPastoralistResearch = 0;
		V.arcologies[0].FSPhysicalIdealistResearch = 0;
		V.arcologies[0].FSRepopulationFocusResearch = 0;
		V.arcologies[0].FSRestartResearch = 0;
		V.arcologies[0].FSHedonisticDecadenceResearch = 0;
		V.arcologies[0].FSHedonisticDecadenceDietResearch = 0;
		V.arcologies[0].FSCummunismResearch = 0;
		V.arcologies[0].FSIncestFetishistResearch = 0;
		V.arcologies[0].FSIntellectualDependencyResearch = 0;
		V.arcologies[0].FSSlaveProfessionalismResearch = 0;
		V.arcologies[0].FSPetiteAdmirationResearch = 0;
		V.arcologies[0].FSStatuesqueGlorificationResearch = 0;

		V.arcologies[0].FSEgyptianRevivalistIncestPolicy = 0;
		V.arcologies[0].FSEgyptianRevivalistInterest = 0;
		V.arcologies[0].FSRepopulationFocusPregPolicy = 0;
		V.arcologies[0].FSRepopulationFocusMilfPolicy = 0;
		V.arcologies[0].FSRepopulationFocusInterest = 0;
		V.arcologies[0].FSEugenicsChastityPolicy = 0;
		V.arcologies[0].FSEugenicsSterilizationPolicy = 0;
		V.arcologies[0].FSEugenicsInterest = 0;

		V.arcologies[0].childhoodFertilityInducedNCSResearch = 0;
	}

	if (V.targetArcology.fs !== "New") {
		V.building = V.targetArcology.building;
		delete V.targetArcology.building;
	} else {
		V.building = App.Arcology.defaultBuilding(V.terrain);
	}
	const sellable = V.building.findCells(cell => cell.canBeSold());
	const random12 = jsRandomMany(sellable, 12);
	random12.forEach(cell => { cell.owner = 0; });

	if (V.secExpEnabled > 0) {
		initSecExp();
	}

	if (V.experimental.food === 1) {
		if (V.localEcon > 100) {
			V.farmyardFoodCost = Math.max(5 / (1 + (Math.trunc(1000-100000/V.localEcon)/10)/100), 3.125);
		} else if (V.localEcon === 100) {
			V.farmyardFoodCost = 5;
		} else {
			V.farmyardFoodCost = Math.min(5 * (1 + 1.5 * Math.sqrt(Math.trunc(100000/V.localEcon-1000)/10)/100), 6.5);
		}
	}

	V.minimumSlaveAge = variableAsNumber(V.minimumSlaveAge, 3, 18, 18);
	V.retirementAge = variableAsNumber(V.retirementAge, 25, 120, 45);
	V.fertilityAge = variableAsNumber(V.fertilityAge, 3, 18, 13);
	V.potencyAge = variableAsNumber(V.potencyAge, 3, 18, 13);

	applyPCQualities();

	/* SET STARTING CONDITIONS */

	V.enduringRep = V.rep;

	initArcologies();

	V.HackingSkillMultiplier = upgradeMultiplier('hacking');
	V.upgradeMultiplierArcology = upgradeMultiplier('engineering');
	V.upgradeMultiplierMedicine = upgradeMultiplier('medicine');
	V.upgradeMultiplierTrade = upgradeMultiplier('trading');

	/* Nationalities Setup */

	if (!V.customVariety) {
		/* If non-custom variety, empties or defines $nationalities */
		V.nationalities = {};
	}
	const needLocalNationalities = !V.customVariety && !V.internationalTrade;
	if (V.terrain === "oceanic") {
		if (V.targetArcology.fs !== "Supremacist") {
			V.arcologies[0].FSSupremacistRace = "white";
		}
		if (V.targetArcology.fs !== "Subjugationist") {
			V.arcologies[0].FSSubjugationistRace = "middle eastern";
		}
		if (needLocalNationalities) {
			// FIXME: equal distributions? probably should use weighted variety instead
			V.nationalities = arr2obj(setup.baseNationalities);
		}
	} else {
		const continentalDefaults = new Map([
			["North America",	{supr: "white", subj: "black", preset: "Vanilla North America"}],
			["South America",	{supr: "latina", subj: "black", preset: "Vanilla South America"}],
			["Brazil",			{supr: "white", subj: "black", preset: "Vanilla Brazil"}],
			["the Middle East",	{supr: "middle eastern", subj: "asian", preset: "Vanilla Middle East"}],
			["Africa",			{supr: "black", subj: "white", preset: "Vanilla Africa"}],
			["Asia",			{supr: "asian", subj: "indo-aryan", preset: "Vanilla Asia"}],
			["Western Europe",	{supr: "white", subj: "middle eastern", preset: "Vanilla Europe"}],
			["Central Europe",	{supr: "white", subj: "middle eastern", preset: "Vanilla Europe"}],
			["Eastern Europe",	{supr: "white", subj: "asian", preset: "Vanilla Europe"}],
			["Scandinavia",		{supr: "white", subj: "southern european", preset: "Vanilla Europe"}],
			["Southern Europe",	{supr: "southern european", subj: "middle eastern", preset: "Vanilla Europe"}],
			["Australia",		{supr: "white", subj: "asian", preset: "Vanilla Australia"}],
			["Japan",			{supr: "asian", subj: "asian", preset: null}]
		]);

		const defaults = continentalDefaults.get(V.continent);
		if (!defaults) {
			throw `Missing defaults for continent: ${V.continent}`;
		}
		if (V.targetArcology.fs !== "Supremacist") {
			V.arcologies[0].FSSupremacistRace = defaults.supr;
		}
		if (V.targetArcology.fs !== "Subjugationist") {
			V.arcologies[0].FSSubjugationistRace = defaults.subj;
		}
		if (needLocalNationalities) {
			if (V.continent === "Japan") { // special case, no preset for Japan
				hashPush(V.nationalities, "Japanese", "Japanese", "Japanese");
			} else {
				hashMerge(V.nationalities, App.Data.NationalityPresets.Vanilla.get(defaults.preset));
			}
		}
	}

	if (!V.customVariety && V.internationalTrade) {
		if (V.internationalVariety === 0) { // weighted
			hashMerge(V.nationalities, App.Data.NationalityPresets.Vanilla.get("Vanilla Global"));
		} else { // normalized
			V.nationalities = arr2obj(setup.baseNationalities);
		}
	}
};