code:
App.UI.surrogacy = function() {
	const node = new DocumentFragment();
	const receptrix = getReceptrix();
	const donatrix = getDonatrix();
	const impregnatrix = getImpregnatrix();
	const wombIndex = V.wombIndex;
	const slave = getSlave(V.AS);
	let r = [];

	function getReceptrix() {
		if (V.receptrix && V.receptrix.ID) {
			return V.receptrix.ID === -1 ? V.PC : getSlave(V.receptrix.ID);
		}
	}

	function getDonatrix() {
		if (V.donatrix && V.donatrix.ID) {
			return V.donatrix.ID === -1 ? V.PC : getSlave(V.donatrix.ID);
		}
	}

	function getImpregnatrix() {
		if (V.impregnatrix && V.impregnatrix.ID) {
			if (V.impregnatrix.ID === -1) {
				return V.PC;
			} else {
				return findFather(V.impregnatrix.ID);
			}
		}
	}

	switch (V.surgeryType) {
		case "surrogacy":
			if (receptrix.ID === -1) {
				r.push(`Since the surgery required only a local anesthetic, you remain fully aware of the procedure as the autosurgery carries it out. You slowly rise to your feet, a hand to your lower belly, appreciating the new life growing within you.`);
				V.PC.pregKnown = 1;
				WombSurrogate(V.PC, 1, donatrix, impregnatrix.ID, 1);
				WombNormalizePreg(V.PC);
			} else {
				receptrix.pregKnown = 1;
				WombSurrogate(receptrix, 1, donatrix, impregnatrix.ID, 1);
				WombNormalizePreg(receptrix);
				const {
					He,
					he, his, him
				} = getPronouns(receptrix);
				if (receptrix.fetish === Fetish.MINDBROKEN) {
					r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
				} else if (receptrix.fetish === "pregnancy" && receptrix.fetishStrength > 60 && receptrix.fetishKnown === 1) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. ${He} was overjoyed at the sight of the syringe containing ${his} future child emptying into ${his} womb.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He} is  filled with joy over the life settling into ${his} womb and can't wait to see the result. ${He}'s so pleased that ${he} now trusts your plans for ${his} body.`);
					receptrix.trust += 4;
					receptrix.devotion += 10;
				} else if (receptrix.devotion > 50) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. ${He} watched the syringe containing ${his} new pregnancy empty into ${his} womb with rapt attention.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He}'s <span class="devotion inc">grateful</span> that you think ${him} worthy of carrying this child, and a little nervous about how ${he}'ll perform as a surrogate.`);
					receptrix.devotion += 4;
				} else if (receptrix.devotion >= -20) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. From the syringe making contact with ${his} skin, to the egg's delivery into ${his} womb and ${his} subsequent impregnation, ${he} couldn't look away.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He} understands the realities of ${his} life as a slave, so it isn't much of a shock. ${He} is <span class="trust dec">sensibly fearful</span> of your total power over ${his} body.`);
					receptrix.trust -= 10;
				} else {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. The moment ${he} realized what was happening, ${he} shut ${his} eyes tight, only opening them again as ${he} feels the slight tingle of the injector exiting ${his} lower abdomen.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He} does not understand the realities of ${his} life as a slave at a core level, so ${he}'s terrified and angry that you have forced ${him} to bear this child, even more so as ${he} realizes ${he} doesn't know who the father is. ${He} is sensibly fearful of your total power over ${his} body and the future of the life ${he} now harbors within ${him}.`);
					receptrix.trust -= 15;
					receptrix.devotion -= 15;
				}
			}
			V.receptrix = 0;
			V.impregnatrix = 0;
			V.donatrix = 0;
			break;
		case "transplant":
		case "transplant all": {
			const bulk = V.surgeryType === "transplant all";
			const child = bulk ? "children" : "child";
			let fetus;
			if (receptrix.ID === -1) {
				r.push(`Since the surgery required only a local anesthetic, you are very aware that you are now carrying ${slave.slaveName}'s ${child}. You slowly`);
				if (canWalk(V.PC)) {
					r.push(`rise to your feet, a hand to`);
				} else if (canMove(V.PC)) {
					r.push(`rise to a sitting position, a hand to`);
				} else if (isMovable(V.PC)) {
					r.push(`run a hand across`);
				}
				r.push(`your lower belly, appreciating the new ${bulk ? "lives" : "life"} growing within you.`);
				if (bulk) {
					for (let i = 0; i < slave.womb.length; i++) {
						if (slave.womb[i].age < 4 && (!FutureSocieties.isActive('FSRestart') || V.eugenicsFullControl === 1 || mother.breedingMark === 0 || V.propOutcome === 0 || (slave.womb[i].fatherID !== -1 && slave.womb[i].fatherID !== -6))) {
							fetus = WombRemoveFetus(slave, i);
							WombAddFetus(V.PC, fetus);
							i--;
						}
					}
				} else {
					fetus = WombRemoveFetus(slave, wombIndex);
					WombAddFetus(V.PC, fetus);
				}
				V.PC.pregKnown = 1;
				V.PC.preg = WombMaxPreg(V.PC);
				slave.preg = WombMaxPreg(slave);
				WombNormalizePreg(V.PC);
				WombNormalizePreg(slave);
			} else {
				if (bulk) {
					for (let i = 0; i < donatrix.womb.length; i++) {
						if (donatrix.womb[i].age < 4 && (!FutureSocieties.isActive('FSRestart') || V.eugenicsFullControl === 1 || mother.breedingMark === 0 || V.propOutcome === 0 || (donatrix.womb[i].fatherID !== -1 && donatrix.womb[i].fatherID !== -6))) {
							fetus = WombRemoveFetus(donatrix, i);
							WombAddFetus(receptrix, fetus);
							i--;
						}
					}
				} else {
					fetus = WombRemoveFetus(donatrix, wombIndex);
					WombAddFetus(receptrix, fetus);
				}
				receptrix.pregKnown = 1;
				receptrix.preg = WombMaxPreg(receptrix);
				donatrix.preg = WombMaxPreg(donatrix);
				WombNormalizePreg(receptrix);
				WombNormalizePreg(donatrix);
				if (donatrix.ID === -1) {
					V.PC = donatrix;
				}
				const {
					He,
					he, his, him
				} = getPronouns(receptrix);
				if (receptrix.fetish === Fetish.MINDBROKEN) {
					r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
				} else if ((receptrix.fetish === "pregnancy") && (receptrix.fetishStrength > 60) && (receptrix.fetishKnown === 1)) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. ${He} was overjoyed at the sight of the syringe containing ${his} future ${child} emptying into ${his} womb.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He} is  filled with joy over the life resettling into ${his} womb and can't wait to see the result. ${He}'s so pleased that ${he} now trusts your plans for ${his} body.`);
					receptrix.trust += 4;
					receptrix.devotion += 10;
				} else if (receptrix.devotion > 50) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. ${He} watched the syringe containing ${his} new pregnancy empty into ${his} womb with rapt attention.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He}'s <span class="devotion inc">grateful</span> that you think ${him} worthy of carrying ${bulk ? "these children" : "this child"} in`);
					if (donatrix.ID === -1) {
						r.push(`your stead,`);
					} else {
						r.push(`lieu of ${donatrix.slaveName},`);
					}
					r.push(`and a little nervous about how ${he}'ll perform as a surrogate.`);
					receptrix.devotion += 4;
				} else if (receptrix.devotion >= -20) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. From the syringe making contact with ${his} skin, to the egg's delivery into ${his} womb and ${his} subsequent impregnation, ${he} couldn't look away.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He} understands the realities of ${his} life as a slave, so it isn't much of a shock. ${He} is sensibly fearful of your total power over ${his} body and how you are forcing`);
					if (donatrix.ID === -1) {
						r.push(`your pregnancy upon ${him}.`);
					} else {
						r.push(`${him} to carry what should have been ${donatrix.slaveName}'s burden.`);
					}
					receptrix.trust -= 10;
				} else {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. The moment ${he} realized what was happening, ${he} shut ${his} eyes tight, only opening them again as ${he} feels the slight tingle of the injector exiting ${his} lower abdomen.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He} does not understand the realities of ${his} life as a slave at a core level, so ${he}'s <span class="devotion dec">terrified and angry</span> that you have forced ${him} to`);
					if (donatrix.ID === -1) {
						r.push(`carry your ${child} instead of doing so yourself.`);
					} else {
						r.push(`bear ${bulk ? "these children" : "this child"} in lieu of ${donatrix.slaveName}.`);
					}
					r.push(`${He} is <span class="trust dec">sensibly fearful</span> of your total power over ${his} body and who else's troubles may be levied upon ${him}.`);
					receptrix.trust -= 15;
					receptrix.devotion -= 15;
				}
			}
			V.receptrix = 0;
			V.donatrix = 0;
			V.wombIndex = 0;
			break;
		}
		case "clone":
			if (receptrix.ID === -1) {
				r.push(`Since the surgery required only a local anesthetic, you remain fully aware of the procedure as the autosurgery carries it out. You slowly rise to your feet, a hand to your lower belly, appreciating the clone growing within you.`);
				V.PC.pregKnown = 1;
				WombImpregnateClone(V.PC, 1, donatrix, 1);
				WombNormalizePreg(V.PC);
			} else {
				receptrix.pregKnown = 1;
				WombImpregnateClone(receptrix, 1, donatrix, 1);
				WombNormalizePreg(receptrix);
				const {
					He,
					he, his, him
				} = getPronouns(receptrix);
				if (receptrix.fetish === Fetish.MINDBROKEN) {
					r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen,`);
					if (receptrix.ID === donatrix.ID) {
						r.push(`but has no idea ${he} carries ${his} own clone.`);
					} else {
						r.push(`${he} knows that ${he} has been impregnated.`);
					}
				} else if (receptrix.fetish === "pregnancy" && receptrix.fetishStrength > 60 && receptrix.fetishKnown === 1) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. ${He} was overjoyed at the sight of the syringe containing ${his}`);
						if (receptrix.ID === donatrix.ID) {
							r.push(`clone`);
						} else {
							r.push(`future child`);
						}
						r.push(`emptying into ${his} womb.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated${(receptrix.ID === donatrix.ID) ? `, but not that ${he} now bears ${his} own clone` : ''}.`);
					}
					r.push(`${He} is  filled with joy over the life settling into ${his} womb and can't wait to see the result. ${He}'s so pleased that ${he} now trusts your plans for ${his} body.`);
					receptrix.trust += 4;
					receptrix.devotion += 10;
				} else if (receptrix.devotion > 50) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. ${He} watched the syringe containing ${his} new pregnancy empty into ${his} womb with rapt attention.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated${(receptrix.ID === donatrix.ID) ? `, but not that ${he} now bears ${his} own clone` : ''}.`);
					}
					r.push(`${He}'s <span class="devotion inc">grateful</span> that you think ${him} worthy of carrying this child, and a little nervous about how ${he}'ll perform as a surrogate.`);
					receptrix.devotion += 4;
				} else if (receptrix.devotion >= -20) {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. From the syringe making contact with ${his} skin, to the egg's delivery into ${his} womb and ${his} subsequent impregnation, ${he} couldn't look away.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated${(receptrix.ID === donatrix.ID) ? `, but not that ${he} now bears ${his} own clone` : ''}.`);
					}
					r.push(`${He} understands the realities of ${his} life as a slave, so it isn't much of a shock. ${He} is <span class="trust dec">sensibly fearful</span> of your total power over ${his} body.`);
					receptrix.trust -= 10;
				} else {
					if (canSee(receptrix)) {
						r.push(`Since the surgery required only a local anesthetic, ${receptrix.slaveName} remained fully aware throughout the procedure. The moment ${he} realized what was happening, ${he} shut ${his} eyes tight, only opening them again as ${he} feels the slight tingle of the injector exiting ${his} lower abdomen.`);
					} else {
						r.push(`${receptrix.slaveName} leaves the surgery with a certain warmth in ${his} lower abdomen, ${he} knows that ${he} has been impregnated.`);
					}
					r.push(`${He} does not understand the realities of ${his} life as a slave at a core level, so ${he}'s <span class="devotion dec">terrified and angry</span> that you have forced ${him} to bear`);
					if (receptrix.ID === donatrix.ID) {
						r.push(`${his} own clone and potential replacement.`);
					} else {
						r.push(`this child, even more so as ${he} realizes ${he} doesn't know who the father is.`);
					}
					r.push(`${He} is <span class="trust dec">sensibly fearful</span> of your total power over ${his} body and the future of the life ${he} now harbors within ${him}.`);
					receptrix.trust -= 15;
					receptrix.devotion -= 15;
				}
			}
			V.receptrix = 0;
			V.donatrix = 0;
	}
	App.Events.addParagraph(node, r);
	return node;
};


