code:
:: cyberConfig [nobr]

<<switch $temp>>

<<case 0>>
	This room is lined with shelves and cabinets, it could be easily mistaken for a storage room if it were not for examination table in its center.

	<<set $nextButton to "Back", $nextLink to "Slave Interact">>
		<<if $activeSlave.amp != 1>>
			Your slave $activeSlave.slaveName is obediently waiting for your instructions.
		<<else>>
			Your slave $activeSlave.slaveName is lying on the table, waiting for your instructions.
		<</if>>

	<<if $activeSlave.origEye == "implant">>

		$pronounCap has an ocular implant installed.
		<<if $activeSlave.eyes == 1>>
			They are operating normally.
		<<elseif $activeSlave.eyes == -1>>
			They are set to blur her vision.
		<<else>>
			They are turned off.
		<</if>>

		<<if $activeSlave.eyes != 1>>[[Restore vision|cyberConfig][$activeSlave.eyes to 1,$temp to 3]] | <</if>>
		<<if $activeSlave.eyes != -1>>[[Blur|cyberConfig][$activeSlave.eyes to -1,$temp to 3]] | <</if>>
		<<if $activeSlave.eyes != -2>>[[Disable|cyberConfig][$activeSlave.eyes to -2,$temp to 3]]<</if>>

		$pronounCap currently has $activeSlave.eyeColor lenses equipped.

		Swap out $possessive lenses:
		[[Blue|cyberConfig][$activeSlave.eyeColor to "blue",$temp to 2,$cash -= $modCost]]
		| [[Black|cyberConfig][$activeSlave.eyeColor to "black",$temp to 2,$cash -= $modCost]]
		| [[Brown|cyberConfig][$activeSlave.eyeColor to "brown",$temp to 2,$cash -= $modCost]]
		| [[Green|cyberConfig][$activeSlave.eyeColor to "green",$temp to 2,$cash -= $modCost]]
		| [[Turquoise|cyberConfig][$activeSlave.eyeColor to "turquoise",$temp to 2,$cash -= $modCost]]
		| [[Sky-blue|cyberConfig][$activeSlave.eyeColor to "sky-blue",$temp to 2,$cash -= $modCost]]
		| [[Hazel|cyberConfig][$activeSlave.eyeColor to "hazel",$temp to 2,$cash -= $modCost]]
		| [[Pale-grey|cyberConfig][$activeSlave.eyeColor to "pale-grey",$temp to 2,$cash -= $modCost]]
		| [[White|cyberConfig][$activeSlave.eyeColor to "white",$temp to 2,$cash -= $modCost]]
		| [[Pink|cyberConfig][$activeSlave.eyeColor to "pink",$temp to 2,$cash -= $modCost]]
		| [[Amber|cyberConfig][$activeSlave.eyeColor to "amber",$temp to 2,$cash -= $modCost]]
		| [[Red|cyberConfig][$activeSlave.eyeColor to "red",$temp to 2,$cash -= $modCost]]
		| [[Catlike|cyberConfig][$activeSlave.eyeColor to "catlike",$temp to 2,$cash -= $modCost]]
		| [[Serpent-like|cyberConfig][$activeSlave.eyeColor to "serpent-like",$temp to 2,$cash -= $modCost]]
		| [[Devilish|cyberConfig][$activeSlave.eyeColor to "devilish",$temp to 2,$cash -= $modCost]]
		| [[Demonic|cyberConfig][$activeSlave.eyeColor to "demonic",$temp to 2,$cash -= $modCost]]
		| [[Hypnotic|cyberConfig][$activeSlave.eyeColor to "hypnotic",$temp to 2,$cash -= $modCost]]
		| [[Heart-shaped|cyberConfig][$activeSlave.eyeColor to "heart-shaped",$temp to 2,$cash -= $modCost]]
	<</if>>

	<<if $activeSlave.PLimb > 0>>

		$pronounCap has PLimb interface installed. You can assign and adjust $possessive prosthetics here.
		<<if $activeSlave.amp <= -1>>

			$possessiveCap prosthetics are currently attached, if you wish to change them you will first need to detach them.

			[[Detach|cyberConfig][$temp to 1,$nextButton to "Continue", $nextLink to "cyberConfig"]]
		<<else>>

			<<if $stockpile.basicPLimb > 0>>[[Attach basic limbs|cyberConfig][$temp to 4, $activeSlave.amp to -1, $stockpile.basicPLimb -= 1]]<</if>>
			<<if $stockpile.advSexPLimb > 0>>[[Attach sex limbs|cyberConfig][$temp to 4, $activeSlave.amp to -2, $stockpile.advSexPLimb -= 1]]<</if>>
			<<if $stockpile.advGracePLimb > 0>>[[Attach beauty limbs|cyberConfig][$temp to 4, $activeSlave.amp to -3, $stockpile.advGracePLimb -= 1]]<</if>>
			<<if $stockpile.advCombatPLimb > 0>>[[Attach combat limbs|cyberConfig][$temp to 4, $activeSlave.amp to -4, $stockpile.advCombatPLimb -= 1]]<</if>>
			<<if $activeSlave.PLimb == 2>>
				<<if $stockpile.cyberneticPLimb > 0>>[[Attach cybernetic limbs|cyberConfig][$temp to 4, $activeSlave.amp to -5, $stockpile.cyberneticPLimb -= 1]]<</if>>
			<<else>>
				 //To equip more advanced prosthetics you will need to upgrade your slaves' PLimb interface.//
			<</if>>
		<</if>>

	<<else>>
		<<if $activeSlave.amp == 0>>

			$pronounCap has healthy limbs so there is no need for prosthetics.
		<<else>>

			$pronounCap does not have a PLimb interface installed so you can't give $possessive any prosthetics.
		<</if>>
	<</if>>

<<case 1>>
	<<set $temp to 0, $nextButton to "Continue", $nextLink to "cyberConfig">>
	<<if $activeSlave.amp is -1>><<set $stockpile.basicPLimb += 1>>
	<<elseif $activeSlave.amp is -2>><<set $stockpile.advSexPLimb += 1>>
	<<elseif $activeSlave.amp is -3>><<set $stockpile.advGracePLimb += 1>>
	<<elseif $activeSlave.amp is -4>><<set $stockpile.advCombatPLimb += 1>>
	<<elseif $activeSlave.amp is -5>><<set $stockpile.cyberneticPLimb += 1>>
	<</if>>
	<<set $activeSlave.amp to 1>>
	Due to built-in safeties it is necessary to remove each limb separately, first releasing the lock and then waiting for automated seal release.

	<<if ($activeSlave.devotion > 20)>>
		You instruct $possessive to lie down on the table and proceed to remove $possessive limbs. $pronounCap <<if canSee($activeSlave)>>watches<<else>>listens<</if>> with interest as you work.<<if ($activeSlave.devotion > 50)>> As you remove the last limb $pronoun playfully wiggles $possessive stumps at you.<</if>>
	<<else>>
		You order $possessive to lie down on the table and proceed to remove $possessive limbs. $pronounCap <<if canSee($activeSlave)>>watches<<else>>listens<</if>> with a bitter expression as you work.
	<</if>>

<<case 2>>
	<<set $temp to 0, $nextButton to "Continue", $nextLink to "cyberConfig">>
	<<if $activeSlave.amp != 1>>You have $possessive lie down and<<else>>You<</if>> use speculum to keep $possessive eyes open while you disengage $possessive lenses remotely and swap them out with $possessive new $activeSlave.eyeColor lenses.
<<case 3>>
	<<set $temp to 0, $nextButton to "Continue", $nextLink to "cyberConfig">>
	<<if $activeSlave.eyes == 1>>
		$pronounCap blinks as $possessive vision returns.
	<<elseif $activeSlave.eyes == -1>>
		$pronounCap squints at you as $possessive vision becomes a blur.
	<<else>>
		$pronounCap has a panicked expression when $possessive vision suddenly goes out.
	<</if>>
<<case 4>>
	<<set $temp to 0, $nextButton to "Continue", $nextLink to "cyberConfig">>
	Attaching $possessive limbs is a simple procedure, simply push connector on each limb into the socket in $possessive implants until lock engages.<<if $activeSlave.PLimb == 2>> $pronounCap jumps a bit as limbs connect to $possessive nerves.<</if>> When you are done, $pronoun sits up and
	<<if $activeSlave.amp == -2>>experimentally engages the vibe function in $possessive fingers.
	<<elseif $activeSlave.amp == -3>>runs a hand over the smooth skin of $possessive new legs.
	<<elseif $activeSlave.amp == -4>>engages and disengages the blade in $possessive forearm.
	<<elseif $activeSlave.amp == -5>>experimentally flexes the muscles in $possessive artificial arm.
	<<else>>catches $possessive balance.
	<</if>>
<</switch>>


code:
:: Research Lab [nobr]

<<switch $temp>>

<<case 0>>

	<<set $nextButton = "Back", $nextLink = "Manage Arcology">>

	//You switch one of the screens in your office to the feed from research facility.//

[[Decommission facility|Manage Arcology][$researchLab.built = "false",$researchLab.hired = 0,$helots += $researchLab.menials,$researchLab.menials = 0,$researchLab.research = "none",$researchLab.manufacture = "none",$researchLab.productionTime = 0]] //Warning! This will cancel all projects, remove all staff and shut down this facility.//

[[Purchase cybernetics|Research Lab][$temp = 1]]

	__Personnel__

	<<if $researchLab.aiModule == 1>>
		[[Buy and install research module|Research Lab][$cash -= 35000, $researchLab.aiModule = 2]] //This module enables your assistant to assist staff and direct menials assigned to your research facility, increasing efficiency. It costs ¤35000//

	<</if>>
	<<if ($researchLab.hired + $researchLab.menials) == 0>>
		//Facility is currently unstaffed.//
	<<else>>
		//You have
		<<if $researchLab.hired == 1>>
			one scientist
		<<elseif $researchLab.hired > 1>>
			$researchLab.hired scientists
		<</if>>
		<<if $researchLab.menials != 0>> and<</if>>
		<<if $researchLab.menials == 0>>
		<<elseif $researchLab.menials == 1>>
			one menial slave
		<<elseif $researchLab.menials > 1>>
			$researchLab.menials menials
		<</if>>
		working in your lab.
			<<if $researchLab.aiModule != 0>>Occasionally you hear voice of your assistant as it helps direct and organize work.<</if>>//
	<</if>>

	 Currently, this facility can employ $researchLab.maxSpace people.
	<<if $researchLab.maxSpace == 5>>
		[[Expand facility|Research Lab][$cash -= Math.trunc(5000*$upgradeMultiplierArcology), $researchLab.maxSpace = 10]]
		//Costs ¤<<print Math.trunc(5000*$upgradeMultiplierArcology)>>//
	<<elseif $researchLab.maxSpace == 10>>
		[[Expand facility|Research Lab][$cash -= Math.trunc(5000*$upgradeMultiplierArcology), $researchLab.maxSpace = 20]]
		//Costs ¤<<print Math.trunc(10000*$upgradeMultiplierArcology)>>//
	<<elseif $researchLab.maxSpace == 20>>
		[[Expand facility|Research Lab][$cash -= Math.trunc(5000*$upgradeMultiplierArcology), $researchLab.maxSpace = 30]]
		//Costs ¤<<print Math.trunc(15000*$upgradeMultiplierArcology)>>//
	<<elseif $researchLab.maxSpace == 30>>
		[[Expand facility|Research Lab][$cash -= Math.trunc(5000*$upgradeMultiplierArcology), $researchLab.maxSpace = 40]]
		//Costs ¤<<print Math.trunc(20000*$upgradeMultiplierArcology)>>//
	<<elseif $researchLab.maxSpace == 40>>
		[[Expand facility|Research Lab][$cash -= Math.trunc(5000*$upgradeMultiplierArcology), $researchLab.maxSpace = 50]]
		//Costs ¤<<print Math.trunc(25000*$upgradeMultiplierArcology)>>//
	<<elseif $researchLab.maxSpace == 50>>
		//Facility is fully expanded.//
	<</if>>

	<<if ($researchLab.hired + $researchLab.menials) < $researchLab.maxSpace>>
		Hire [[x1|Research Lab][$researchLab.hired += 1,$cash -= 5000]]
		<<if ($researchLab.maxSpace - ($researchLab.hired + $researchLab.menials)) >= 5>>
			| [[x5|Research Lab][$researchLab.hired += 5,$cash -= 25000]]
		<</if>>
		<<if ($researchLab.maxSpace - ($researchLab.hired + $researchLab.menials)) >= 10>>
			| [[x10|Research Lab][$researchLab.hired += 10,$cash -= 50000]]
		<</if>>
		scientists.

		    //Each scientist will require an initial fee of ¤5000 and incur ¤300 weekly.//

		 <<if $helots > 0>>
			Assign [[x1|Research Lab][$researchLab.menials += 1,$helots -= 1]]
			<<if ($researchLab.maxSpace - ($researchLab.hired + $researchLab.menials)) >= 5 && $helots >= 5>>
				| [[x5|Research Lab][$researchLab.menials += 5,$helots -= 5]]
			<</if>>
			<<if ($researchLab.maxSpace - ($researchLab.hired + $researchLab.menials)) >= 10 && $helots >= 10>>
				| [[x10|Research Lab][$researchLab.menials += 10,$helots -= 10]]
			<</if>>
			  menial slaves to lab.

		<<else>>You do not own any <<if $researchLab.menials != 0>>unassigned <</if>>menial slaves.

		<</if>>
		   //Using menial slaves is much cheaper than hiring scientists, but they are less effective. ¤100 per slave each week.//

	<<else>>
		   //Facility is fully staffed.//
	<</if>>

	<<if $researchLab.hired > 0>>
		Fire [[x1|Research Lab][$researchLab.hired -= 1]]
		<<if $researchLab.hired >= 5>>
			| [[x5|Research Lab][$researchLab.hired -= 5]]
		<</if>>
		<<if $researchLab.hired >= 10>>
			| [[x10|Research Lab][$researchLab.hired -= 10]]
		<</if>>
		| [[All|Research Lab][$researchLab.hired = 0]]
		scientists.
	<</if>>
	<<if $researchLab.menials > 0>>

		Dismiss [[x1|Research Lab][$researchLab.menials -= 1,$helots += 1]]
		<<if $researchLab.menials >= 5>>
			| [[x5|Research Lab][$researchLab.menials -= 5,$helots += 5]]
		<</if>>
		<<if $researchLab.menials >= 10>>
			| [[x10|Research Lab][$researchLab.menials -= 10,$helots += 10]]
		<</if>>
		| [[All|Research Lab][$helots += $researchLab.menials,$researchLab.menials = 0]]
		menial slaves.
	<</if>>

	__Research__

	<<if $researchLab.research != "none">>
		//Your lab staff is currently researching @@.yellow;$researchLab.research@@.//

		[[Cancel Research|Research Lab][$researchLab.productionTime = 0,$researchLab.research = "none"]]
	<<elseif $researchLab.manufacture != "none">>
		//Your lab staff is currently busy with other projects.//
	<<else>>
		//No research active.//

		<<if $researchLab.basicPLimbInterface == 0 && $stockpile.basicPLimbInterface > 0>>

			[[Reverse engineer basic prosthetics interface|Research Lab][$researchLab.productionTime = 80,$researchLab.research = "Basic prosthetics interface"]]
		<</if>>
		<<if $researchLab.basicPLimbInterface != 0 && $researchLab.advPLimbInterface == 0 && $stockpile.advPLimbInterface > 0>>

			[[Reverse engineer advanced prosthetics interface|Research Lab][$researchLab.productionTime = 120,$researchLab.research = "Advanced prosthetics interface"]]
		<</if>>
		<<if $researchLab.basicPLimb == 0 && $stockpile.basicPLimb > 0>>

			[[Reverse engineer basic prosthetic limbs|Research Lab][$researchLab.productionTime = 80,$researchLab.research = "Basic prosthetic limbs"]]
		<</if>>
		<<if $researchLab.advSexPLimb == 0 && $stockpile.advSexPLimb > 0>>

			[[Reverse engineer advanced sex limbs|Research Lab][$stockpile.advSexPLimb -= 1,$researchLab.productionTime = 100,$researchLab.research = "Advanced sex limbs"]]
		<</if>>
		<<if $researchLab.advGracePLimb == 0 && $stockpile.advGracePLimb > 0>>

			[[Reverse engineer advanced beauty limbs|Research Lab][$stockpile.advGracePLimb -= 1,$researchLab.productionTime = 100,$researchLab.research = "Advanced beauty limbs"]]
		<</if>>
		<<if $researchLab.advCombatPLimb == 0 && $stockpile.advCombatPLimb > 0>>

			[[Reverse engineer advanced combat limbs|Research Lab][$stockpile.advCombatPLimb -= 1,$researchLab.productionTime = 100,$researchLab.research = "Advanced combat limbs"]]
		<</if>>
		<<if $researchLab.cyberneticPLimb == 0 && $researchLab.advPLimbInterface == 1 && ($researchLab.advSexPLimb + $researchLab.advGracePLimb + $researchLab.advCombatPLimb) >= 1>>

			[[Develop cybernetic limbs|Research Lab][$researchLab.productionTime = 300,$researchLab.research = "Cybernetic limbs"]]
		<</if>>
		<<if $researchLab.ocularImplant == 0 && $stockpile.ocularImplant > 0>>

			[[Reverse engineer ocular implants|Research Lab][$stockpile.ocularImplant -= 1,$researchLab.productionTime = 100,$researchLab.research = "Ocular implants"]]
		<</if>>
		<<if $researchLab.erectileImplant == 0 && $stockpile.erectileImplant > 0>>

			[[Reverse engineer erectile implant|Research Lab][$stockpile.erectileImplant -= 1,$researchLab.productionTime = 100,$researchLab.research = "Erectile implant"]]
		<</if>>
	<</if>>

	__Manufacture__

	<<if $researchLab.research != "none">>
		//Your lab staff is currently busy with research.//
	<<elseif $researchLab.manufacture != "none">>
		//Your lab staff is currently building @@.yellow;$researchLab.manufacture@@.//
		[[Cancel Project|Research Lab][$researchLab.productionTime = 0,$researchLab.manufacture = "none"]]
	<<else>>
		//No projects currently active.//

		<<if $researchLab.basicPLimbInterface == 1>>

			[[Build basic prosthetics interface|Research Lab][$researchLab.productionTime = 50,$researchLab.manufacture = "Basic prosthetics interface"]] // Currently have: $stockpile.basicPLimbInterface//
		<</if>>
		<<if $researchLab.advPLimbInterface == 1>>

			[[Build advanced prosthetics interface|Research Lab][$researchLab.productionTime = 80,$researchLab.manufacture = "Advanced prosthetics interface"]] // Currently have: $stockpile.advPLimbInterface//
		<</if>>
		<<if $researchLab.basicPLimb == 1>>

			[[Build basic prosthetic limbs|Research Lab][$researchLab.productionTime = 20,$researchLab.manufacture = "Basic prosthetic limbs"]] // Currently have: $stockpile.basicPLimb//
		<</if>>
		<<if $researchLab.advSexPLimb == 1>>

			[[Build advanced sex limbs|Research Lab][$researchLab.productionTime = 100,$researchLab.manufacture = "Advanced sex limbs"]] // Currently have: $stockpile.advSexPLimb//
		<</if>>
		<<if $researchLab.advGracePLimb == 1>>

			[[Build advanced beauty limbs|Research Lab][$researchLab.productionTime = 100,$researchLab.manufacture = "Advanced beauty limbs"]] // Currently have: $stockpile.advGracePLimb//
		<</if>>
		<<if $researchLab.advCombatPLimb == 1>>

			[[Build advanced combat limbs|Research Lab][$researchLab.productionTime = 100,$researchLab.manufacture = "Advanced combat limbs"]] // Currently have: $stockpile.advCombatPLimb//
		<</if>>
		<<if $researchLab.cyberneticPLimb == 1>>

			[[Build cybernetic limbs|Research Lab][$researchLab.productionTime = 150,$researchLab.manufacture = "Cybernetic limbs"]] // Currently have: $stockpile.cyberneticPLimb//
		<</if>>
		<<if $researchLab.ocularImplant == 1>>

			[[Build ocular implants|Research Lab][$researchLab.productionTime = 80,$researchLab.manufacture = "Ocular implants"]] // Currently have: $stockpile.ocularImplant//
		<</if>>
		<<if $researchLab.erectileImplant == 1>>

			[[Build Erectile implant|Research Lab][$researchLab.productionTime = 50,$researchLab.manufacture = "Erectile implant"]] // Costs ¤25000. Currently have: $stockpile.erectileImplant//
		<</if>>
	<</if>>

<<case 1>>
	<<set $temp = 0>>
	<<set $nextLink = "Research Lab">>
	//You contact representative of one of the larger cybernetics companies and inquired about their products.
 This is what they have to offer://

	__Hardware__

	[[Basic prosthetics interface|Research Lab][$temp = 1, $cash -= 25000, $stockpile.basicPLimbInterface += 1]] //Costs ¤25000. Currently have: $stockpile.basicPLimbInterface//

	[[Advanced prosthetics interface|Research Lab][$temp = 1, $cash -= 60000, $stockpile.advPLimbInterface += 1]] //Costs ¤60000. Currently have: $stockpile.advPLimbInterface//

	[[Basic prosthetic limbs|Research Lab][$temp = 1, $cash -= 15000, $stockpile.basicPLimb += 1]] //Costs ¤15000. Currently have: $stockpile.basicPLimb//

	[[Advanced sex limbs|Research Lab][$temp = 1, $cash -= 30000, $stockpile.advSexPLimb += 1]] //Costs ¤30000. Currently have: $stockpile.advSexPLimb//

	[[Advanced beauty limbs|Research Lab][$temp = 1, $cash -= 30000, $stockpile.advGracePLimb += 1]] //Costs ¤30000. Currently have: $stockpile.advGracePLimb//

	[[Advanced combat limbs|Research Lab][$temp = 1, $cash -= 30000, $stockpile.advCombatPLimb += 1]] //Costs ¤30000. Currently have: $stockpile.advCombatPLimb//

	[[Ocular implants|Research Lab][$temp = 1, $cash -= 35000, $stockpile.ocularImplant += 1]] //Costs ¤35000. Currently have: $stockpile.ocularImplant//

	/*[[Erectile implant|Research Lab][$temp = 1, $cash -= 25000, $stockpile.erectileImplant += 1]] //Costs ¤25000. Currently have: $stockpile.erectileImplant//*/