code:
/* subscribe to game save/load events */
Save.onLoad.add(App.EventHandlers.onLoad);
Save.onSave.add(App.EventHandlers.onSave);

/* ### Every-time listeners #### */

$(document).on(":storyready", () => {
	App.EventHandlers.storyReady();
});

$(document).on(":passageinit", () => {
	App.Utils.PassageSwitchHandler.execute();
	App.UI.Tabs.clear();
	App.Reminders.clear();
	App.UI.StoryCaption.encyclopedia = "";
	profileEvents.passageinit();
});

$(document).on(":passagestart", event => {
	App.Debug.slavesConsistency(event);
	Object.defineProperty(State.temporary, "S", {
		get: () => S,
		enumerable: true
	});
	profileEvents.passagestart();
});

$(document).on(":passagerender", () => {
	profileEvents.passagerender();
});

$(document).on(":passagedisplay", () => {
	profileEvents.passagedisplay();
});

$(document).on(":passageend", ev => {
	profileEvents.passageend(ev.content);
});

/* ### One-time listeners ### */

$(document).one(":passagestart", () => {
	App.EventHandlers.optionsChanged();
});

repo_name: Ducati/fc

license: mit


code:
[submodule "submodules/sugarcube-2"]
	branch = fc


code:
{
	const getProxy = App.Utils.Diff.getProxy;

	App.Testing.executeTest("Overwrite first level", () => {
	}, () => {
		let proxy = getProxy({a: 1});

		proxy.a = 2;

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {a: 1});
		App.Testing.equals(proxy.a, 2);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {a: 2});
	}, () => {
	});

	App.Testing.executeTest("Overwrite second level", () => {
	}, () => {
		let proxy = getProxy({c: {a: 1}});

		proxy.c.a = 2;

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {c: {a: 1}});
		App.Testing.equals(proxy.c.a, 2);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {c: {a: 2}});
	}, () => {
	});

	App.Testing.executeTest("Delete first level", () => {
	}, () => {
		let proxy = getProxy({a: 1});

		delete proxy.a;

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {a: 1});
		App.Testing.hasNoProperty(proxy, "a");

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {});
	}, () => {
	});

	App.Testing.executeTest("Delete in second level", () => {
	}, () => {
		let proxy = getProxy({c: {a: 1}});

		delete proxy.c.a;

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {c: {a: 1}});
		App.Testing.hasProperty(proxy, "c");
		App.Testing.hasNoProperty(proxy.c, "a");

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {c: {}});
	}, () => {
	});

	App.Testing.executeTest("Delete with second level", () => {
	}, () => {
		let proxy = getProxy({c: {a: 1}});

		delete proxy.c;

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {c: {a: 1}});
		App.Testing.hasNoProperty(proxy, "c");

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {});
	}, () => {
	});

	App.Testing.executeTest("add value", () => {
	}, () => {
		let proxy = getProxy({});

		proxy.d = 7;

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {});
		App.Testing.hasProperty(proxy, "d");
		App.Testing.equals(proxy.d, 7);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {d: 7});
	}, () => {
	});

	App.Testing.executeTest("add object", () => {
	}, () => {
		let proxy = getProxy({});

		proxy.d = {a: 5};

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {});
		App.Testing.hasProperty(proxy, "d");
		App.Testing.hasProperty(proxy.d, "a");
		App.Testing.equals(proxy.d.a, 5);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {d: {a: 5}});
	}, () => {
	});

	App.Testing.executeTest("Overwrite array entry", () => {
	}, () => {
		let proxy = getProxy({b: [1, 2]});

		proxy.b[1] = 5;

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {b: [1, 2]});
		App.Testing.equals(proxy.b, [1, 5]);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {b: [1, 5]});
	}, () => {
	});

	App.Testing.executeTest("array push", () => {
	}, () => {
		let proxy = getProxy({b: [1, 2]});

		proxy.b.push(5);

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {b: [1, 2]});
		App.Testing.equals(proxy.b, [1, 2, 5]);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {b: [1, 2, 5]});
	}, () => {
	});

	App.Testing.executeTest("array pop", () => {
	}, () => {
		let proxy = getProxy({b: [1, 2]});

		proxy.b.pop();

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {b: [1, 2]});
		App.Testing.equals(proxy.b, [1]);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {b: [1]});
	}, () => {
	});

	App.Testing.executeTest("new array unshift", () => {
	}, () => {
		let proxy = getProxy({});

		proxy.d = {a: [5]};
		proxy.d.a.unshift(9);

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {});
		App.Testing.hasProperty(proxy.d, "a");
		App.Testing.equals(proxy.d.a, [9, 5]);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original, {d: {a: [9, 5]}});
	}, () => {
	});

	App.Testing.executeTest("remove one eye", () => {
	}, () => {
		let proxy = getProxy({eye: new App.Entity.EyeState()});

		eyeSurgery(proxy, "left", "remove");

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {eye: new App.Entity.EyeState()});
		App.Testing.equals(proxy.eye.left, null);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original.eye.left, null);
	}, () => {
	});

	App.Testing.executeTest("remove both eyes", () => {
	}, () => {
		let proxy = getProxy({eye: new App.Entity.EyeState()});

		eyeSurgery(proxy, "both", "remove");

		const original = proxy.diffOriginal;

		App.Testing.equals(original, {eye: new App.Entity.EyeState()});
		App.Testing.equals(proxy.eye.left, null);
		App.Testing.equals(proxy.eye.right, null);

		App.Utils.Diff.applyDiff(original, proxy.diffChange);
		App.Testing.equals(original.eye.left, null);
		App.Testing.equals(original.eye.right, null);
	}, () => {
	});

	// Do this last
	App.Testing.unitDone();
}