__Schematics__

	<<if $researchLab.basicPLimbInterface != 1 && $researchLab.research != "Basic prosthetics interface">>
		[[Basic prosthetics interface|Research Lab][$temp = 1, $cash -= 85000, $researchLab.basicPLimbInterface = 1]] //Costs ¤85000.//

	<</if>>
	<<if $researchLab.advPLimbInterface != 1 && $researchLab.research != "Advanced prosthetics interface">>
	[[Advanced prosthetics interface|Research Lab][$temp = 1, $cash -= 260000, $researchLab.advPLimbInterface = 1]] //Costs ¤260000.//

	<</if>>
	<<if $researchLab.basicPLimb != 1 && $researchLab.research != "Basic prosthetic limbs">>
	[[Basic prosthetic limbs|Research Lab][$temp = 1, $cash -= 60000, $researchLab.basicPLimb = 1]] //Costs ¤60000.//

	<</if>>
	<<if $researchLab.advSexPLimb != 1 && $researchLab.research != "Advanced sex limbs">>
	[[Advanced sex limbs|Research Lab][$temp = 1, $cash -= 120000, $researchLab.advSexPLimb = 1]] //Costs ¤120000.//

	<</if>>
	<<if $researchLab.advGracePLimb != 1 && $researchLab.research != "Advanced beauty limbs">>
	[[Advanced beauty limbs|Research Lab][$temp = 1, $cash -= 120000, $researchLab.advGracePLimb = 1]] //Costs ¤120000.//

	<</if>>
	<<if $researchLab.advCombatPLimb != 1 && $researchLab.research != "Advanced combat limbs">>
	[[Advanced combat limbs|Research Lab][$temp = 1, $cash -= 120000, $researchLab.advCombatPLimb = 1]] //Costs ¤120000.//

	<</if>>
	<<if $researchLab.ocularImplant != 1 && $researchLab.research != "Ocular implants">>
	[[Ocular implants|Research Lab][$temp = 1, $cash -= 160000, $researchLab.ocularImplant = 1]] //Costs ¤160000.//

	<</if>>
	/*<<if $researchLab.erectileImplant != 1 && $researchLab.research != "Erectile implant">>
	[[Erectile implant|Research Lab][$temp = 1, $cash -= 95000, $researchLab.erectileImplant = 1]] //Costs ¤95000.//
	<</if>>*/

<</switch>>

repo_name: darkcy/fc

license: mit


code:
:: accordionJS.tw [script]
/* Accordion 000-250-006 */

/*
 * We're making changes to the DOM, so we need to make them *after* everything has been generated
 * Sticking this all in postdisplay calls reduces the chance of there being a timing conflict
 *   with other scripts, since anything poking the DOM here will be done last
 *
 * Dev Note: The accordion mod should be able to turn *anything* into an accordion. This iteration
 *   is configured tightly for the end of week report runs, but it shouldn't be that hard to adapt for
 *   other uses, like character bios. For now, I'll see what other extra-long passages of cosmetic text
 *   might benefit.
 *
 * 000-250-006 03092017
*/

postdisplay["doAccordionSet"] = function (content) {
    if (variables().useAccordion == 1) {
        Array.prototype.slice.call(document.querySelectorAll('.macro-include'))
        .forEach(function(element) {
            element.classList.add('accHidden');
        });
    }
}

postdisplay["doAccordion"] = function (content) {
    var acc = document.getElementsByClassName("accordion");
    var i;

    for (i = 0; i < acc.length; i += 1) {
        acc[i].onclick = function () {
            this.classList.toggle("active");
            var panel = this.nextElementSibling;
            if (panel.style.maxHeight) {
                panel.style.maxHeight = null;
            } else {
                panel.style.maxHeight = 2*panel.scrollHeight + "px";
            }
        };
    }
};


code:
:: EconomyJS [script]

window.LivingRule = Object.freeze({LUXURIOUS: 'luxurious', NORMAL: 'normal', SPARSE: 'sparse'});
window.Job = Object.freeze({
	DAIRY: 'work in the dairy', MASTER_SUITE: 'serve in the master suite', CONCUBINE: 'be your concubine',
	BABY_FACTORY: 'labor in the production line', BROTHEL: 'work in the brothel', ARCADE: 'be confined in the arcade',
	SERVANT: 'work as a servant', SERVER: 'be a servant', CLUB: 'serve in the club'});
window.PersonalAttention = Object.freeze({TRADE: 'trading', WAR: 'warfare', SLAVEING: 'slaving', ENGINEERING: 'engineering', MEDICINE: 'medicine', MAID: 'upkeep'});

window.getCost = function(array) {
	var rulesCost = State.variables.rulesCost;
	var foodCost = State.variables.foodCost;
	var drugsCost = State.variables.drugsCost;
	var facilityCost = State.variables.facilityCost;
	var economy = State.variables.economy;
	var brothel = State.variables.brothel;
	var arcade = State.variables.arcade;
	var club = State.variables.club;
	var dairy = State.variables.dairy;
	var servantsQuarters = State.variables.servantsQuarters;
	var incubator = State.variables.incubator;
	var mercCosts = State.variables.mercenaries * 2000;
	var policyCost = State.variables.policyCost;
	var costs = (State.variables.brothel * facilityCost)
		+ (State.variables.arcade * facilityCost * 0.5)
		+ (State.variables.club * facilityCost)
		+ (State.variables.dairy * facilityCost)
		+ (State.variables.incubator * facilityCost * 10);
	var secExpCost = 0;
	var soldierMod = 0;

	//facility expenses
	costs += (0.1 * State.variables.brothelUpgradeDrugs * brothel * facilityCost)
	+ (0.2 * State.variables.arcadeUpgradeInjectors * arcade * facilityCost)
	+ (0.2 * State.variables.arcadeUpgradeCollectors * arcade * facilityCost)
	+ (0.2 * State.variables.clubUpgradePDAs * club * facilityCost)
	+ (0.2 * State.variables.dairyFeedersUpgrade * dairy * facilityCost)
	+ (0.1 * State.variables.dairyPregUpgrade * dairy * facilityCost)
	+ (0.2 * State.variables.dairyStimulatorsUpgrade * facilityCost)
	+ (0.2 * State.variables.servantsQuartersUpgradeMonitoring *  servantsQuarters * facilityCost)
	+ (0.2 * State.variables.incubatorUpgradeWeight * incubator * facilityCost)
	+ (0.2 * State.variables.incubatorUpgradeMuscles * incubator * facilityCost)
	+ (0.2 * State.variables.incubatorUpgradeReproduction * incubator * facilityCost)
	+ (0.2 * State.variables.incubatorUpgradeGrowthStims * incubator * facilityCost)
	+ (0.5 * State.variables.incubatorUpgradeSpeed * incubator * facilityCost);

	if(dairy > 0) {
		costs += ((State.variables.bioreactorsXY + State.variables.bioreactorsXX + State.variables.bioreactorsHerm + State.variables.bioreactorsBarren) * 100);
	}
	if(brothel > 0) {
		costs += State.variables.brothelAdsSpending;
	}
	if(club > 0) {
		costs += State.variables.clubAdsSpending;
	}
	if(incubator > 0) {
		costs += ((State.variables.incubatorWeightSetting + State.variables.incubatorMusclesSetting + State.variables.incubatorReproductionSetting + State.variables.incubatorGrowthStimsSetting) * 500);
	}
	if(State.variables.masterSuitePregnancySlaveLuxuries === 1) {
		costs += 500;
	}
	if(State.variables.masterSuitePregnancyFertilitySupplements === 1) {
		costs += 1000;
	}

	//security expansion
	if(State.variables.secExp == 1) {
		if(State.variables.edictsUpkeep > 0) {
			secExpCost += State.variables.edictsUpkeep;
		}
		if(State.variables.SFSupportUpkeep > 0) {
			secExpCost += State.variables.SFSupportUpkeep;
		}
		if(State.variables.propHub > 0) {
			secExpCost += State.variables.propHubUpkeep;
		}
		if(State.variables.secHQ > 0) {
			secExpCost += State.variables.secHQUpkeep + 20 * state.variables.secHelots;
		}
		if(State.variables.secBarracks > 0) {
			secExpCost += State.variables.secBarracksUpkeep;
		}
		if(State.variables.riotCenter > 0) {
			secExpCost += State.variables.riotUpkeep;
		}
		if(State.variables.soldierWages == 0) {
			soldierMod = 1;
		}
		else if(State.variables.soldierWages == 1) {
			soldierMod = 1.5;
		}
		else {
			soldierMod = 2;
		}
		if (State.variables.militiaUnits != null) {
			for (var i = 0; i < State.variables.militiaUnits.length; i++) {
				if( !(State.variables.militiaUnits[i] === null) ){
					secExpCost += State.variables.militiaUnits[i].troops * State.variables.soldierUpkeep * soldierMod;
				}
			}
		}
		if (State.variables.slaveUnits != null) {
			for (var i = 0; i < State.variables.slaveUnits.length; i++) {
				if( !( State.variables.slaveUnits[i] === null) ){
					secExpCost +=  State.variables.slaveUnits[i].troops * State.variables.soldierUpkeep * 0.5 * soldierMod;
				}
			}
		}
		if (State.variables.mercUnits != null) {
			for (var i = 0; i < State.variables.mercUnits.length; i++) {
				if( !(State.variables.mercUnits[i] === null) ){
					secExpCost += State.variables.mercUnits[i].troops * State.variables.soldierUpkeep * 1.5 * soldierMod;
				}
			}
		}
	}

	//general arcology costs
	costs += (State.variables.girls * (250 + (economy * 500)));

	if(State.variables.arcologies[0].FSRepopulationFocusLaw === 1 && State.variables.PC.pregKnown == 1) {
		costs -= 500;
	}

	costs += State.variables.citizenOrphanageTotal * 100;
	costs += State.variables.privateOrphanageTotal * 500;
	if(State.variables.breederOrphanageTotal > 0) {
		costs += 50;
	}

	if(State.variables.peacekeepers !== 0 && State.variables.peacekeepers.undermining !== 0) {
		costs += State.variables.peacekeepers.undermining;
	}

	if(State.variables.mercenaries > 0) {
		if(State.variables.barracks) {
			mercCosts *= 0.5;
		}
		if((State.variables.PC.warfare >= 100) || (State.variables.PC.career === 'arcology owner')) {
			mercCosts *= 0.5;
		}
		costs += mercCosts;
	}

	costs += State.variables.FSSpending;

	//slave expenses
	for (var slave of array) {
		costs += getSlaveCost(slave);
		if(slave.assignment === Job.SERVANT || slave.assignment === Job.SERVER) {
			if(slave.trust < -20) {
				costs -= rulesCost * 4;
			} else if(slave.devotion < -20) {
				costs -= rulesCost * 2;
			} else if(slave.devotion <= 20) {
				costs -= rulesCost * 3;
			} else if(slave.devotion <= 50) {
				costs -= rulesCost * 4;
			} else {
				costs -= rulesCost * 5;
			}
			if(slave.fetish === 'submissive') {
				costs -= rulesCost;
			} else if(slave.fetish === 'dom') {
				costs += rulesCost;
			}
			if(slave.relationship < -1) {
				costs -= rulesCost;
			}
			if(slave.energy < 20) {
				costs -= rulesCost;
			} else if(slave.energy < 40) {
				costs -= rulesCost / 2;
			}
			if(slave.lactation > 0) {
				costs -= 25;
			}
		}
	}

	// policy and other expenses
	if(State.variables.Recruiter !== 0) {
		costs += 250;
	}
	if(State.variables.TSS.schoolPresent === 1) {
		costs += 1000;
	}
	if(State.variables.GRI.schoolPresent === 1) {
		costs += 1000;
	}
	if(State.variables.SCP.schoolPresent === 1) {
		costs += 1000;
	}
	if(State.variables.LDE.schoolPresent === 1) {
		costs += 1000;
	}
	if(State.variables.TGA.schoolPresent === 1) {
		costs += 1000;
	}
	if(State.variables.TCR.schoolPresent === 1) {
		costs += 1000;
	}
	if((State.variables.TFS.schoolPresent === 1) && ((State.variables.PC.dick === 0) || (State.variables.PC.vagina === 0) || (State.variables.PC.boobs === 0))) {
		costs += 1000;
	}
	if(State.variables.TSS.subsidize !== 0) {
		costs += 1000;
	}
	if(State.variables.GRI.subsidize !== 0) {
		costs += 1000;
	}
	if(State.variables.SCP.subsidize !== 0) {
		costs += 1000;
	}
	if(State.variables.LDE.subsidize !== 0) {
		costs += 1000;
	}
	if(State.variables.TGA.subsidize !== 0) {
		costs += 1000;
	}
	if(State.variables.TCR.subsidize !== 0) {
		costs += 1000;
	}
	if(State.variables.TFS.subsidize !== 0) {
		costs += 1000;
	}

	if(State.variables.alwaysSubsidizeGrowth === 1) {
		costs += policyCost;
	}
	if(State.variables.alwaysSubsidizeRep === 1) {
		costs += policyCost;
	}
	if(State.variables.RegularParties === 1) {
		costs += policyCost;
	}

	if(State.variables.ProImmigrationCash === 1) {
		costs += policyCost;
	}
	if(State.variables.AntiEnslavementCash === 1) {
		costs += policyCost;
	}

	if(State.variables.CoursingAssociation === 1) {
		costs += 1000;
	}

	if(State.variables.personalAttention === PersonalAttention.TRADE) {
		costs += 10000;
	} else if(State.variables.personalAttention === PersonalAttention.WAR) {
		costs += 10000;
	} else if(State.variables.personalAttention === PersonalAttention.SLAVING) {
		costs += 10000;
	} else if(State.variables.personalAttention === PersonalAttention.ENGINEERING) {
		costs += 10000;
	} else if(State.variables.personalAttention === PersonalAttention.MEDICINE) {
		costs += 10000;
	}

	if(State.variables.cyberMod !== 0 && State.variables.researchLab.built === 'true') {
		costs += ((100 * State.variables.researchLab.maxSpace) + (300 * State.variables.researchLab.hired) + (100 * State.variables.researchLab.hired));
	}

	//player expenses

	if(State.variables.PC.preg === -1) {
		costs += 25;
	} else if(State.variables.PC.fertDrugs === 1) {
		costs += 50;
	} else if(State.variables.PC.preg >= 16) {
		costs += 100;
	}

	if(State.variables.PC.career === 'servant') {
		if(State.variables.personalAttention === PersonalAttention.MAID) {
			if(State.variables.PC.belly >= 5000) {
				costs *= 0.80;
			} else {
				costs *= 0.75;
			}
		} else {
			costs *= 0.9;
		}
	}

	if(State.variables.secExp == 1) {
		if(State.variables.terrain === 'oceanic' || State.variables.terrain === 'marine') {
			if(State.variables.docks > 0) {
				costs *= (1 - State.variables.docks * 0.05);
			}
		} else if(State.variables.railway > 0) {
			costs *= (1 - State.variables.railway * 0.05);
		}
	}

	// clean up
	if(costs < 0) {
		costs = 0;
	} else {
		costs = Math.trunc(costs);
	}

	return costs;
}

window.getSlaveCost = function(s) {
	if(!s) { return 0; }
	// Data duplicated from Cost Report
	var cost = 0;
	var rulesCost = State.variables.rulesCost;
	var foodCost = State.variables.foodCost;
	var drugsCost = State.variables.drugsCost;

	// Living expenses
	if((s.assignment !== Job.DAIRY || State.variables.dairyRestraintsSetting < 2) && (s.assignment !== Job.ARCADE)) {
		if(s.livingRules === LivingRule.LUXURIOUS) {
			cost += rulesCost * (s.relationship >= 4 ? 3 : 4);
		} else if(s.livingRules == LivingRule.NORMAL) {
			cost += rulesCost * 2;
		} else {
			cost += rulesCost;
		}
	} else {
		cost += rulesCost * .75;
	}

	// Food
	cost += foodCost * 4;
	switch(s.diet) {
		case 'fattening': case 'muscle building':
			cost += foodCost;
			break;
		case 'restricted': case 'slimming':
			cost -= foodCost;
			break;
	}
	if(s.weight > 130) {
		cost += foodCost * 2;
	} else if(s.weight > 50) {
		cost += foodCost;
	} else if(s.weight < -50) {
		cost -= foodCost;
	}
	if(s.drugs === 'appetite suppressors') {
		cost -= foodCost;
	}
	if(s.lactation > 0) {
		cost += foodCost * s.lactation * (1 + Math.trunc(s.boobs/10000));
	}
	if(s.preg > 5) {
		if(s.assignment === Job.DAIRY && State.variables.dairyFeedersSetting > 0) {
			// Extra feeding costs to support pregnancy are covered by dairy feeders.
			// TODO: Include them here anyway?
		} else if((s.assignment === Job.MASTER_SUITE || s.assignment === Job.CONCUBINE)
			&& State.variables.masterSuiteUpgradePregnancy === 1) {
			// Extra feeding costs to support pregnancy are covered by master suite luxuries.
			// TODO: Include them here anyway?
		} else {
			cost += foodCost * s.pregType * (s.pregControl === 'speed up' ? 3 : 1);
		}
	}
	if(s.diet === 'XX' || s.diet === 'XY') {
		cost += 25;
	} else if(s.diet === 'cleansing') {
		cost += 50;
	} else if(s.diet === 'XXY') {
		cost += 75;
	}

	// Accessibility costs
	if(State.variables.boobAccessibility !== 1 && s.boobs > 20000
		&& (s.assignment != Job.DAIRY || State.variables.dairyRestraintsSetting < 2) && (s.assignment != Job.ARCADE)) {
		cost += 50;
	}
	if(State.variables.pregAccessibility !== 1
		&& ((s.belly >= 60000) || (s.preg > 20 && s.pregType >= 10) || (s.preg > 10 && s.pregType >= 20) || s.bellyImplant >= 10000)
		&& s.assignment != Job.BABY_FACTORY && (s.assignment != Job.DAIRY || State.variables.dairyRestraintsSetting < 2) && (s.assignment != Job.ARCADE)) {
		cost += 100;
	}
	if(State.variables.dickAccessibility != 1 && s.dick > 45 && (s.assignment != Job.DAIRY || State.variables.dairyRestraintsSetting < 2) && (s.assignment != Job.ARCADE)) {
		cost += 50;
	}
	if(State.variables.ballsAccessibility != 1 && s.balls > 90 && (s.assignment != Job.DAIRY || State.variables.dairyRestraintsSetting < 2) && (s.assignment != Job.ARCADE)) {
		cost += 50;
	}
	if(State.variables.buttAccessibility != 1 && s.butt > 15 && (s.assignment != Job.DAIRY || State.variables.dairyRestraintsSetting < 2) && (s.assignment != Job.ARCADE)) {
		cost += 50;
	}
	if(!canSee(s) && (s.assignment != Job.DAIRY || State.variables.dairyRestraintsSetting < 2) && (s.assignment != Job.ARCADE)) {
		cost += 50;
	} else if(s.eyes <= -1 &&	s.eyewear !== 'corrective glasses' && s.eyewear !== 'corrective contacts') {
		cost += 25;
	} else if(s.eyewear === 'blurring glasses' || s.eyewear === 'blurring contacts') {
		cost += 25;
	}
	if((s.assignment !== Job.DAIRY || State.variables.dairyRestraintsSetting < 2) && (s.assignment !== Job.ARCADE)) {
		if(s.amp !== 0) {
			if(s.amp === 1) {
				cost += rulesCost;
			} else {
				cost += rulesCost / 2;
			}
		} else if(!canWalk(s)) {
			cost += rulesCost;
		}
	}

	// Maintenance
	if(s.boobs > 10000 && s.boobsImplantType === 1) {
		cost += 50;
	}
	if(s.butt > 10 && s.buttImplantType === 1) {
		cost += 50;
	}
	if((s.assignment !== Job.DAIRY || State.variables.dairyRestraintsSetting < 2) && (s.assignment !== Job.ARCADE)) {
		if(s.preg > 35 && State.variables.universalRulesBirthing === 1) {
			cost += 50;
		}
	}

	// Retirement account
	if(State.variables.CitizenRetirement === 1) {
		cost += 250;
	}

	// Enemas
	if(s.inflation === 3) {
		switch(s.inflationType) {
		case 'water':
			cost += 100;
			break;
		case 'food':
			cost += (foodCost * 4);
			break;
		case 'curative': case 'aphrodisiac': case 'tightener':
			cost += (100 + (drugsCost * 2));
			break;
		}
	} else if(s.inflation === 2) {
		switch(s.inflationType) {
		case 'water':
			cost += 50;
			break;
		case 'food':
			cost += (foodCost * 2);
			break;
		case 'curative': case 'aphrodisiac': case 'tightener':
			cost += (50 + (drugsCost * 2));
			break;
		}
	} else if(s.inflation === 1) {
		switch(s.inflationType) {
		case 'water':
			cost += 25;
			break;
		case 'food':
			cost += (foodCost);
			break;
		case 'curative': case 'aphrodisiac': case 'tightener':
			cost += (25 + (drugsCost * 2));
			break;
		}
	}

	// Drugs
	switch(s.drugs) {
	case 'anti-aging cream':
		cost += drugsCost * 10;
		break;
	case 'female hormone injections': case 'male hormone injections': case 'intensive breast injections':
	case 'intensive butt injections': case 'intensive penis enhancement': case 'intensive testicle enhancement':
	case 'intensive lip injections': case 'hyper breast injections': case 'hyper butt injections':
	case 'hyper penis enhancement': case 'hyper testicle enhancement': case 'hyper lip injections':
	case 'growth stimulants':
		cost += drugsCost * 5;
		break;
	case 'sag-B-gone':
		cost += drugsCost * .1;
		break;
	case 'no drugs': case 'none':
		break;
	default:
		cost += drugsCost * 2;
		break;
	}
	if(s.curatives > 0 && s.assignmentVisible === 1) {
		cost += drugsCost * s.curatives;
	}
	if(s.aphrodisiacs !== 0) {
		cost += drugsCost * Math.abs(s.aphrodisiacs);
	}
	if(s.hormones !== 0) {
		cost += (drugsCost * Math.abs(s.hormones) * 0.5);
	}
	if(s.preg === -1 && isFertile(s)) {
		cost += (drugsCost * 0.5);
	}

	// Promotion costs
	if(State.variables.studio === 1) {
		if(s.pornFameSpending > 0) {
			cost += s.pornFameSpending;
		}
	}

	if(isNaN(cost)) {
		throw new Error('Cost calculation for slave ' + s.slaveName + ' (' + s.ID + ') failed.');
	}
	return cost;
};

window.getSlaveStatisticData = function(s, facility) {
	if(!s || !facility) {
		// Base data, even without facility
		return {
			ID: s.ID, slaveName: s.slaveName, customLabel: s.customLabel,
			income: 0, adsIncome: 0, rep: 0, cost: getSlaveCost(s),
			customers: 0 /* brothel, club, ... */
		};
	}

	if(!facility.income) {
		facility.income = new Map();
	}

	if(facility.income.has(s.ID)) {
		return facility.income.get(s.ID);
	} else {
		var data = {
			ID: s.ID, slaveName: s.slaveName, customLabel: s.customLabel,
			income: 0, adsIncome: 0, rep: 0, cost: getSlaveCost(s),
			customers: 0 /* brothel, club, ... */
		};
		facility.income.set(s.ID, data);
		return data;
	}
};