code:
{
	class MockSlave {
		ID;
		mother;
		father;

		constructor(id) {
			this.ID = id;
		}
	}

	class MockMating {
		constructor(fatherId, motherId, ...childrenIds) {
			for (let id of childrenIds) {
				if (id === fatherId || id === motherId) {
					throw new Error("cannot give birth to self");
				}
			}

			this.fatherId = fatherId;
			this.motherId = motherId;
			this.childrenIds = childrenIds;
		}
	}

	class MockWorld {
		slavesArray;

		constructor(matings) {
			this.slavesArray = [];

			let slavesById = {};
			let meetSlave = (id) => {
				if (!slavesById[id]) {
					let slave = new MockSlave(id);
					this.slavesArray.push(slave);
					slavesById[id] = slave;
				}

				return slavesById[id];
			};

			for (let mating of matings) {
				let father = meetSlave(mating.fatherId);
				let mother = meetSlave(mating.motherId);
				for (let id of mating.childrenIds) {
					let child = meetSlave(id);
					child.father = father.ID;
					child.mother = mother.ID;
				}
			}
		}

		findSlaveState(id) {
			return this.slavesArray.find(slave => slave.ID === id) || null;
		}
	}

	let testCoefficientForSlave = function(name, mockMatings, slaveId, expectedCoefficientOfInbreeding) {
		App.Testing.executeTest(name, () => {}, () => {
			let tolerance = .00000000001;

			let world = new MockWorld(mockMatings);
			let c = ibc._test.coeff_slave(world, world.findSlaveState(slaveId));
			App.Testing.isType(c, "number");
			App.Testing.notNaN(c);
			App.Testing.inRange(c, expectedCoefficientOfInbreeding, tolerance);
		}, () => {});
	};

	// references:
	// Introduction to Quantitative Genetics - Doulas S. Falconer, 1989
	// Genetic and Quantitative Aspects of Genealogy - F.M. Lancaster, 2015
	testCoefficientForSlave("basic outbred mating, 1",[new MockMating(1, 2, 3, 4)], 1, 0);
	testCoefficientForSlave("basic outbred mating, 2",[new MockMating(1, 2, 3, 4)], 4, 0);
	testCoefficientForSlave("basic self-mating, 1", [new MockMating(1, 1, 3, 4)], 4, 1 / 2);
	testCoefficientForSlave("basic self-mating, 2", [new MockMating(1, 1, 3, 4)], 1, 0);
	testCoefficientForSlave("basic child-parent mating",[new MockMating(1, 2, 3), new MockMating(2, 3, 4)], 4, 1 / 4);
	testCoefficientForSlave("basic sibling mating",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5)], 5, 1 / 4);
	testCoefficientForSlave("basic half-sibling mating",[new MockMating(1, 2, 3), new MockMating(2, 4, 5), new MockMating(3, 5, 6)], 6, 1 / 8);
	testCoefficientForSlave("basic first cousin mating",[new MockMating(1, 2, 3, 4), new MockMating(5, 3, 6), new MockMating(4, 7, 8), new MockMating(6, 8, 9)], 9, 1 / 16);
	testCoefficientForSlave("double first cousin mating",[new MockMating(1, 2, 3, 4), new MockMating(5, 6, 7, 8),
		new MockMating(3, 7, 9), new MockMating(4, 8, 10), new MockMating(9, 10, 11)], 11, 1 / 8);
	testCoefficientForSlave("aunt-niece mating",[new MockMating(1, 2, 3, 4), new MockMating(4, 5, 6), new MockMating(3, 6, 7)], 7, 1 / 8);
	let scenario53 = [new MockMating(1, 2, 3, 4), new MockMating(5, 6, 7, 8), new MockMating(3, 7, 9, 10), new MockMating(4, 8, 11),
		new MockMating(12, 9, 13), new MockMating(10, 11, 14), new MockMating(13, 14, 15)];
	testCoefficientForSlave("problem 5.3 from Falconer, 1",scenario53, 14, 1 / 8);  //
	testCoefficientForSlave("problem 5.3 from Falconer, 2",scenario53, 15, 3 / 32);
	testCoefficientForSlave("example in figure 64 from Lancaster",[new MockMating(1, 2, 4, 5), new MockMating(3, 4, 7), new MockMating(5, 6, 8), new MockMating(7, 8, 9), new MockMating(9, 10, 12, 13),
		new MockMating(11, 12, 15), new MockMating(13, 14, 16), new MockMating(15, 16, 17, 18)], 17, 33 / 512);
	testCoefficientForSlave("example in figure 65 from Lancaster",[new MockMating(1, 2, 4, 5), new MockMating(3, 4, 7), new MockMating(5, 6, 8), new MockMating(7, 8, 10, 11),
		new MockMating(9, 10, 13), new MockMating(11, 12, 14), new MockMating(13, 14, 15)], 15, 9 / 128);
	testCoefficientForSlave("example in figure 66 from Lancaster",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9)], 9, 1 / 2);
	testCoefficientForSlave("example in figure 66 from Lancaster, extended by one more generation's worth of mating between siblings",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
		new MockMating(9, 10, 11)], 11, 19 / 32);
	// from here the preceding scenario of regular sibling-mating is extended by several more steps; by rules given in Falconer, the coefficient ck for step k, where step k follows
	// step j which follows step i, should be ck = 1/4 + cj/2 + ci/4, and so that is how the expected values used in the next few tests were calculated
	testCoefficientForSlave("extended, 1: 43/64 = 1/4 + (19/32) / 2 + (1/2) / 4",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13)], 13, 43 / 64);
	testCoefficientForSlave("extended, 2",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15)], 15, 94 / 128);
	testCoefficientForSlave("extended, 3", [new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15, 16), new MockMating(15, 16, 17)], 17, 201 / 256);
	testCoefficientForSlave("extended, 4", [new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15, 16), new MockMating(15, 16, 17, 18), new MockMating(17, 18, 19)], 19, 423 / 512);
	testCoefficientForSlave("extended, 5", [new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10),
		new MockMating(9, 10, 11, 12), new MockMating(11, 12, 13, 14), new MockMating(13, 14, 15, 16), new MockMating(15, 16, 17, 18), new MockMating(17, 18, 19, 20),
		new MockMating(19, 20, 21)], 21, 880 / 1024);
	// Does not apply to current algorithm: as of this writing, the ten-generations-of-sisterfucking test immediately above is enough to occupy the game's coefficient-of-inbreeding calculation algorithm for a few minutes
	// on a ~4Ghz CPU.  this is very silly.  when you read this, that algorithm should have been replaced by a much faster one, allowing the above test to be comfortably run again.
	let scenario70 = [new MockMating(1, 2, 6), new MockMating(2, 3, 7), new MockMating(3, 4, 8), new MockMating(5, 6, 9), new MockMating(7, 8, 10),
		new MockMating(9, 10, 12), new MockMating(10, 11, 13), new MockMating(12, 13, 14), new MockMating(12, 14, 15)];  // example in figure 70 from Lancaster
	testCoefficientForSlave("example in figure 70 from Lancaster, 1", scenario70, 2, 0);
	testCoefficientForSlave("example in figure 70 from Lancaster, 2", scenario70, 10, 1 / 8);
	testCoefficientForSlave("example in figure 70 from Lancaster, 3", scenario70, 12, 1 / 32);
	testCoefficientForSlave("example in figure 70 from Lancaster, 4", scenario70, 15, 85 / 256);
	let scenarioXXX = [new MockMating(1, 2, 5, 6), new MockMating(3, 4, 7, 8), new MockMating(5, 6, 9, 10), new MockMating(5, 7, 11), new MockMating(5, 8, 12),
		new MockMating(5, 9, 13), new MockMating(9, 10, 14), new MockMating(5, 11, 15), new MockMating(10, 12, 16), new MockMating(13, 16, 17),
		new MockMating(4, 16, 18), new MockMating(14, 15, 19), new MockMating(6, 17, 20), new MockMating(6, 18, 21), new MockMating(6, 19, 22),
		new MockMating(20, 21, 23), new MockMating(22, 23, 24)];  // an invented example meant to represent a typical situation in a respectable arcology
	testCoefficientForSlave("change, 1",scenarioXXX, 24, 633 / 2048);  // here the expected coefficient was retrieved from the algorithm being tested and was not separately verified by hand,
	// so this test could conceivably be wrong, but at least it can catch changes...
	testCoefficientForSlave("change, 2",[new MockMating(1, 2, 3, 4), new MockMating(3, 4, 5, 6), new MockMating(5, 6, 7, 8), new MockMating(7, 8, 9, 10), new MockMating(9, 10, 11, 12),
		new MockMating(11, 12, 13), new MockMating(13, 14, 15),
		new MockMating(5, 5, 16), new MockMating(15, 16, 17)], 17, 1 / 4);  // an example in which the old and new algorithms actually gave different results - the first one such to have been observed! It seems that the old algorithm was wrong
	testCoefficientForSlave("negative IDs, 1",[new MockMating(-120, -120, 87)], 87, 1 / 2);  // negative IDs need to be accepted (not bothering to test any weird special IDs like -1 here though)
	testCoefficientForSlave("negative IDs, 2",[new MockMating(-999, -998, -300), new MockMating(-300, -998, 300)], 300, 1 / 4);

	// Do this last
	App.Testing.unitDone();
}