window.initFacilityStatistics = function(facility) {
	facility = facility || {};
	facility.adsIncome = 0;
	facility.maintenance = 0;
	facility.totalIncome = 0;
	facility.totalExpenses = 0;
	facility.profit = 0;
	facility.income = new Map();
	return facility;
}

Number.prototype.toFixedHTML = function() {
	return Number.prototype.toFixed.apply(this, arguments).replace(/\.0+$/, '$&');
}


code:
:: Extended Family Mode JS [script]

/* see documentation for details /devNotes/Extended Family Mode Explained.txt */

window.sameDad = function(slave1, slave2){
	if ((slave1.father == slave2.father) && (slave1.father != 0 && slave1.father != -2)) {
		return true;
	} else {
		return false;
	}
};

window.sameMom = function(slave1, slave2){
	if ((slave1.mother == slave2.mother) && (slave1.mother != 0 && slave1.mother != -2)) {
		return true;
	} else {
		return false;
	}
};

// testtest catches the case if a mother is a father or a father a mother - thank you familyAnon, for this code
window.sameTParent = function(slave1, slave2) {
	if (slave1.mother == -1 && slave1.father == 1 && slave2.mother == -1 && slave2.father == -1) {
		return 1;
	} else if (slave1.mother == slave2.father && slave1.father == slave2.mother && slave1.mother != 0 && slave1.mother != -2 && slave1.father != 0 && slave1.father != -2 && slave1.mother != slave1.father) {
		return 2;
	} else if ((slave1.mother == slave2.father || slave1.father == slave2.mother) && slave1.mother != 0 && slave1.mother != -2 && slave2.mother != 0 && slave2.mother != -2 && slave1.mother != slave1.father) {
		return 3;
	} else {
		return 0;
	}
};

/*
window.sameTParent = function(slave1, slave2) {
	if ((slave1.mother == slave2.father || slave1.father == slave2.mother) && (slave1.mother != 0 && slave1.mother != -2 && slave1.father != 0 && slave1.father != -2)) {
		return true; //testtest catches the case if a mother is a father or a father a mother
	} else {
		return false;
	}
};
*/

window.areTwins = function(slave1, slave2) {
	if (sameDad(slave1, slave2) == false) {
		return false;
	} else if (sameMom(slave1, slave2) == false) {
		return false;
	} else if (slave1.actualAge == slave2.actualAge && slave1.birthWeek == slave2.birthWeek) {
		return true;
	} else {
		return false;
	}
};

window.areSisters = function(slave1, slave2) {
	if (slave1.ID == slave2.ID) {
		return 0; //you are not your own sister
	} else if ((slave1.father != 0 && slave1.father != -2) || (slave1.mother != 0 && slave1.mother != -2)) {
		if (sameDad(slave1, slave2) == false && sameMom(slave1, slave2) == true) {
			return 3; //half sisters
		} else if (sameDad(slave1, slave2) == true && sameMom(slave1, slave2) == false) {
			return 3; //half sisters
		} else if (sameTParent(slave1, slave2) == 3) {
			return 3; //half sisters
		} else if (sameTParent(slave1, slave2) == 2) {
			return 2; //sisters
		} else if (sameDad(slave1, slave2) == true && sameMom(slave1, slave2) == true) {
			if (slave1.actualAge == slave2.actualAge && slave1.birthWeek == slave2.birthWeek) {
				return 1; //twins
			} else {
				return 2; //sisters
			}
		} else {
			return 0; //not related
		}
	} else {
		return 0; //not related
	}
};

/*
//3 = half-sisters, 2 = sisters, 1 = twins, 0 = not related
window.areSisters = function(c1, c2) {
	if(c1.ID == c2.ID) {
		return 0;
	}
	var sib = 4;
	if(sameMom(c1, c2)) {
		sib -= 1;
	}
	if(sameDad(c1, c2)) {
	   sib -=1;
	}
	if (sib == 2 && c1.actualAge == c2.actualAge && c1.birthWeek == c2.birthWeek) {
		sib -= 1;
	}
	if(sib == 4) {
		return 0
	} else {
		return sib;
	}
}
*/

window.totalRelatives = function(slave) {
	var relatives = 0;
	if (slave.mother > 0) {
		relatives += 1
	}
	if (slave.father > 0) {
		relatives += 1
	}
	if (slave.daughters > 0) {
		relatives += slave.daughters
	}
	if (slave.sisters > 0) {
		relatives += slave.sisters
	}
	return relatives
};

window.mutualChildren = function(slave1, slave2, slaves) {
	return slaves.filter(function(s) { return s.ID != slave1.ID && s.ID != slave2.ID && s.mother > 0 && s.father > 0 && ((s.mother == slave1.ID && s.father == slave2.ID) || (s.mother == slave2.ID && s.father == slave1.ID)); }).length;
}

window.isSlaveAvailable = function(slave) {
	if (!slave) {
		return null;
	} else if (slave.assignment == "be your agent") {
		return false;
	} else if (slave.assignment == "live with your agent") {
		return false;
	} else if (slave.assignment == "be confined in the arcade") {
		return false;
	} else if (slave.assignment == "work in the dairy" && SugarCube.State.variables.DairyRestraintsSetting >= 2) {
		return false;
	} else {
		return true;
	}
};

if (typeof DairyRestraintsSetting == "undefined") {
	var DairyRestraintsSetting = {
		setSetting: function (setting) {
			setting = Number(setting)
			return setting
		}
	};
	// Raise namespace scope to Global.
	window.DairyRestraintsSetting = DairyRestraintsSetting;
};

/* OLD
window.randomRelatedSlave = function(slave, filterFunction) {
  if(!slave || !SugarCube) { return undefined; }
  if(typeof filterFunction !==  'function') { filterFunction = function(s, index, array) { return true; }; }
  return SugarCube.State.variables.slaves.filter(filterFunction).shuffle().find(function(s, index, array) {return areSisters(slave, s) || s.mother == slave.ID || s.father == slave.ID || slave.ID == s.mother || slave.ID == s.father; })
}
*/

window.randomRelatedSlave = function(slave, filterFunction) {
  if(!slave || !SugarCube) { return undefined; }
  if(typeof filterFunction !== 'function') {
    filterFunction = function(s, index, array) { return true; };
  }
  var arr = SugarCube.State.variables.slaves.filter(filterFunction)
  arr.shuffle()
  return arr.find(function(s, index, array) {
    return areSisters(slave, s)
      || slave.ID === s.mother
      || slave.ID === s.father
      || s.ID === slave.mother
      || s.ID === slave.father;
  })
}

window.randomRelatedAvailableSlave = function(slave) {
	return randomRelatedSlave(slave, function(s, index, array) { return isSlaveAvailable(s); });
}

window.randomSister = function(slave) {
	return randomRelatedSlave(slave, function(s, index, array) { return areSisters(slave, s); });
}

window.randomTwinSister = function(slave) {
	return randomRelatedSlave(slave, function(s, index, array) { return areSisters(slave, s) == 1; });
}

window.randomAvailableSister = function(slave) {
	return randomRelatedSlave(slave, function(s, index, array) { return isSlaveAvailable(s) && areSisters(slave, s); });
}

window.randomAvailableTwinSister = function(slave) {
	return randomRelatedSlave(slave, function(s, index, array) { return isSlaveAvailable(s) && areSisters(slave, s) == 1; });
}

window.randomDaughter = function(slave) {
	return randomRelatedSlave(slave, function(s, index, array) { return s.mother == slave.ID || s.father == slave.ID; });
}

window.randomAvailableDaughter = function(slave) {
	return randomRelatedSlave(slave, function(s, index, array) { return isSlaveAvailable(s) && (s.mother == slave.ID || s.father == slave.ID); });
}

window.randomParent = function(slave) {
	return randomRelatedSlave(slave, function(s, index, array) { return s.ID == slave.mother || s.ID == slave.father; });
}

window.randomAvailableParent = function(slave) {
	return randomRelatedSlave(slave, function(s, index, array) { return isSlaveAvailable(s) && (s.ID == slave.mother || s.ID == slave.father); });
}

window.totalPlayerRelatives = function(pc) {
	var relatives = 0;
	if (pc.mother > 0) {
		relatives += 1
	}
	if (pc.father > 0) {
		relatives += 1
	}
	if (pc.daughters > 0) {
		relatives += pc.daughters
	}
	if (pc.sisters > 0) {
		relatives += pc.sisters
	}
	return relatives
};


code:
:: FamilyTreeJS [script]

var lastActiveSlave, lastSlaves, lastPC;

/*
  To use, add something like:

<<run updateFamilyTree($activeSlave, $slaves, $PC)>>

If you want to update the tree, just re-call the run line.

If there's no active slave, you can do:

<<run updateFamilyTree(null, $slaves, $PC)>>

*/

window.updateFamilyTree = function(activeSlave = lastActiveSlave, slaves = lastSlaves, PC = lastPC) {
  lastActiveSlave = activeSlave;
  lastSlaves = slaves;
  lastPC = PC;
  var treeDepth = 0;
  var numTreeNodes = 0;

  var graphElement = document.getElementById("graph");
  if(!graphElement)
    return;
  graphElement.innerHTML = "";

  /* The way this code works is that we start with the activeSlave then we call
     slaveInfo() recursively to work our way up the tree finding their parents.

   */

  function getSlave(id, expectedGenes) {
    if(id == -1) {
      return {"slaveName":"YOU", "ID":id, "physicalAge":PC.physicalAge, "genes":PC.genes, father:PC.father, mother:PC.mother};
    }
    if(id == 0) {
      return {"slaveName":"-", "ID":id, "genes":expectedGenes};
    }
    if(id == activeSlave.ID) {
      return activeSlave;
    }
    for(var i = 0; i < slaves.length; ++i) {
      if(slaves[i].ID == id)
        return slaves[i];
    }
    return {"slaveName":"-", "ID":id, "genes":expectedGenes};
  }

  function slaveInfo(slave, activeSlaveId, recursionProtectSlaveId = {}) {
    numTreeNodes = 0;
    treeDepth = 0;
    if(recursionProtectSlaveId[slave.ID]) {
      console.log("Recursion protection");
      return slaveInfo_(slave, activeSlaveId);
    }
    recursionProtectSlaveId[slave.ID] = true;

    if(typeof slave.father == "undefined" || typeof slave.mother == "undefined")
       return slaveInfo_(slave, activeSlaveId);

    if(slave.father == -1 || slave.mother == -1) {
      return slaveInfo(getSlave(-1), activeSlaveId, recursionProtectSlaveId);
    }
    if(slave.father != 0) {
      return slaveInfo(getSlave(slave.father, "unknownXY"), activeSlaveId, recursionProtectSlaveId);
    }

    if(slave.mother != 0) {
      return slaveInfo(getSlave(slave.mother, "unknownXX"), activeSlaveId, recursionProtectSlaveId);
    }
    return slaveInfo_(slave, activeSlaveId);
  }
  function slaveInfo_(slave, activeSlaveId, slavesAdded={}, depth = 1) {
    numTreeNodes += 1;
    treeDepth = Math.max(treeDepth, depth);
    var shouldAddChildren = false;
    if(!slavesAdded[slave.ID]) {
      shouldAddChildren = true;
      slavesAdded[slave.ID] = true;
    }
    var data = {
      "name": slave.slaveName + (slave.physicalAge?(" (" + slave.physicalAge + ")"):""),
      "class" : slave.genes,
      "textClass": (activeSlaveId == slave.ID)?"emphasis":"",
      "marriages": [],
    };

    var spouseToChild = {};

    function maybeAddSpouseToChild(child) {
      if(child.ID == slave.ID)
        return;
      if (child.father == slave.ID) {
        if(!spouseToChild[child.mother]) {
          spouseToChild[child.mother] = []
        }
        spouseToChild[child.mother].push(child);
      } else if (child.mother == slave.ID) {
        if(!spouseToChild[child.father]) {
          spouseToChild[child.father] = []
        }
        spouseToChild[child.father].push(child);
      }
    }

    if(activeSlave.ID != PC.ID)
      maybeAddSpouseToChild(activeSlave);
    maybeAddSpouseToChild(getSlave(-1));

    for(var i = 0; i < slaves.length; ++i) {
      var child = slaves[i];
      if(child.ID != activeSlave.ID)
        maybeAddSpouseToChild(child);
    }

    for(var key in spouseToChild) {
      if(spouseToChild.hasOwnProperty(key)) {
        var children = shouldAddChildren?spouseToChild[key]:[];
        var spouse = getSlave(key, (slaves.genes=="XX")?"unknownXY":(slaves.genes=="XY")?"unknownXX":"unknown");
        var spouseName;
        if (spouse.ID != slave.ID){
          spouseName = spouse.slaveName + (spouse.physicalAge?(" (" + spouse.physicalAge + ")"):"")
        } else {
          spouseName = (spouse.ID==-1)?"(yourself)":"(themselves)";
        }
        var marriage = {
          "spouse": {"name": spouseName, "class": spouse.genes},
          "children": children.map(function(x) { return slaveInfo_(x, activeSlaveId, slavesAdded, depth+1)} ),
        };
        data.marriages.push(marriage);
      }
    }
    return data;
  }

  if(activeSlave == PC || activeSlave == null)
    activeSlave = getSlave(-1)
  const treeData = [slaveInfo(activeSlave, activeSlave.ID)];
  console.log("Family tree is", treeData, 'and has:', numTreeNodes);

  var parentWidth = document.getElementById('editFamily').offsetWidth;

  console.log(parentWidth, document.getElementById('passages').offsetWidth);
  if(!parentWidth)
    parentWidth = document.body.offsetWidth - 483;

  console.log(parentWidth, Math.min(200 + 40*numTreeNodes,parentWidth-200) + 200);

  dTree.init(treeData, {
	target: "#graph",
	debug: true,
	height: 50 + 50*treeDepth, /* very rough heuristics */
	width: Math.min(200 + 40*numTreeNodes,
                  parentWidth-200) + 200,
	callbacks: {
		nodeClick: function(name, extra) {
		}
	}
	});

};


code:
:: Foreach Macro JS [script]

Macro.add('foreach', {
	skipArgs : true,
	tags     : null,

	handler() {
		const payload        = this.payload[0].contents.replace(/\n$/, '');
		let statement        = this.args.raw.trim();
		let variable;
		let array;
		let result;

		if (statement.length !== 0) {
			const parts = statement.match(/^(\S+?)\s+of\s+(\S.*?)\s*$/i);
			if(parts !== null) {
				variable = parts[1];
				array = parts[2];
			}
		}
		if(!variable || !array) {
			return this.error('invalid syntax, format: <<foreach variable of array-expression>>... <</foreach>>');
		}

		try {
			result = Scripting.evalTwineScript(array);
		} catch (ex) {
			return this.error(`bad evaluation: ${typeof ex === 'object' ? ex.message : ex}`);
		}

		let resultLength = result['length'];

		// We don't check for "instanceof Array" to also be able to pass arguments or other strange objects
		if(typeof resultLength !== 'number' || (resultLength % 1) !== 0) {
			return this.error("bad evaluation: '" + result + "' is not an array or array-like object");
		}

		if(resultLength > Config.macros.maxLoopIterations) {
			return this.error('Array too large for maxLoopIterations (' + resultLength + ' > ' + Config.macros.maxLoopIterations + ')');
		}

		if(!new RegExp(`^(${Patterns.variable})$`).test(variable)) {
			return this.error('not a variable identifier: ' + variable);
		}

		if(resultLength <= 0) {
			// Don't bother with empty stuff
			return;
		}

		Config.debug && this.debugView.modes({block: true});

		payload.replace(/^\n/, '');
		for(let i = 0; i < resultLength; ++ i) {
			Wikifier.setValue(variable, result[i]);
			new Wikifier(this.output, payload);
		}
	}
});


code:
:: heroCreator.tw [script]

window.getHeroSlave = function(heroSlave, baseHeroSlave) {
    var newSlave = clone(baseHeroSlave);
    for (var attrname in heroSlave) {
        newSlave[attrname] = heroSlave[attrname];
    };
    return newSlave;
}


code:
:: pregJS [script]

/*Major props to the anons who worked together to forge the Super Pregnancy Project. Let your legacy go unforgotten.*/
window.getPregBellySize = function(s) {
	var gestastionWeek = s.preg;
	var fetuses = s.pregType;
	var phi = 1.618;

	if(gestastionWeek <= 32) {
		var targetLen = ((0.00006396 * Math.pow(gestastionWeek, 4)) - (0.005501 * Math.pow(gestastionWeek, 3)) + (0.161 * Math.pow(gestastionWeek, 2)) - (0.76 * gestastionWeek) + 0.208);
	} else if(gestastionWeek <= 106) {
		var targetLen = ((-0.0000004675 * Math.pow(gestastionWeek, 4)) + (0.0001905 * Math.pow(gestastionWeek, 3)) - (0.029 * Math.pow(gestastionWeek, 2)) + (2.132 * gestastionWeek) - 16.575);
	} else {
		var targetLen = ((-0.00003266 * Math.pow(gestastionWeek,2)) + (0.076 * gestastionWeek) + 43.843);
	}

	var bellySize = ((4 / 3) * (Math.PI) * (phi / 2) * (Math.pow((targetLen / 2), 3)) * fetuses);
	return bellySize;
};

window.bellyAdjective = function(slave) {
	slave = slave || State.variables.activeSlave;
	if(slave.belly >= 1500) {
		if(slave.belly >= 750000) {
			if(slave.preg > 10) {
				return 'monolithic bulging';
			} else {
				return 'monolithic';
			}
		} else if(slave.belly >= 600000) {
			if(slave.preg > 10) {
				return 'titanic bulging';
			} else {
				return 'titanic';
			}
		} else if(slave.belly >= 450000) {
			if(slave.preg > 10) {
				return 'gigantic bulgy';
			} else {
				return 'gigantic';
			}
		} else if(slave.belly >= 300000) {
			return 'massive';
		} else if(slave.belly >= 100000) {
			return 'giant';
		} else if(slave.belly >= 15000) {
			return 'huge';
		} else if(slave.belly >= 10000) {
			return 'big';
		} else {
			return 'swollen';
		}
	} else {
		return '';
	}
}


code:
:: RA Selector JS [script]

window.growAdvSelector = function(slave, rule) {

	var ret = "";
	var setd = 0;

	if ((rule.breastSize == "small" && slave.boobs < 350) ||
		(rule.breastSize == "c-cup" && slave.boobs < 550) ||
		(rule.breastSize == "f-cup" && slave.boobs < 1100) ||
		(rule.breastSize == "k-cup" && slave.boobs < 2150) ||
		(rule.breastSize == "p-cup" && slave.boobs < 3700) ||
		(rule.breastSize == "unlimited" && slave.boobs < 24000))
	{
		slave.drugs = "breast injections";
		ret = slave.slaveName + "'s boobs could be bigger, so she's been put on breast injections.";
		return ret;
	}

	if ((rule.buttSize == "small" && slave.butt < 1) ||
		(rule.buttSize == "plump" && slave.butt < 3) ||
		(rule.buttSize == "huge" && slave.butt < 4) ||
		(rule.buttSize == "enormous" && slave.butt < 6) ||
		(rule.buttSize == "unlimited" && slave.butt < 19))
	{
		slave.drugs = "butt injections";
		ret = slave.slaveName + "'s butt could be bigger, so she's been put on butt injections.";
		return ret;
	}

	if (slave.balls > 0 &&
		((rule.ballSize == "small" && slave.balls < 2) ||
		 (rule.ballSize == "big" && slave.balls < 4) ||
		 (rule.ballSize == "huge" && slave.balls < 6) ||
		 (rule.ballSize == "unlimited" && slave.balls < 9)))
	{
		slave.drugs = "testicle enhancement";
		ret = slave.slaveName + "'s balls aren't large enough, so she's been put on testicle enhancement.";
		return ret;
	}

	if (slave.dick > 0 &&
		((rule.penisSize == "small" && slave.dick < 2) ||
		 (rule.penisSize == "big" && slave.dick < 4) ||
		 (rule.penisSize == "huge" && slave.dick < 6) ||
		 (rule.penisSize == "unlimited" && slave.dick < 12)))
	{
		slave.drugs = "penis enhancement";
		ret = slave.slaveName + "'s dick isn't large enough, so she's been put on penis enhancement.";
		return ret;
	}

	if ((rule.lipSize == "normal" && slave.lips < 15) ||
		(rule.lipSize == "pretty" && slave.lips < 30) ||
		(rule.lipSize == "plush" && slave.lips < 55) ||
		(rule.lipSize == "huge" && slave.lips < 85) ||
		(rule.lipSize == "facepussy" && slave.lips < 99))
	{
		slave.drugs = "lip injections";
		ret = slave.slaveName + "'s lips aren't large enough, so she's been put on lips enhancement.";
		return ret;
	}

	if (slave.drugs != "no drugs")
	{
		slave.drugs = "no drugs";
		ret = slave.slaveName + " has reached growth targets and has been taken off growth injections.";
	}

	return ret;

}

window.growAdvSelectorSlim = function(slave, rule) {

	var ret = "";
	var setd = 0;

	if ((rule.breastSize == "small" && slave.boobs > 350) ||
		(rule.breastSize == "c-cup" && slave.boobs > 550) ||
		(rule.breastSize == "f-cup" && slave.boobs > 1100) ||
		(rule.breastSize == "k-cup" && slave.boobs > 2150) ||
		(rule.breastSize == "p-cup" && slave.boobs > 3700))
	{
		slave.drugs = "breast redistributors";
		ret = slave.slaveName + "'s boobs are too big, so she's been put on breast reduction drugs.";
		return ret;
	}

	if ((rule.buttSize == "small" && slave.butt > 1) ||
		(rule.buttSize == "plump" && slave.butt > 3) ||
		(rule.buttSize == "huge" && slave.butt > 4) ||
		(rule.buttSize == "enormous" && slave.butt > 6))
	{
		slave.drugs = "butt redistributors";
		ret = slave.slaveName + "'s butt is too big, so she's been put on butt reduction drugs.";
		return ret;
	}

	if ((rule.ballSize == "small" && slave.balls > 2) ||
		(rule.ballSize == "big" && slave.balls > 4) ||
		(rule.ballSize == "huge" && slave.balls > 6))
	{
		slave.drugs = "testicle atrophiers";
		ret = slave.slaveName + "'s balls are too big, so she's been put on testicle atrophiers.";
		return ret;
	}

	if ((rule.penisSize == "small" && slave.dick > 2) ||
		(rule.penisSize == "big" && slave.dick > 4) ||
		(rule.penisSize == "huge" && slave.dick > 6))
	{
		slave.drugs = "penis atrophiers";
		ret = slave.slaveName + "'s dick is too big, so she's been put on penis atrophiers.";
		return ret;
	}

	if ((rule.lipSize == "normal" && slave.lips > 15) ||
		(rule.lipSize == "pretty" && slave.lips > 30) ||
		(rule.lipSize == "plush" && slave.lips > 55) ||
		(rule.lipSize == "huge" && slave.lips > 85))
	{
		slave.drugs = "lip atrophiers";
		ret = slave.slaveName + "'s lips are too big, so she's been put on lips atrophiers.";
		return ret;
	}

	if (slave.drugs != "no drugs")
	{
		slave.drugs = "no drugs";
		ret = slave.slaveName + " has reached growth targets and has been taken off growth injections.";
	}

	return ret;

}