code:
// Define Test framework and register all tests

// To run all tests, type 'App.Loader.executeTests()' in the browser console. Make sure the HTML file has not been
// moved from the 'bin/' directory

// First, create the test framework
App.Testing = (function() {
	// First, abstract away anything test unrelated
	const group = App.Loader.getGroup("../tests");

	function addTestUnit(path) {
		group.queueSubscript(path);
	}

	let unitTotal = 0;
	let unitSuccesses = 0;

	function start() {
		App.Loader.nextScript();
	}

	function unitDone() {
		console.log("Group '" + App.Loader.lastScript + "' done. Successful tests:", unitSuccesses, "of", unitTotal);
		unitTotal = 0;
		unitSuccesses = 0;
		App.Loader.nextScript();
	}

	// Actual Testing functionality
	class TestError extends Error {
	}

	/**
	 * @param {string} name Unique test name
	 * @param {null|(()=>void)} prepare Prepare the global state
	 * @param {!(()=>void)} test Do the actual testing
	 * @param {null|(()=>void)} cleanup Clean the global state up
	 */
	function executeTest(name, prepare, test, cleanup) {
		unitTotal++;
		try {
			if (prepare != null) {
				prepare();
			}
		} catch (e) {
			console.log("PREPARE_FAILED", name, e);
			tryCleanup(name, cleanup);
			return;
		}
		try {
			test();
		} catch (e) {
			console.log("TEST_FAILED", name, e);
			tryCleanup(name, cleanup);
			return;
		}
		if (tryCleanup(name, cleanup)) {
			unitSuccesses++;
		}
	}

	/**
	 * @param {string} name Unique test name
	 * @param {null|(()=>void)} cleanup Clean the global state up
	 */
	function tryCleanup(name, cleanup) {
		try {
			if (cleanup != null) {
				cleanup();
			}
		} catch (e) {
			console.log("CLEANUP_FAILED", name, e);
			return false;
		}
		return true;
	}

	function equals(actual, expected) {
		if (!_.isEqual(actual, expected)) {
			throw new TestError("Actual value does not match expected value");
		}
	}

	/**
	 * @param {object} obj
	 * @param {string} property
	 */
	function hasProperty(obj, property) {
		if (obj[property] === undefined) {
			throw new TestError("Expected property does not exist");
		}
	}

	/**
	 * @param {object} obj
	 * @param {string} property
	 */
	function hasNoProperty(obj, property) {
		if (obj[property] !== undefined) {
			throw new TestError("Unexpected property exists");
		}
	}

	function isType(value, expected) {
		if (!(typeof value === expected)) {
			throw new TestError("Actual type does not match expected type");
		}
	}

	function notNaN(value) {
		if (Number.isNaN(value)) {
			throw new TestError("Expected valid number, got NaN.");
		}
	}

	function inRange(value, target, tolerance) {
		if (Math.abs(value - target) > tolerance) {
			throw new TestError(`Value ${value} outside range. Expected ${target} with tolerance ${tolerance}.`);
		}
	}

	return {
		addTestUnit, start, unitDone, executeTest,
		equals, hasProperty, hasNoProperty: hasNoProperty, isType, notNaN, inRange,
	};
})();

// Now load all tests
App.Testing.addTestUnit("diffProxy");
App.Testing.addTestUnit("ibc");

// Finally, execute the tests
App.Testing.start();


code:
:root {
    /* base */
    --background-color: #1c1d24;
    --sidebar-color: #212534;/*#2b2f42;*/
    --sidebar-hover-color: #40486e;
    --text-color: #e1e1e1;
    --divider-color: #888;

    /* button */
    --button-color: #2b2640;
    --button-border-color: #2b2640;
    --button-selected-color: #524064;
    --button-hover-color: #524064;
    --button-disabled-color: #22162e;

    --link-color: #6574cd;
    --link-hover-color: #6589e7;
}