code:
:: rbuttonJS [script]
/* This is modifed radiobutton macro, for automatic checked state setup*/
/* Usage (be sure to use quotes around parameters):

	<<rbutton "$variable" "value">>
Or:
	<<rbutton "$variable" "value" "HTML_element_ID" "Text to replace with, inside html element with ID from previous parameter. <br> HTML tags allowed.">>

Group of radiobutton will be created based on variable name. Checked state will be setted up if variable contain value matched with second parameter. Full form of macro call can be used to display extended description of selected value.
*/

Macro.add('rbutton', {
	handler() {
	    if (this.args.length < 2) {
		const errors = [];
		if (this.args.length < 1) { errors.push('variable name'); }
		if (this.args.length < 2) { errors.push('checked value'); }
		return this.error(`no ${errors.join(' or ')} specified`);
	    }

	    // Ensure that the variable name argument is a string.
	    if (typeof this.args[0] !== 'string') {
		return this.error('variable name argument is not a string');
	    }

	    const varName = this.args[0].trim();

	    // Try to ensure that we receive the variable's name (incl. sigil), not its value.
	    if (varName[0] !== '$' && varName[0] !== '_') {
		return this.error(`variable name "${this.args[0]}" is missing its sigil ($ or _)`);
	    }

	    const initValue	 = Wikifier.getValue(this.args[0]);
	    const varId      = Util.slugify(varName);
	    const checkValue = this.args[1];
	    const el         = document.createElement('input');

	    var replaceID = "";
	    var replaceText = "";
	    if (typeof this.args[2] === 'string') {
			replaceID = this.args[2];
	    }
	    if (typeof this.args[3] === 'string') {
			replaceText = this.args[3];
	    }

	    /*
		Setup and initialize the group counter.
	    */
	    if (!TempState.hasOwnProperty(this.name)) {
		TempState[this.name] = {};
	    }

	    if (!TempState[this.name].hasOwnProperty(varId)) {
		TempState[this.name][varId] = 0;
	    }

	    /*
		Setup and append the input element to the output buffer.
	    */
	    jQuery(el)
		.attr({
		    id       : `${this.name}-${varId}-${TempState[this.name][varId]++}`,
		    name     : `${this.name}-${varId}`,
		    type     : 'radio',
		    tabindex : 0 // for accessiblity
		})
		.addClass(`macro-${this.name}`)
		.on('change', function () {
		    if (this.checked) {
			Wikifier.setValue(varName, checkValue);

			if (replaceID.length > 0 && replaceText.length > 0){

				var replaceEl = document.getElementById(replaceID);
				//alert (replaceEl);
				if (replaceEl != null) {
					replaceEl.innerHTML = replaceText;
					}

				}
		    }
		})
		.ready (function () {
			//alert ("DOM finished");
			if (el.checked && replaceID.length > 0 && replaceText.length > 0){

				var replaceEl = document.getElementById(replaceID);
				//alert (replaceEl);
				if (replaceEl != null) {
					replaceEl.innerHTML = replaceText;
				}

			}
		})
		.appendTo(this.output);

	    /*
		Set the story variable to the checked value and the input element to checked, if requested.
	    */
	    if (initValue == checkValue) {
		el.checked = true;
		Wikifier.setValue(varName, checkValue);
	    }
	}
    });


code:
:: rulesAssistant [script]

function panic(message) {
    message = message || "panic";
    if (typeof Error !== "undefined") {
        throw new Error(message);
    }
    throw message;
}

function assert(condition, message) {
    if (!condition)
        panic(message || "Assertion failed");
}

window.isSimpleCondition = function(expr, validNames) {
    assert(validNames, "validNames was not given");

    switch (expr.id) {
    case "true": case "false":
        return true;

    case "<": case "<=": case ">": case ">=":
        return (
            expr.first.id === "(name)"              // first operand should be a name
            && validNames.includes(expr.first.name) // among the valid ones
            && expr.second.id === "(number)"        // and second should be a literal
        );

    case "&&": case "||":
        return (
            (expr.first.id == "<" || expr.first.id == "<=")
            && (expr.second.id == ">" || expr.second.id == ">=")
            && isSimpleCondition(expr.first, validNames)
            && isSimpleCondition(expr.second, validNames)
            && expr.first.first.name === expr.second.first.name
        );
    }
    return false;
}

window.getVariable = function(expr) {
    switch (expr.id) {
    case "true":
        return "always";
    case "false":
        return "none";
    case "<": case "<=": case ">": case ">=":
        return expr.first.name === "energy" ? "sex drive" : expr.first.name;
    case "&&": case "||":
        return getVariable(expr.first);
    }
}

window.changeVariable = function(expr, newVar) {
    //assert(isSimpleCondition(expr), "expr is not simple");

    switch (expr.id) {
    case "true": case "false":
        return {
            id: "<",
            first: {id: "(name)", name: newVar},
            second: {id: "(number)", value: 0}
        };

    case "<": case "<=": case ">": case ">=":
        expr.first.name = newVar;
        return expr;

    case "&&": case "||":
        expr.first.first.name = newVar;
        expr.second.first.name = newVar;
        return expr;
    }
}

window.changeComparison = function(expr, newComparison) {
    assert(expr.id !== "true" && expr.id !== "false", "expr is constant");
    //assert(isSimpleCondition(expr), "expr is not simple");

    var newOperand = {
        id: newComparison,
        first: {id: "(name)", name: expr.first.name},
        second: {id: "(number)", value: 0}
    };

    if (newComparison === "<" || newComparison === "<=") {
        switch (expr.id) {
        case "<": case "<=":
            expr.id = newComparison;
            return expr;
        case ">": case ">=":
            return { id: "&&", first: newOperand, second: expr };
        case "&&": case "||":
            expr.first.id = newComparison;
            return expr;
        }
    } else {
        switch (expr.id) {
        case "<": case "<=":
            return { id: "&&", first: expr, second: newOperand };
        case ">": case ">=":
            expr.id = newComparison;
            return expr;
        case "&&": case "||":
            expr.second.id = newComparison;
            return expr;
        }
    }
}

window.removeComparison = function(expr, comparisonType) {
    assert(expr.id !== "true" && expr.id !== "false", "expr is constant");
    //assert(isSimpleCondition(expr), "expr is not simple");
    assert(comparisonType === "lower" || comparisonType === "upper",
           "invalid comparisonType '" + comparisonType + "'");

    if (comparisonType === "lower") {
        switch (expr.id) {
        case "<": case "<=":
            return expr;
        case ">": case ">=":
            return { id: "true" };
        case "&&": case "||":
            return expr.first;
        }
    } else {
        switch (expr.id) {
        case "<": case "<=":
            return { id: "false" };
        case ">": case ">=":
            return expr;
        case "&&": case "||":
            return expr.second;
        }
    }
}

window.changeConnective = function(expr, newConnective) {
    switch (expr.id) {
    case "true": case "false":
    case "<": case "<=": case ">": case ">=":
        return expr;
    case "&&": case "||":
        expr.id = newConnective;
        return expr;
    }
}

window.unparseExpr = function(expr) {
    switch (expr.id) {

    // literals
    case "true":
        return "true";
    case "false":
        return "false";
    case "(number)": case "(string)":
        return expr.value;

    // names
    case "(name)":
        return expr.name;

    // logical infix operators
    case "&&": case "||":
    // numerical infix comperators
    case "<": case "<=":
    case ">": case ">=":
    case "=": case "!=":
    // numerical infix operators (excluding minus)
    case "+": case "*": case "/": case "^":
        return [unparseExpr(expr.first),
                expr.id,
                unparseExpr(expr.second)].join(" ");

    // unary/prefix operators
    case "!":
        return expr.id + unparseExpr(expr.first);

    case "-":
        if (expr.second !== undefined) {
            return unparseExpr(expr.first) + " - " + unparseExpr(expr.second);
        } else {
            return "-" + unparseExpr(expr.first);
        }

    // parentheses
    case "(":
        return "(" + unparseExpr(expr.first) + ")";
    }

    panic("how did I get here? unknown expr.id: " + expr.id);
}

window.typeExpr = function(expr, env) {
    switch (expr.id) {

    case "true": case "false":
        return "bool";
    case "(number)":
        return "number";
    case "(string)":
        return "string";

    case "(name)":
        return env[expr.name];

    case "&&": case "||":
        return "bool";

    case "<": case "<=":
    case ">": case ">=":
        return "bool";
    case "=": case "!=":
        return "bool";

    case "*": case "/":  case "^":
        return "number";

    case "+":
        return typeExpr(expr.first, env) === "string" ? "string" : "number";

    case "!":
        return typeExpr(expr.first, env);
    case "-":
        if (expr.second !== undefined) {
            return "number";
        } else {
            return typeExpr(expr.first, env);
        }

    case "(":
        return typeExpr(expr.first, env);
    }
}

window.evalExpr = function(expr, env) {
    switch (expr.id) {

    case "true":
        return true;
    case "false":
        return false;
    case "(number)":
        return expr.value;
	case "(string)":
		if(expr.value.startsWith('"') && expr.value.endsWith('"')) {
			return JSON.parse(expr.value);
		} else {
			return expr.value;
		}
    case "(name)":
        return env[expr.name];

    case "&&":
        return evalExpr(expr.first, env) && evalExpr(expr.second, env);
    case "||":
        return evalExpr(expr.first, env) || evalExpr(expr.second, env);
    case "<":
        return evalExpr(expr.first, env) < evalExpr(expr.second, env);
    case "<=":
        return evalExpr(expr.first, env) <= evalExpr(expr.second, env);
    case ">":
        return evalExpr(expr.first, env) > evalExpr(expr.second, env);
    case ">=":
        return evalExpr(expr.first, env) >= evalExpr(expr.second, env);
    case "=":
        return evalExpr(expr.first, env) == evalExpr(expr.second, env);
    case "!=":
        return evalExpr(expr.first, env) != evalExpr(expr.second, env);

    case "+":
        return evalExpr(expr.first, env) + evalExpr(expr.second, env);
    case "*":
        return evalExpr(expr.first, env) * evalExpr(expr.second, env);
    case "/":
        return evalExpr(expr.first, env) / evalExpr(expr.second, env);
    case "^":
        return Math.pow(evalExpr(expr.first, env), evalExpr(expr.second, env));

    case "!":
        return !evalExpr(expr.first, env);
    case "-":
        if (expr.second !== undefined) {
            return evalExpr(expr.first, env) - evalExpr(expr.second, env);
        } else {
            return -evalExpr(expr.first, env);
        }

    case "(":
        return evalExpr(expr.first, env);
    }
}

window.optimizeExpr = function(expr) {
    switch (expr.id) {
    case "true": case "false":
    case "(number)": case "(string)":
    case "(name)":
        return expr;

    case "-":
        // The only "optimization" we are doing.  Obviously, this is not done
        // for the sake of speed, rather, to make the UI and isSimpleCondition
        // simpler, since they don't have to explicitly check for negative
        // numbers.
        if (expr.second === undefined && expr.first.id === "(number)")
            return {id: "(number)", value: evalExpr(expr)};
        // fallthrough, if the minus was not unary

    case "&&": case "||":
    case "<": case "<=": case ">": case ">=":
    case "=": case "!=":
    case "+": case "*": case "/": case "^":
        return {
            id: expr.id,
            first: optimizeExpr(expr.first),
            second: optimizeExpr(expr.second)
        };

    case "!": case "(":
        return {
            id: expr.id,
            first: optimizeExpr(expr.first)
        };
    }
}

window.printError = function(exprStr, error) {
    var result = [],
        inError = false;
    for (var i = 0; i < exprStr.length; i++) {
        if (i === error.index)
            result.push("@@.red;");
        result.push(exprStr[i]);
    }
    result.push("@@");
    return result.join("");
}


code:
:: rulesAssistantParser [script]

// Implements a Top Down Operator Precedence parser, also know as a Pratt
// parser, after its "inventor", Vaughan Pratt.  The one implemented here
// closely follows what's presented here,
//     * http://javascript.crockford.com/tdop/tdop.html
// by Douglas Crockford, that uses that technique in JSLint.  Other relevant
// resources on the interweb
//     * http://effbot.org/zone/simple-top-down-parsing.htm
//     * http://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing
//     * http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
//     * https://higherlogics.blogspot.gr/2009/11/extensible-statically-typed-pratt.html
//     * https://github.com/fholm/Vaughan
//     * https://github.com/DasIch/pratt
// included here mostly as bookmarks for potential future reference.
//
// With regards to the lexer, I used the following with many changes
//     * http://eli.thegreenplace.net/2013/06/25/regex-based-lexical-analysis-in-python-and-javascript/
//
// Other useful things that I may not use any more but wouldn't want to lose,
//     * https://plainjs.com/javascript/utilities/merge-two-javascript-objects-19/
//     * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions

if (!RegExp.escape) {
    RegExp.escape = function(s) {
        return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    };
}

if (!String.prototype.format) {
    String.prototype.format = function() {
        var args = arguments;
        return this.replace(/{(\d+)}/g, function(match, number) {
            return typeof args[number] !== "undefined" ? args[number] : match;
        });
    };
}

function panic(index, msg) {
    throw {index: index, message: msg};
}

var Lexer = function(skipWhitespace) {
    this.rules = [];
    this.index = 0;
    this.buffer = "";
    this.skipWhitespace = skipWhitespace ? /\S/ : null;
}

Lexer.prototype.addRule = function(id, defn) {
    var pattern = (defn && defn.pattern) || RegExp.escape(id);
    this.rules.push({
        id: id,
        pattern: new RegExp('^' + pattern)
    });
}

Lexer.prototype.feed = function(buffer) {
    this.buffer = buffer;
    this.index = 0;
}

Lexer.prototype.nextInterestingChar = function() {
    if (this.skipWhitespace) {
        var match = this.skipWhitespace.exec(this.buffer.substr(this.index));
        return match ? this.index + match.index
                     : this.buffer.length;
    }
    return this.index;
}

Lexer.prototype.next = function() {
    this.index = this.nextInterestingChar();

    if (this.index >= this.buffer.length)
        return { done: true };

    for (var i = 0; i < this.rules.length; ++i) {
        var rule = this.rules[i],
            match = rule.pattern.exec(this.buffer.substr(this.index));
        if (match) {
            var token = {
                id: rule.id,
                value: match[0],
                index: this.index,
            };
            this.index += token.value.length;
            return { done: false, value: token };
        }
    }

    panic(this.index, "illegal character");
}

var BaseSymbol = {
    lbp: 0,
    nud: function() { panic(this.index, "unexpected '" + this.id + "'"); },
    led: function() { panic(this.index, "not an operator"); }
};

var Parser = function(eofToken) {
    this.lexer = new Lexer(true);
    this.currentSymbol = null;

    this.eofToken = eofToken;
    this.symbolTable = {
        [this.eofToken]: Object.create(BaseSymbol, {id: {value: this.eofToken}})
    };
}

Parser.prototype.addSymbol = function(id, defn) {
    var s = this.symbolTable[id];
    if (s) {
        if (defn) {
            if (defn.lbp !== undefined) s.lbp = defn.lbp;
            if (defn.nud !== undefined) s.nud = defn.nud;
            if (defn.led !== undefined) s.led = defn.led;
        }
    } else {
        s = Object.create(BaseSymbol);
        s.id = id;
        if (defn && defn.lbp !== undefined) s.lbp = defn.lbp;
        if (defn && defn.nud) s.nud = defn.nud;
        if (defn && defn.led) s.led = defn.led;
        this.symbolTable[id] = s;
        this.lexer.addRule(id, defn);
    }
    return this;
}

Parser.prototype.addInfix = function(id, lbp, callback) {
    this.addSymbol(id, {
        lbp: lbp,
        led: function(p, left) { return callback(this, left, p.parse(lbp)); }
    });
    return this;
}

Parser.prototype.addInfixR = function(id, lbp, callback) {
    this.addSymbol(id, {
        lbp: lbp,
        led: function(p, left) { return callback(this, left, p.parse(lbp-1)); }
    });
    return this;
}

Parser.prototype.addPrefix = function(id, callback) {
    this.addSymbol(id, {
        // FIXME: this should not always be 70
        nud: function (p) { return callback(this, p.parse(70)); }
    });
    return this;
}

Parser.prototype.addConstant = function(id, callback) {
    this.addSymbol(id, {
        nud: function () { return callback(this); }
    });
    return this;
}

Parser.prototype.advance = function(id) {
    if (id !== undefined && this.currentSymbol.id !== id)
        panic(this.currentSymbol.index, "expected '" + id + "', got '" + this.currentSymbol.id + "'");

    var iter = this.lexer.next(),
        token = iter.value;
    if (iter.done)
        token = {
            id: this.eofToken,
            index: this.lexer.buffer.length
        };

    var symbol = this.symbolTable[iter.done ? this.eofToken : token.id];
    if (!symbol)
        panic(token.index, "unknown token '" + token.id + "'");

    var newSymbol = Object.create(symbol);
    newSymbol.value = token.value;
    newSymbol.index = token.index;
    return this.currentSymbol = newSymbol;
}

Parser.prototype.parse = function(rbp) {
    var symbol = this.currentSymbol;
    this.advance();
    var left = symbol.nud(this);

    rbp = rbp || 0;
    while (rbp < this.currentSymbol.lbp) {
        symbol = this.currentSymbol;
        this.advance();
        left = symbol.led(this, left);
    }
    return left;
}

Parser.prototype.parseString = function(string) {
    this.lexer.feed(string);
    this.advance();  // "kickstart" the lexer
    var result = this.parse();
    this.advance(this.eofToken);
    return result;
}

var ASTBuilder = function(eofToken) {
    this.parser = new Parser(eofToken);
}

ASTBuilder.prototype.addSymbol = function(id, extra) {
    this.parser.addSymbol(id, extra);
    return this;
}

ASTBuilder.prototype.addInfix = function(id, lbp) {
    this.parser.addInfix(id, lbp, function(symbol, left, right) {
        return {
            id: id,
            first: left,
            second: right,
        };
    });
    return this;
}

ASTBuilder.prototype.addInfixR = function(id, lbp) {
    this.parser.addInfixR(id, lbp, function(symbol, left, right) {
        return {
            id: id,
            first: left,
            second: right,
        };
    });
    return this;
}

ASTBuilder.prototype.addPrefix = function(id) {
    this.parser.addPrefix(id, function(symbol, left) {
        return {
            id: id,
            first: left,  // it's not really the left is it?
        };
    });
    return this;
}

ASTBuilder.prototype.addConstant = function(id, value) {
    this.parser.addConstant(id, function(symbol) {
        return {
            id: id,
            value: value,
        };
    });
    return this;
}

var op = {
    add: function(a, b) { return a + b; },
    sub: function(a, b) { return a - b; },
    mul: function(a, b) { return a * b; },
    div: function(a, b) { return a / b; },
    pow: function(a, b) { return Math.pow(a, b); },

    neg: function(a) { return -a; },

    lt: function(a, b) { return a < b; },
    le: function(a, b) { return a <= b; },
    gt: function(a, b) { return a > b; },
    ge: function(a, b) { return a >= b; },
    eq: function(a, b) { return a === b; },
    neq: function(a, b) { return a !== b; },

    not: function(a) { return !a; },
    or: function(a, b) { return a || b; },
    and: function(a, b) { return a && b; },
};

var parserBuilder = new ASTBuilder("(end)")
    // XXX: need to be first to not be recognised as a (name)
    .addConstant("true", true)
    .addConstant("false", false)

    .addSymbol("(number)", {
        pattern: "\\d+",
        nud: function() {
            return {
                id: "(number)",
                value: parseInt(this.value),
            };
        }
    })

    .addSymbol("(string)", {
        pattern: "\"(?:[^\\\\\"]|\\\\\"|\\\\(?!\"))*\"",
        nud: function(p) {
            return {
                id: "(string)",
                value: this.value.replace(/^\"|\"$/g, ""),
            };
        }
    })

    .addSymbol("(name)", {
        pattern: "[a-zA-Z]\\w*",
        nud: function(p) {
            return {
                id: "(name)",
                name: this.value,
            };
        }
    })

    .addInfix("+", 50,  op.add)
    .addInfix("-", 50,  op.sub)
    .addInfix("*", 60,  op.mul)
    .addInfix("/", 60,  op.div)
    .addInfixR("^", 70, op.pow)

    .addPrefix("-", op.neg)

    .addInfix("<=", 40, op.le)
    .addInfix("<",  40, op.lt)
    .addInfix(">=", 40, op.ge)
    .addInfix(">",  40, op.gt)
    .addInfix("!=", 40, op.neq)
    .addInfix("=",  40, op.eq)

    .addPrefix("!", op.not)
    .addInfix("||", 30, op.or)
    .addInfix("&&", 30, op.and)

    .addSymbol(")")
    .addSymbol("(", {
        nud: function(p) {
            var expr = p.parse(0);
            p.advance(")");
            return expr;
        }
    });

var parser = parserBuilder.parser;
window.parser = parser;

window.parseCondition = function(condition) {
    try {
        return {expr: parser.parseString(condition), error: null};
    } catch (e) {
        return {expr: null, error: e};
    }
}


code:
:: Span Macro JS [script]

/*
 * <> macro
 * A minimal macro which allows to create  elements with dynamic IDs.
 *
 * Usage: <>...<>
 */
Macro.add('span', {
	skipArgs : true,
	tags     : null,

	handler() {
		const payload        = this.payload[0].contents.replace(/(^\n+|\n+$)/, '');
		let statement        = this.args.raw.trim();
		let result;

		if(statement.length === 0) {
			return this.error('invalid syntax, format: <>');
		}

		try {
			result = Scripting.evalTwineScript(statement);
		} catch (ex) {
			return this.error(`bad evaluation: ${typeof ex === 'object' ? ex.message : ex}`);
		}

		Config.debug && this.debugView.modes({block: true});

		jQuery("")
			.wiki(payload)
			.appendTo(this.output);
	}
});