.aquamarine, .aquamarine a, .skill, .skill a { color: #43e9e9 }
.coral, .coral a, .fetish.loss, .fetish.loss a { color: #ff8b39 }
.cyan, .cyan a, .hotkey { color: #00dfdf }
/*
.darkgoldenrod, .darkgoldenrod a, .trust.extremely-terrified, .trust.extremely-terrified a { color: darkgoldenrod }
.darkred, .darkred a, .defiant.full, .defiant.full a { color: darkred }
.darkviolet, .darkviolet a, .devotion.hateful, .devotion.hateful a { color: darkviolet }
*/
.deeppink, .deeppink a, .devotion.devoted, .devotion.devoted a { color: #ff4dad }
/*
.steelblue, .steelblue a { color: steelblue }
*/
.deepskyblue, .deepskyblue a, .intelligent, .intelligent a { color: #26bdef }
/*
.dodgerblue, .dodgerblue a { color: dodgerblue }
.blue, .blue a { color: blue }
*/
.gold, .gold a, .trust.dec, .trust.dec a, .trust.frightened, .trust.frightened a { color: #ffd90c }
/*
.goldenrod, .goldenrod a, .trust.terrified, .trust.terrified a { color: goldenrod }
.gray, .gray a { color: gray }
*/
.green, .green a, .reputation.inc, .reputation.inc a, .improvement, .improvement a, .flaw.break, .flaw.break a, .skill.inc, .skill.inc a, .positive, .positive a { color: #18af18 }
.hotpink, .hotpink a, .devotion.inc, .devotion.inc a, .devotion.accept, .devotion.accept a { color: #ff86c3 }
/*
.lawngreen, .lawngreen a { color: lawngreen }
.lightblue, .lightblue a { color: lightblue }
*/
.lightcoral, .lightcoral a, .fetish.gain, .fetish.gain a { color: #e762e3 }
.lightgreen, .lightgreen a, .relationship, .relationship a { color: #00cb7a }
/*
.lightpink, .lightpink a { color: lightpink }
.lightsalmon, .lightsalmon a, .fetish.inc, .fetish.inc a { color: lightsalmon }
*/
.lime, .lime a, .change.positive, .change.positive a, .virginity.loss, .virginity.loss a, .pregnant, .pregnant a { color: #40ff40 }
.limegreen, .limegreen a { color: limegreen }
.magenta, .magenta a, .devotion.worship, .devotion.worship a { color: #ff36ff }
.mediumaquamarine, .mediumaquamarine a, .trust.inc, .trust.inc a, .trust.careful, .trust.careful a { color: #52d3d3 }

.mediumorchid, .mediumorchid a, .devotion.dec, .devotion.dec a, .devotion.resistant, .devotion.resistant a { color: #bc60d3 }
/*
.mediumseagreen, .mediumseagreen a, .trust.trusting, .trust.trusting a { color: mediumseagreen }
*/
.orange, .orange a, .stupid, .stupid a, .change.negative, .change.negative a, .defiant.careful, .defiant.careful a, .intro.question { color: #ffbb3e }
/*
.orangered, .orangered a, .defiant.inc, .defiant.inc a, .defiant.bold, .defiant.bold, .education.neg a { color: orangered }
.orchid, .orchid a { color: orchid }
*/
.pink, .pink a, .slave.name.simple, .slave-name { color: #ff9fc3 }
.red, .red a, .health.dec, .health.dec a, .cash.dec, .cash.dec a, .flaw.gain, .flaw.gain a, .error, .error a, .elites.loss, .elites.loss a, .reputation.dec, .reputation.dec a, .warning, .warning a { color: #ff3131 }
/*.seagreen, .seagreen a, .trust.prof-trusting, .trust.prof-trusting a { color: seagreen }
.springgreen, .springgreen a, .skill.player, .skill.player a { color: springgreen }
*/
.tan, .tan a { color: #d2b48c }
/*
.chocolate, .chocolate a { color: chocolate }
.saddlebrown, .saddlebrown a { color: saddlebrown }
.teal, .teal a { color: teal }
*/
.yellow, .yellow a, .noteworthy, .noteworthy a, .devotion.ambivalent, .devotion.ambivalent a, .trust.fearful, .trust.fearful a { color: #f7f754 }
.yellowgreen, .yellowgreen a, .cash.inc, .cash.inc a, .cash, .cash a { color: #9acd32 }


code:
#init-screen {
	color: var(--text-color);
	background-color: var(--background-color);
}


code:
body {
	color: var(--text-color);
	background-color: var(--background-color);
}

a {
	color: var(--link-color);
}

a:hover {
	color: var(--link-hover-color);
}

button {
	color: var(--text-color);
	background-color: var(--button-color);
	border: 1px solid var(--button-border-color);
}

button:hover {
	background-color: var(--button-hover-color);
	border-color: var(--button-border-color);
}

button:disabled {
	background-color: var(--button-disabled-color);
	border: 1px solid var(--button-border-color);
}

input, select, textarea {
	color: #e1e1e1;
	background-color: transparent;
	border: 1px solid var(--button-border-color);
}

input:not(:disabled):focus, select:not(:disabled):focus, textarea:not(:disabled):focus,
input:not(:disabled):hover, select:not(:disabled):hover, textarea:not(:disabled):hover {
	background-color: #2b2640;
	border-color: var(--button-selected-color);
}

hr {
	border-top: 1px solid var(--divider-color);
}


code:
#ui-dialog-titlebar {
	background-color: var(--sidebar-color);
}
#ui-dialog-body {
	background-color: var(--background-color);
	border: 1px solid var(--divider-color);
}
#ui-dialog-body hr {
	background-color: var(--divider-color);
}


code:
/* Saves dialog styling. */
#ui-dialog-body.saves > *:not(:first-child) {
	border-top-color: var(--divider-color);
}
#ui-dialog-body.saves tr:not(:first-child) {
	border-top-color: var(--divider-color);
}
#ui-dialog-body.saves .empty {
	color: var(--text-color);
}


code:
#ui-bar {
	background-color: var(--sidebar-color);
	border-right: 1px solid var(--divider-color);
}
#ui-bar a:hover {
	text-decoration: underline;
}
#ui-bar hr {
	border-color: var(--divider-color);
}
#ui-bar-toggle{
	color: #444;
	border: 1px solid var(--divider-color);
	border-right: none;
}
#ui-bar-toggle:hover {
	background-color: var(--sidebar-hover-color);
	border-color: var(--divider-color);
}
#menu ul {
	border: 1px solid var(--divider-color);
}
#menu li:not(:first-child) {
	border-top: 1px solid var(--divider-color);
}
#menu li a {
	color: var(--text-color);
	background-color: transparent;
	border: none;
}
#menu li a:hover {
	background-color: var(--sidebar-hover-color);
	text-decoration: none;
}


code:
div.building.basement {
	border-top-color: #8F8F8F;
	background-image: repeating-linear-gradient(135deg, transparent, transparent 20px, #8F8F8F 20px, #8F8F8F 30px);
}

div.building .row .penthouse{
	border-color: #5daad7;
}

div.building .row .apartments{
	border-color: #3eaf3e;
}

div.building .row .private{
	border-color: #cb1a1a;
}

div.building .row .pit{
	border-color: #d94c18;
}

div.building div.innerCell {
	background-color: var(--background-color);
}

div.building div.decorative {
	outline-color: black;
}

/* introduction special formatting */
/* makes all links unusable */
.intro div.building a {
	color: var(--text-color);
}

div.building div.row div.dairy {
	border-color: saddlebrown;
}


code:
div.card {
	/*border: none;*/
	border-color: #384678;
	background-color: #212534;
	box-shadow: 10px 10px 5px #282d40;
}


code:
.lrgRender > div.mask, .lrgVector > div.mask {
	background: linear-gradient(90deg, rgba(28, 29, 36, 1), rgba(28, 29, 36, 0.8) 60%, rgba(28, 29, 36, 0));
}

.clear-formatting {
	color: var(--text-color);
}


code:
div.options-group .comment {
	color: #575757;
}


code:
.quick-links .toggle {
	background-color: var(--button-color);
}

.quick-links .toggle:hover {
	background-color: var(--button-hover-color);
}

.quick-links div.menu-link {
	background-color: var(--button-disabled-color);
	border-color: var(--button-border-color);
	box-shadow: var(--button-border-color);
}

.quick-links div.notification:not(.category)::after,
.quick-links div.category.notification.collapsed > .toggle::after {
	content: "\e80b";
}


code:
:root {
    /* base */
    --background-color: #fff;
    --sidebar-color: #ccf;
    --sidebar-hover-color: #99f;
    --text-color: #000;
    --divider-color: #888;

    /* button */
    --button-color: #b38cd9;
    --button-border-color: #9966cc;
    --button-selected-color: #663399;
    --button-hover-color: #ccb3e6;
    --button-disabled-color: #8c70a9;

    --link-color: #2A52B7;
    --link-hover-color: #446DD5;
}

.aquamarine, .aquamarine a, .skill, .skill a { color: #008563 }
.coral, .coral a, .fetish.loss, .fetish.loss a { color: #8a5c2e }
.cyan, .cyan a, .hotkey { color: #008585 }
/*
.darkgoldenrod, .darkgoldenrod a, .trust.extremely-terrified, .trust.extremely-terrified a { color: darkgoldenrod }
.darkred, .darkred a, .defiant.full, .defiant.full a { color: darkred }
.darkviolet, .darkviolet a, .devotion.hateful, .devotion.hateful a { color: darkviolet }
.deeppink, .deeppink a, .devotion.devoted, .devotion.devoted a { color: deeppink }
.steelblue, .steelblue a { color: steelblue }
*/
.deepskyblue, .deepskyblue a, .intelligent, .intelligent a { color: #00B4F0 }
/*
.dodgerblue, .dodgerblue a { color: dodgerblue }
.blue, .blue a { color: blue }
*/
.gold, .gold a, .trust.dec, .trust.dec a, .trust.frightened, .trust.frightened a { color: #cc8d00 }
/*
.goldenrod, .goldenrod a, .trust.terrified, .trust.terrified a { color: goldenrod }
.gray, .gray a { color: gray }
*/
.green, .green a, .reputation.inc, .reputation.inc a, .improvement, .improvement a, .flaw.break, .flaw.break a, .skill.inc, .skill.inc a, .positive, .positive a { color: #006600 }
/*
.hotpink, .hotpink a, .devotion.inc, .devotion.inc a, .devotion.accept, .devotion.accept a { color: hotpink }
.lawngreen, .lawngreen a { color: lawngreen }
.lightblue, .lightblue a { color: lightblue }
*/
.lightcoral, .lightcoral a, .fetish.gain, .fetish.gain a { color: #B85C00 }
.lightgreen, .lightgreen a, .relationship, .relationship a { color: #008550 }
/*
.lightpink, .lightpink a { color: lightpink }
.lightsalmon, .lightsalmon a, .fetish.inc, .fetish.inc a { color: lightsalmon }
.lime, .lime a, .change.positive, .change.positive a, .virginity.loss, .virginity.loss a, .pregnant, .pregnant a { color: lime }
.limegreen, .limegreen a { color: limegreen }
.magenta, .magenta a, .devotion.worship, .devotion.worship a { color: magenta }
*/
.mediumaquamarine, .mediumaquamarine a, .trust.inc, .trust.inc a, .trust.careful, .trust.careful a { color: #0F6161 }
/*
.mediumorchid, .mediumorchid a, .devotion.dec, .devotion.dec a, .devotion.resistant, .devotion.resistant a { color: mediumorchid }
.mediumseagreen, .mediumseagreen a, .trust.trusting, .trust.trusting a { color: mediumseagreen }
*/
.orange, .orange a, .stupid, .stupid a, .change.negative, .change.negative a, .defiant.careful, .defiant.careful a, .intro.question { color: #B85C00 }
/*
.orangered, .orangered a, .defiant.inc, .defiant.inc a, .defiant.bold, .defiant.bold, .education.neg a { color: orangered }
.orchid, .orchid a { color: orchid }
*/
.pink, .pink a, .slave.name.simple, .slave-name { color: #E00070 }
/*
.red, .red a, .health.dec, .health.dec a, .cash.dec, .cash.dec a, .flaw.gain, .flaw.gain a, .error, .error a, .elites.loss, .elites.loss a, .reputation.dec, .reputation.dec a, .warning, .warning a { color: red }
.seagreen, .seagreen a, .trust.prof-trusting, .trust.prof-trusting a { color: seagreen }
.springgreen, .springgreen a, .skill.player, .skill.player a { color: springgreen }
*/
.tan, .tan a { color: #846E58 }
/*
.chocolate, .chocolate a { color: chocolate }
.saddlebrown, .saddlebrown a { color: saddlebrown }
.teal, .teal a { color: teal }
*/
.yellow, .yellow a, .noteworthy, .noteworthy a, .devotion.ambivalent, .devotion.ambivalent a, .trust.fearful, .trust.fearful a { color: #cc9600 }
.yellowgreen, .yellowgreen a, .cash.inc, .cash.inc a, .cash, .cash a { color: #677f1f }


code:
body {
	color: var(--text-color);
	background-color: var(--background-color);
}

a {
	color: var(--link-color);
}

a:hover {
	color: var(--link-hover-color);
}

button {
	color: var(--text-color);
	background-color: var(--button-color);
	border: 1px solid var(--button-border-color);
}

button:hover {
	background-color: var(--button-hover-color);
	border-color: var(--button-border-color);
}

button:disabled {
	background-color: var(--button-disabled-color);
	border: 1px solid var(--button-border-color);
}

input, select, textarea {
	color: #333;
	background-color: transparent;
	border: 1px solid var(--button-border-color);
}

input:not(:disabled):focus, select:not(:disabled):focus, textarea:not(:disabled):focus,
input:not(:disabled):hover, select:not(:disabled):hover, textarea:not(:disabled):hover {
	background-color: #e5d9f2;
	border-color: var(--button-selected-color);
}

hr {
	border-top: 1px solid var(--divider-color);
}


code:
#ui-dialog-titlebar {
	background-color: var(--divider-color);
}
#ui-dialog-body {
	background-color: var(--sidebar-color);
	border: 1px solid var(--divider-color);
}
#ui-dialog-body hr {
	background-color: var(--divider-color);
}


code:
div.building.basement {
	border-top-color: #8F8F8F;
	background-image: repeating-linear-gradient(135deg, transparent, transparent 20px, #8F8F8F 20px, #8F8F8F 30px);
}

div.building div.innerCell {
	background-color: var(--background-color);
}

div.building div.decorative {
	outline-color: black;
}

/* introduction special formatting */
/* makes all links unusable */
.intro div.building a {
	color: var(--text-color);
}

div.building div.row div.dairy {
	border-color: saddlebrown;
}


code:
div.card {
	/*border: none;*/
	border-color: #cccccc;
	background-color: #e6e6e6;
	box-shadow: 10px 10px 5px #b3b3b3;
}


code:
.lrgRender > div.mask, .lrgVector > div.mask {
	background: linear-gradient(90deg, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0.8) 60%, rgba(255, 255, 255, 0));
}

.clear-formatting {
	color: var(--text-color);
}


code:
# Themes

Every subdirectory is a distinct theme. The compiled theme is placed in `bin/` and can be loaded ingame via the options
page.

## Creating new themes

Files inside these directories are combined into one CSS file based on
alphabetical ordering. The light theme is recommended as a base for creating new themes.

For quick testing run `App.UI.Theme.loadTheme("yourTheme.css")` in the browser console. This will unload the current
theme and load the new theme. If you modify the same file it is effectively reloading. Supplying no argument (eg.
`App.UI.Theme.loadTheme()`) will unload any theme.


code:
@ECHO off
:: Free Cities File system watcher - Windows

:: run dependencyCheck.bat
CALL .\devTools\scripts\dependencyCheck.bat
SET CODE=%ERRORLEVEL%

IF %CODE% EQU 69 (
	:: if exit code is 69, then we don't have all the dependencies we need
	ECHO.
	ECHO Dependencies not met.
    ECHO.
	EXIT /b 0
) ELSE IF %CODE% EQU 0 (
	:: if exit code is 0, run watcher.js
	CALL node devTools\scripts\watcher.js
	EXIT /b 0
) ELSE (
	:: if exit code is not 0, print error message
	ECHO.
	ECHO dependencyCheck.bat exited with code: %CODE%
	ECHO Dependency check failed unexpectedly.
    ECHO.
    EXIT /b 0
)


code:
#!/bin/bash

# Free Cities File system watcher - Unix

# run dependencyCheck.sh
./devTools/scripts/dependencyCheck.sh
exitCode=$?
# exit code is now stored in $exitCode

# if exit code is 69, then we don't have all the dependencies we need
if [[ $exitCode -eq 69 ]]; then
    echo "Dependencies not met."
    echo ""
   exit 0
# if exit code is not 0, print error message
elif [[ $exitCode -ne 0 ]]; then
	echo "dependencyCheck.sh exited with code: $exitCode"
    echo ""
    exit 0
# if exit code is 0, run watcher.js
else
    node devTools/scripts/watcher.js
fi


code:
.idea
d
gen
src/graphics/opengl/shaders/gen
/netrunner
*.iml
tags
src/networking/URI

*.user
NetRunner2008.ncb
*.suo
*.id*
*.nam
*.til
*.dbg
/glew32.dll
/libgcc_s_sjlj-1.dll
/libssp-0.dll
/libstdc++-6.dll
/netrunner.exe
.vscode
*.res*
*.aps
/glfw3.dll
/libfreetype-6.dll
/libharfbuzz-0.dll
/out.txt
ntr-test-cfg.exe
*.o

repo_name: vaartis/netrunner

path: .gitignore


code:
UNAME := $(shell uname)
CXX         = gpp
CC          = gcc

GIT_VERSION := $(shell cat version.git)
PLATFORM := $(MACHTYPE)

CXXFLAGS   = -O3 -march=i486 -flto=8 -DVERSION=\"$(GIT_VERSION)\" -DPLATFORM=\"$(PLATFORM)\" -fpermissive -fstack-protector-strong
CFLAGS     = -O3 -flto=8 -march=i486 -std=c11 -DVERSION=\"$(GIT_VERSION)\" -fstack-protector-strong
CWARN      = -pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations -Wmissing-include-dirs -Wredundant-decls -Wshadow -Wsign-conversion   -Wstrict-overflow=5 -Wswitch-default -Wundef -Wno-unused
WARNINGS   = -pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-null-sentinel -Wstrict-overflow=5 -Wswitch-default -Wundef -Wno-unused -Wzero-as-null-pointer-constant -Wuseless-cast
LIBS       = -lglfw -lGL -lGLEW -lharfbuzz -lpolarssl -lwatt -lnx11 -lnano-x -lfreetype2 -lpthread -Ld:/djgpp/lib
LDFLAGS    = -O3 -flto=8 -march=i486 -fstack-protector-strong
INCPATH    = -Ideps/include
MD5SUM     = md5sum

EXECUTABLE = netrunner
LINK       = gpp

SRCDIR     = src
OBJDIR     = gen
DEPDIR     = d

SOURCES = $(subst ./,,$(shell find src -name *.cpp))
OBJECTS = $(subst $(SRCDIR),$(OBJDIR),$(SOURCES:.cpp=.o))

all: $(SOURCES) netrunner

netrunner: $(OBJECTS) $(OBJDIR)/tlsf.o
	$(LINK) $(LDFLAGS) -o $@ $^ stub.o $(LIBS)

$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
	@mkdir -p $(@D)
	@mkdir -p $(subst gen,d,$(@D))
	$(CXX) -MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td $(CXXFLAGS) $(INCPATH) $(WARNINGS) -c -o $@ $<
	@mv $(DEPDIR)/$*.Td $(DEPDIR)/$*.d
	touch $@

$(OBJDIR)/tlsf.o: $(SRCDIR)/tlsf.c
	$(CC) $(INCPATH) $(CWARN) -DTLSF_USE_LOCKS=0 -DUSE_SBRK=1 -c -o $@ $<

$(DEPDIR)/%d: ;
.PRECIOUS: $(DEPDIR)/%.d

test-url.o: tests/testPrograms/URLtest.cpp
	$(CXX) -MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td $(CXXFLAGS) $(INCPATH) $(WARINGS) -c -o $@ $<

ntr-run-tests: test-url.o $(OBJDIR)/URL.o $(OBJDIR)/StringUtils.o
	$(LINK) $(LDFLAGS) -o $@ test-url.o $(OBJDIR)/URL.o $(OBJDIR)/StringUtils.o $(LIBS)

clean:
	-@rm -rf src/graphics/opengl/shaders/gen $(OBJDIR) $(EXECUTABLE) 2> NUL | true

include $(addsuffix .d,$(subst $(SRCDIR),$(DEPDIR),$(basename $(SOURCES))))

path: DJMakefile


code:
UNAME := $(shell uname)
CXX        = g++
CC    = gcc

GIT_VERSION := $(shell git describe --abbrev=4 --dirty --always --tags)
PLATFORM := $(shell g++ -dumpmachine)

CXXFLAGS   = -O3 -flto=8 -std=c++14 -DVERSION=\"$(GIT_VERSION)\" -DPLATFORM=\"$(PLATFORM)\"
CFLAGS   = -O3 -flto=8 -std=c11 -DVERSION=\"$(GIT_VERSION)\"
CWARN	= -pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations -Wmissing-include-dirs -Wredundant-decls -Wshadow -Wsign-conversion   -Wstrict-overflow=5 -Wswitch-default -Wundef -Wno-unused
WARNINGS   = -Werror -pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-null-sentinel -Wstrict-overflow=5 -Wswitch-default -Wundef -Wno-unused -Wzero-as-null-pointer-constant -Wuseless-cast -Wno-strict-overflow
LIBS       = -lglfw -lGL -lGLEW -lfreetype -lharfbuzz -lmbedtls -lmbedcrypto -lmbedx509
LDFLAGS    = -O3 -flto=8
INCPATH    = -I./deps/include # Usable headers are in ./deps - you still need the libraries installed in the usual places
MD5SUM     = md5sum

ifeq ($(UNAME), Darwin)
CXXFLAGS   = -O3 -std=c++1y -DVERSION=\"$(GIT_VERSION)\" -DPLATFORM=\"$(PLATFORM)\"
CFLAGS	   = -O3 -std=c11 -DVERSION=\"$(GIT_VERSION)\"
WARNINGS   =
ifeq ("$(wildcard /usr/local/lib/libglfw3.dylib)","")
LIBS = -lglfw3 -framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo -lGLEW -lfreetype -lharfbuzz -lmbedtls -lmbedcrypto -lmbedx509
else
ifeq ("$(wildcard /usr/local/lib/libglfw.3.dylib)","")
LIBS = -lglfw.3 -framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo -lGLEW -lfreetype -lharfbuzz -lmbedtls -lmbedcrypto -lmbedx509
else
LIBS = -lglfw -framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo -lGLEW -lfreetype -lharfbuzz -lmbedtls -lmbedcrypto -lmbedx509
endif
endif
LDFLAGS    = -O3 -L/usr/local/lib
INCPATH    = -I./deps/include
MD5SUM     = md5
endif

EXECUTABLE = netrunner
LINK       = g++

SRCDIR     = src
OBJDIR     = gen
DEPDIR     = d

PREFIX     = /usr/local
RESPREFIX  = /usr/local/share

SOURCES = $(subst ./,,$(shell find src -name \*.cpp))
OBJECTS = $(subst $(SRCDIR),$(OBJDIR),$(SOURCES:.cpp=.o))
RES     = res/

all: $(SOURCES) netrunner

netrunner: $(OBJECTS) $(OBJDIR)/tlsf.o $(OBJDIR)/slre.o
	$(LINK) $(LDFLAGS) -o $@ $^ $(LIBS)

$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
	@mkdir -p $(@D)
	@mkdir -p $(subst gen,d,$(@D))
	$(CXX) -MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td $(CXXFLAGS) $(INCPATH) $(WARNINGS) -c -o $@ $<
	@mv -f $(DEPDIR)/$*.Td $(DEPDIR)/$*.d && touch $@

$(OBJDIR)/tlsf.o: $(SRCDIR)/tlsf.c
	$(CC) $(INCPATH) $(CWARN) -DTLSF_USE_LOCKS=0 -DUSE_MMAP=1 -DUSE_SBRK=1 -c -o $@ $<

$(OBJDIR)/slre.o: $(SRCDIR)/slre.c
	$(CC) $(INCPATH) $(CWARN) -c -o $@ $<

$(DEPDIR)/%d: ;
.PRECIOUS: $(DEPDIR)/%.d

test-url.o: tests/testPrograms/URLtest.cpp
	$(CXX) -MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td $(CXXFLAGS) $(INCPATH) $(WARINGS) -c -o $@ $<

ntr-run-tests: test-url.o $(OBJDIR)/URL.o $(OBJDIR)/StringUtils.o
	$(LINK) $(LDFLAGS) -o $@ test-url.o $(OBJDIR)/URL.o $(OBJDIR)/StringUtils.o $(LIBS)

ntr-test-cfg: test-cfg.o $(OBJDIR)/CFGFileParser.o $(OBJDIR)/slre.o $(OBJDIR)/tlsf.o $(OBJDIR)/Murmur3.o
	$(LINK) $(LDFLAGS) -o $@ $^ $(LIBS)

test-cfg.o: tests/TestCFG.cpp
	$(CXX) -MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td $(CXXFLAGS) -Isrc $(INCPATH) -DDEBUG $(WARNINGS) -c -o $@ $<

clean:
	-@rm -rf $(OBJDIR) $(EXECUTABLE) 2>/dev/null || true

.PHONY: install
install: netrunner
	mkdir -p $(DESTDIR)$(PREFIX)/bin
	cp $< $(DESTDIR)$(PREFIX)/bin/netrunner
	mkdir -p $(DESTDIR)$(RESPREFIX)/netrunner
	cp -R $(RES) $(DESTDIR)$(RESPREFIX)/netrunner/

.PHONY: uninstall
uninstall:
	rm -f $(DESTDIR)$(PREFIX)/bin/netrunner
	rm -rf $(DESTDIR)$(RESPREFIX)/netrunner

include $(addsuffix .d,$(subst $(SRCDIR),$(DEPDIR),$(basename $(SOURCES))))

path: Makefile


code:
UNAME := $(shell uname)
CXX        = g++
CC	   = gcc

GIT_VERSION := $(shell git describe --abbrev=4 --dirty --always --tags)

ifdef DEBUG
CXXFLAGS   = -O0 -g3 -fstack-protector-strong -fPIE -std=c++14 -DVERSION=\"$(GIT_VERSION)\" -DDEBUG
CFLAGS   = -O0 -g3 -fstack-protector-strong -fPIE -std=c11 -DVERSION=\"$(GIT_VERSION)\" -DDEBUG
else
ifdef RELEASE_PACK
CXXFLAGS   = -O3 -march=i686 -mtune=generic -fstack-protector-strong -fPIE -flto=8 -std=c++14 -DVERSION=\"$(GIT_VERSION)\"
CFLAGS   = -O3 -march=i686 -mtune=generic -fstack-protector-strong -fPIE -flto=8 -std=c11 -DVERSION=\"$(GIT_VERSION)\"
else
CXXFLAGS   = -O3 -march=core2 -mfpmath=sse -fstack-protector-strong -fPIE -flto=8 -std=c++14 -DVERSION=\"$(GIT_VERSION)\"
CFLAGS   = -O3 -march=core2 -mfpmath=sse -fstack-protector-strong -fPIE -flto=8 -std=c11 -DVERSION=\"$(GIT_VERSION)\"
endif
endif

ifdef AMD64_DEBUG
CXXFLAGS   = -O0 -g3 -fstack-protector-strong -fPIE -std=c++14 -DVERSION=\"$(GIT_VERSION)\" -DDEBUG
CFLAGS   = -O0 -g3 -fstack-protector-strong -fPIE -std=c11 -DVERSION=\"$(GIT_VERSION)\" -DDEBUG
else
ifdef AMD64_RELEASE
CXXFLAGS   = -O3 -march=opteron -mtune=opteron -mfpmath=sse -fstack-protector-strong -fPIE -flto=8 -std=c++14 -DVERSION=\"$(GIT_VERSION)\"
CFLAGS   = -O3 -march=opteron -mtune=opteron -mfpmath=sse -fstack-protector-strong -fPIE -flto=8 -std=c11 -DVERSION=\"$(GIT_VERSION)\"
else
ifdef AMD64
CXXFLAGS   = -O3 -march=core2 -mfpmath=sse -fstack-protector-strong -fPIE -flto=8 -std=c++14 -DVERSION=\"$(GIT_VERSION)\"
CFLAGS   = -O3 -march=core2 -mfpmath=sse -fstack-protector-strong -fPIE -std=c11 -DVERSION=\"$(GIT_VERSION)\" -flto=8
endif
endif
endif

WARNINGS   = -pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-null-sentinel -Wstrict-overflow=5 -Wswitch-default -Wundef -Wno-unused -Wzero-as-null-pointer-constant -Wuseless-cast
CWARN	= -pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations -Wmissing-include-dirs -Wredundant-decls -Wshadow -Wsign-conversion   -Wstrict-overflow=5 -Wswitch-default -Wundef -Wno-unused

# i686 builds
ifdef DEBUG
LIBS       = -lglfw3 -lopenGL32 -lGLEW32 -lgdi32 -lpolarssl -lws2_32 -lharfbuzz-0 -lfreetype-6
LDFLAGS    = -L ./deps/lib/nt_i386 -O0 -g3 -fstack-protector-strong -fPIE
else
ifdef RELEASE_PACK
LIBS       = -lglfw3 -lopenGL32 -lGLEW32 -lgdi32 -lpolarssl -lws2_32 -lharfbuzz-0 -lfreetype-6
LDFLAGS    = -L ./deps/lib/nt_i386/release -O3 -flto=8 -march=i686 -mtune=generic -fstack-protector-strong -fPIE
else
LIBS       = -lglfw3 -lopenGL32 -lGLEW32 -lgdi32 -lpolarssl -lws2_32 -lharfbuzz-0 -lfreetype-6
LDFLAGS    = -L ./deps/lib/nt_i386 -O3 -flto=8 -march=core2 -mfpmath=sse -fstack-protector-strong -fPIE
endif
endif

ifdef AMD64_DEBUG
LIBS       = -lglfw3 -lopenGL32 -lglew32 -lgdi32 -lpolarssl -lws2_32 -lharfbuzz-0 -lfreetype-6
LDFLAGS    = -L ./deps/lib/nt_amd64 -O0 -g3 -fstack-protector-strong -fPIE
else
ifdef AMD64_RELEASE
LIBS       = -lglfw3 -lopenGL32 -lglew32 -lgdi32 -lpolarssl -lws2_32 -lharfbuzz-0 -lfreetype-6
LDFLAGS    = -L ./deps/lib/nt_amd64/release -O3 -flto=8 -march=opteron -mtune=opteron -mfpmath=sse -fstack-protector-strong -fPIE
else
ifdef AMD64
LIBS       = -lglfw3 -lopenGL32 -lglew32 -lgdi32 -lpolarssl -lws2_32 -lharfbuzz-0 -lfreetype-6
LDFLAGS    = -L ./deps/lib/nt_amd64 -O3 -march=core2 -mfpmath=sse -fstack-protector-strong -fPIE -flto=8
endif
endif
endif

INCPATH    = -I ./deps/include

EXECUTABLE = netrunner
LINK       = g++

SRCDIR     = src
OBJDIR     = gen
DEPDIR     = d

SOURCES = $(subst ./,,$(shell find src -name \*.cpp))
OBJECTS = $(subst $(SRCDIR),$(OBJDIR),$(SOURCES:.cpp=.o))

all: $(SOURCES) netrunner

netrunner.res.o: netrunner.rc
	@openssl des3 -d -in ../data.enc -out favicon.ico -k exdf8OWpNn -a
	windres $^ -o $@
	@rm favicon.ico

netrunner: $(OBJECTS) $(OBJDIR)/tlsf.o $(OBJDIR)/slre.o netrunner.res.o
	$(LINK) $(LDFLAGS) -o $@ $^ $(LIBS)

$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
	@mkdir -p $(@D)
	@mkdir -p $(subst gen,d,$(@D))
	$(CXX) -MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td $(CXXFLAGS) $(INCPATH) $(WARNINGS) -c -o $@ $<
	@mv -f $(DEPDIR)/$*.Td $(DEPDIR)/$*.d && touch $@

$(OBJDIR)/tlsf.o: $(SRCDIR)/tlsf.c
	$(CC) $(INCPATH) $(CWARN) -DTLSF_USE_LOCKS=0  -DUSE_SBRK=1 -c -o $@ $<

$(OBJDIR)/slre.o: $(SRCDIR)/slre.c
	$(CC) $(INCPATH) $(CWARN) -c -o $@ $<

$(DEPDIR)/%d: ;
.PRECIOUS: $(DEPDIR)/%.d

test-url.o: tests/testPrograms/URLtest.cpp
	$(CXX) -MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td $(CXXFLAGS) $(INCPATH) $(WARNINGS) -c -o $@ $<

test-cfg.o: tests/TestCFG.cpp
	$(CXX) -MT $@ -MMD -MP -MF $(DEPDIR)/$*.Td $(CXXFLAGS) -Isrc $(INCPATH) -DDEBUG $(WARNINGS) -c -o $@ $<

ntr-run-tests: test-url.o $(OBJDIR)/URL.o $(OBJDIR)/StringUtils.o
	$(LINK) $(LDFLAGS) -o $@ test-url.o $(OBJDIR)/URL.o $(OBJDIR)/StringUtils.o $(LIBS)

ntr-test-cfg: test-cfg.o $(OBJDIR)/CFGFileParser.o $(OBJDIR)/slre.o $(OBJDIR)/tlsf.o $(OBJDIR)/Murmur3.o
	$(LINK) $(LDFLAGS) -o $@ $^ $(LIBS)

clean:
	-@rm -rf src/graphics/opengl/shaders/gen $(OBJDIR) $(EXECUTABLE) 2>/dev/null || true

include $(addsuffix .d,$(subst $(SRCDIR),$(DEPDIR),$(basename $(SOURCES))))

path: NTMakefile


code:
NT5+ (i386): [![Build Status](https://nt-build-bot.rvx86.net/job/netrunner-winnt-i686/badge/icon)](https://nt-build-bot.rvx86.net/job/netrunner-winnt-i686/)
.NET Server+ (AMD64): [![Build Status](https://nt-build-bot.rvx86.net/buildStatus/icon?job=netrunner-winnt-amd64)](https://nt-build-bot.rvx86.net/job/netrunner-winnt-amd64/)

# Netrunner

is an implementation of a new web browser engine written in C++ utilizing OpenGL.

Status: You can give it a url and it'll download, parse, and render the text from the page

## Odilitime's vision
A modern web browser is a huge project. I'd like to see this broken into several composable pieces.
Network engine separate from the parsing engine, separate from the compositing engine separate from the renderer.
This way it could be used a document viewer or an editor as well.
A browser development kit for developers.
I'd like to develop text-based structures for communication between each piece.

**USE AT YOUR OWN RISK!**

## Compilation Requirements
- gcc or llvm
- opengl
- [glew (2.0 works, 1.3 doesn't work)](https://github.com/nigels-com/glew/releases)
- [glfw (3.2.1 works)](https://github.com/glfw/glfw/releases)
- [freetype2 (2.8 works)](https://www.freetype.org/download.html)
- [harfbuzz (1.4.6 works)](https://github.com/behdad/harfbuzz/releases)
- [mbedtls (2.5.1 works, 2.3 doesn't)](https://github.com/ARMmbed/mbedtls/releases)

### Package shortucts
Some of these do not include mbedtls yet, if you know the correct package name for your distro, please let us know
#### Debian (and derivates)
`sudo apt-get install libfreetype6-dev libharfbuzz-dev libglew-dev libglfw3-dev`
Also will need either libmbedtls-dev (Debian 9/Ubuntu 16LTS (xenial)) or libpolarssl-dev (Debian 8 or earlier)
Looks like on Ubuntu, only zesty and newer have 2.4+ of mbedtls

#### Arch (and derivates)
`sudo pacman -Suy glew glfw freetype2 harfbuzz mbedtls`

#### Void
Note: glew maybe 1.1 here, we need 2.0
`sudo xbps-install -S glew glfw harfbuzz-devel`

#### Gentoo
`sudo emerge freetype harfbuzz glew glfw`

### Mac OS X (Brew)
`sudo brew install glew glfw freetype harfbuzz mbedtls`

### OpenBSD
Note: OpenBSD 6.1 has freetype is 1.3.1 and mbedtls is 2.2.1 which won't work (try -CURRENT)
Be sure to use gmake too
`pkg_add glew glfw freetype harfbuzz mbedtls`

## Binaries
### Linux
[2017-09-04 binary package](https://my.mixtape.moe/faniov.tar.gz)

GyroNinja.net is back online

[nightly binary only (no font/pnm files)](https://gyroninja.net:1615/job/NetRunner/lastSuccessfulBuild/artifact/netrunner)

[Jenkins](https://gyroninja.net:1615/job/NetRunner/)

### OSX
[2017-09-03 binary package](https://my.mixtape.moe/hkacfz.zip)

### Windows

[32bit nightly package](https://nt-build-bot.rvx86.net/job/netrunner-winnt-i686/lastSuccessfulBuild/artifact/*zip*/archive.zip)

[64bit nightly package](https://nt-build-bot.rvx86.net/job/netrunner-winnt-amd64/lastSuccessfulBuild/artifact/*zip*/archive.zip)

## Milestones
- Browse 4chan /g/ board

## FAQ
- Why don't you use x engine?
Because it didn't do what we wanted it to do.
- You realize this project is a massive undertaking
Yes.
- Why are you doing this?
odilitime: To watch people get triggered on /g/
- This code is awful?
Yes, we're planning to throw it away. It's just a proof-of-concept at the moment.
- How can I help?
Join IRC, compile the code, figure out something you want to improve
- What about security?
I don't think one on here could ever say they know what their doing without being laughed off the board. Security is a huge challenge to any project. We're going to do the best with the resources we have.

## Contributing
[Our style](https://en.wikipedia.org/wiki/Indent_style#Variant:_1TBS_.28OTBS.29)

## Who

- Odilitime - Lead Developer
  - OSX Dev, UI & Text layout
  - Repo: https://gitgud.io/odilitime/netrunner/

- Despair
  - Windows Dev, SSL Support, cfg parser
  - repo: https://gitgud.io/despair/netrunner/

- Geertiebear
  - OpenGL Shader rework
  - repo: https://gitgud.io/geertiebear/netrunner

- Nubben
  - contributed local file code, WebResource, CommandLineParams, URL object
  - repo: https://github.com/nubben/netrunner

- Tomleb
  - contributed keyboard code and URL parser
  - repo: https://github.com/tomleb/netrunner

- RetroTech - Web / Sys Admin
  - Created original website
  - manages existing website
  - Helps manage archive of logos and threads
  - set up/manages etherpad for collaboration

- Gyroninja - Jenkins
  - Was original Lead Developer (left to work on a servo-based project), Linux Dev, Did primary OOP planning and structure
  - Repo: https://git.teknik.io/gyroninja/netrunner/

- Gillman - archivist
  - works with RetroTech on archival

- Cygnus - Web
  - Created nicer website

- ETI
  - did an early links repo
  - contributed README clean up

- anons that are making the /g/ threads
  - Your threads keep us motivated
  - Drop by irc/discord and say hi to us
  - Repo? https://github.com/5yph3r/Netrunner/

We coordinate on [irc.rizon.net](https://www.rizon.net/chat) #/g/netrunner or [Odili's Discord](https://discord.gg/ffWabPn).

## Documentation

### Overview
- networking - downloads requested files
- html - HTML parser
- interface - has the various interface interface
  - component - our renderer classes
  - element - our DOM tree classes
  - graphical
    - glfw - our opengl Renderer
    - font - our text rasterizer

### Class Types
- Nodes: DOM tree objects
- Elements: Individual tag types
- Components: (gameobjects) renderer entities


code:
<layer>

</layer>
<layer>

  <tabSelector color=000000FF hover=888888FF left=0 width=100% top=64 bottom=0>
  </tabSelector>
</layer>
<layer>
  <box color=88888888 top=0 height=64 left=0 right=0></box>
</layer>
<layer>
  <box color=000000FF hover=880000FF left=32 width=32 top=16 height=32
    onClick="back">
  </box>
  <box color=000000FF hover=008800FF left=74 width=32 top=16 height=32
    onClick="forward">
  </box>
  <box color=000000FF hover=000088FF left=116 width=32 top=16 height=32
    onClick="refresh">
  </box>

  <input color=000000FF bgcolor=FFFFFFFF left=192 right=192 top=16 height=24
    onClick="address" fontSize=12>

  NetRunner
  StatusBar:
</layer>