code:
:: FamilyTreeJS [script]
'use strict';

var lastActiveSlave, lastSlaves, lastPC;

/*
  To use, add something like:

		<<link "Pull up the file on her family tree.">>
		<<replace #familyTreeLink>>
			<<run renderFamilyTree($slaves, $activeSlave.ID)>>
		<</replace>>
	<</link>>

*/

window.renderFamilyTree = function(slaves, filterID) {

	var ftreeWidth,ftreeHeight;
	var chartWidth, chartHeight;
	var margin;
	d3.select('#ftree-canvas').remove();
	var svg = d3.select('#familyTree')
		.append('svg')
		.attr('id','ftree-canvas');
	var chartLayer = svg.append('g').classed('chartLayer', true);

	var range = 100;
	var data = buildFamilyTree(slaves, filterID);

	initFtreeSVG(data);
	runFtreeSim(data);

	function initFtreeSVG(data) {
		ftreeWidth = data.nodes.length * 45;
		ftreeHeight = data.nodes.length * 35;
		if(ftreeWidth < 600) {
			ftreeWidth = 600;
		} else if(ftreeWidth > 1920) {
			ftreeWidth = 1920;
		}
		if(ftreeHeight < 480) {
			ftreeHeight = 480;
		} else if(ftreeHeight > 1200) {
			ftreeHeight = 1200;
		}

		margin = {top:0, left:0, bottom:0, right:0 }

		chartWidth = ftreeWidth - (margin.left+margin.right)
		chartHeight = ftreeHeight - (margin.top+margin.bottom)

		svg.attr('width', ftreeWidth).attr('height', ftreeHeight)

		var defs = svg.append('defs');

		svg.append('defs').append('marker')
			.attr('id','arrowhead')
			.attr('viewBox','-0 -5 10 10')
			.attr('refX',13)
			.attr('refY',0)
			.attr('orient','auto')
			.attr('markerWidth',13)
			.attr('markerHeight',13)
			.attr('xoverflow','visible')
			.append('svg:path')
			.attr('d', 'M 0,-1 L 5,0 L 0,1')
			.attr('fill', '#a1a1a1')
			.style('stroke','none');

		chartLayer
			.attr('width', chartWidth)
			.attr('height', chartHeight)
			.attr('transform', 'translate('+[margin.left, margin.top]+')')
	}

	function runFtreeSim(data) {
		var simulation = d3.forceSimulation()
			.force('link', d3.forceLink().id(function(d) { return d.index }))
			.force('collide',d3.forceCollide( function(d){ return 60; }).iterations(4) )
			.force('charge', d3.forceManyBody().strength(-200).distanceMin(100).distanceMax(1000))
			.force('center', d3.forceCenter(chartWidth / 2, chartHeight / 2))
			.force('y', d3.forceY(100))
			.force('x', d3.forceX(200))

		var link = svg.append('g')
			.attr('class', 'link')
			.selectAll('link')
			.data(data.links)
			.enter()
			.append('line')
			.attr('marker-end','url(#arrowhead)')
			.attr('stroke', function(d) {
				if(d.type == 'homologous') {
					return '#862d59';
				} else if(d.type == 'paternal') {
					return '#24478f';
				} else {
					return '#aa909b';
				}
			})
			.attr('stroke-width', 2)
			.attr('fill', 'none');

		var node = svg.selectAll('.node')
			.data(data.nodes)
			.enter().append('g')
			.attr('class', 'node')
			.call(d3.drag()
			.on('start', dragstarted)
			.on('drag', dragged)
			.on('end', dragended));

		node.append('circle')
			.attr('r', function(d){  return d.r })
			.attr('stroke', function(d) {
				if(d.id == filterID) {
					return '#ffff20';
				} else {
					return '#5a5a5a';
				}
			})
			.attr('class', 'node-circle')
			.attr('r', 20);

		node.append('text')
			.text(function(d) {
				var ssym;
				if(d.ID == -1) {
					ssym = '';
				} else if(d.dick > 0 && d.vagina > -1) {
					ssym = '☿'
				} else if (d.dick > 0) {
					ssym = '♂';
				} else if (d.vagina > -1) {
					ssym = '♀';
				} else {
					ssym = '?';
				}
				return d.name + '('+ssym+')';
			})
			.attr('dy', 4)
			.attr('dx', function(d) { return -(8*d.name.length)/2; })
			.attr('class', 'node-text')
			.style('fill', function(d) {
				if(d.is_mother && d.is_father) {
					return '#b84dff';
				} else if(d.is_father) {
					return '#00ffff';
				} else if(d.is_mother) {
					return '#ff3399';
				} else if(d.unborn) {
					return '#a3a3c2';
				} else {
					return '#66cc66';
				}
			});

		var circles = svg.selectAll('.node-circle');
		var texts = svg.selectAll('.node-text');

		var ticked = function() {
			link
				.attr('x1', function(d) { return d.source.x; })
				.attr('y1', function(d) { return d.source.y; })
				.attr('x2', function(d) { return d.target.x; })
				.attr('y2', function(d) { return d.target.y; });

			node
				.attr("transform", function (d) {return "translate(" + d.x + ", " + d.y + ")";});
		}

		simulation.nodes(data.nodes)
			.on('tick', ticked);

		simulation.force('link')
			.links(data.links);

		function dragstarted(d) {
			if (!d3.event.active) simulation.alphaTarget(0.3).restart();
			d.fx = d.x;
			d.fy = d.y;
		}

		function dragged(d) {
			d.fx = d3.event.x;
			d.fy = d3.event.y;
		}

		function dragended(d) {
			if (!d3.event.active) simulation.alphaTarget(0);
			d.fx = null;
			d.fy = null;
		}

	}
};

window.buildFamilyTree = function(slaves = State.variables.slaves, filterID) {
	var family_graph = {
		nodes: [],
		links: []
	};
	var node_lookup = {};
	var preset_lookup = {
		'-2': 'Social elite',
		'-3': 'Client',
		'-4': 'Former master',
		'-5': 'An arcology owner',
		'-6': 'A citizen'
	};
	var outdads = {};
	var outmoms = {};
	var kids = {};

	var fake_pc = {
		slaveName: State.variables.PC.name + '(You)',
		mother: State.variables.PC.mother,
		father: State.variables.PC.father,
		dick: State.variables.PC.dick,
		vagina: State.variables.PC.vagina,
		ID: State.variables.PC.ID
	};
	var charList = [fake_pc];
	charList.push.apply(charList, slaves);
	charList.push.apply(charList, State.variables.tanks);

	var unborn = {};
	for(var i = 0; i < State.variables.tanks.length; i++) {
		unborn[State.variables.tanks[i].ID] = true;
	}

	for(var i  = 0; i < charList.length; i++) {
		var mom = charList[i].mother;
		var dad = charList[i].father;

		if(mom) {
			if(!kids[mom]) {
				kids[mom] = {};
			}
			kids[mom].mother = true;
		}
		if(dad) {
			if(!kids[dad]) {
				kids[dad] = {};
			}
			kids[dad].father = true;
		}
	}

	for(var i  = 0; i < charList.length; i++) {
		var character = charList[i];
		if(character.mother == 0 && character.father == 0 && !kids[character.ID]) {
			continue;
		}
		var mom = character.mother;
		if(mom < -6) {
			if(typeof outmoms[mom] == 'undefined') {
				outmoms[mom] = [];
			}
			outmoms[mom].push(character.slaveName);
		} else if(mom < 0 && typeof node_lookup[mom] == 'undefined' && typeof preset_lookup[mom] != 'undefined') {
			node_lookup[mom] = family_graph.nodes.length;
			charList.push({ID: mom, mother: 0, father: 0, is_father: true, dick: 0, vagina: 1, slaveName: preset_lookup[mom]});
		}

		var dad = character.father;
		if(dad < -6) {
			if(typeof outdads[dad] == 'undefined') {
				outdads[dad] = [];
			}
			outdads[dad].push(character.slaveName);
		} else if(dad < 0 && typeof node_lookup[dad] == 'undefined' && typeof preset_lookup[dad] != 'undefined') {
			node_lookup[dad] = family_graph.nodes.length;
			charList.push({ID: dad, mother: 0, father: 0, is_father: true, dick: 1, vagina: -1, slaveName: preset_lookup[dad]});
		}
	}
	var mkeys = Object.keys(outmoms);
	for(var i = 0; i < mkeys.length; i++) {
		var name;
		var key = mkeys[i];
		var names = outmoms[key];
		if(names.length == 1) {
			name = names[0];
		} else if(names.length == 2) {
			name = names.join(' and ');
		} else {
			names[-1] = 'and '+names[-1];
			name = names.join(', ');
		}
		node_lookup[key] = family_graph.nodes.length;
		//Outside extant slaves set
		charList.push({ID: key, mother: 0, father: 0, is_mother: true, dick: 0, vagina: 1, slaveName: name+"'s mother"});
	}

	var dkeys = Object.keys(outdads);
	for(var i = 0; i < dkeys.length; i++) {
		var name;
		var key = dkeys[i];
		var names = outdads[key];
		if(names.length == 1) {
			name = names[0];
		} else if(names.length == 2) {
			name = names.join(' and ');
		} else {
			names[-1] = 'and '+names[-1];
			name = names.join(', ');
		}
		node_lookup[key] = family_graph.nodes.length;
		//Outside extant slaves set
		charList.push({ID: key, mother: 0, father: 0, is_father: true, dick: 1, vagina: -1, slaveName: name+"'s father"});
	}

	var charHash = {};
	for(var i = 0; i < charList.length; i++) {
		charHash[charList[i].ID] = charList[i];
	}

	var related = {};
	var seen = {};
	var saveTree = {};
	function relatedTo(character, targetID, relIDs = {tree: {}, related: false}) {
		relIDs.tree[character.ID] = true;
		if(related[character.ID]) {
			relIDs.related = true;
			return relIDs;
		}
		if(character.ID == targetID) {
			relIDs.related = true;
		}
		if(seen[character.ID]) {
			return relIDs;
		}
		seen[character.ID] = true;
		if(character.mother != 0) {
			if(charHash[character.mother]) {
				relatedTo(charHash[character.mother], targetID, relIDs);
			}
		}
		if(character.father != 0) {
			if(charHash[character.father]) {
				relatedTo(charHash[character.father], targetID, relIDs);
			}
		}
		return relIDs;
	}
	if(filterID) {
		if(charHash[filterID]) {
			var relIDs = relatedTo(charHash[filterID], filterID);
			for(var k in relIDs.tree) {
				related[k] = true;
			}
			for(var i  = 0; i < charList.length; i++) {
				if(charHash[charList[i].ID]) {
					var pRelIDs = relatedTo(charHash[charList[i].ID], filterID);
					if(pRelIDs.related) {
						for(var k in pRelIDs.tree) {
							related[k] = true;
							if(saveTree[k]) {
								for(var k2 in saveTree[k].tree) {
									related[k2] = true;
								}
							}
						}
					}
					saveTree[charList[i].ID] = pRelIDs;
				}
			}
		}
	}

	for(var i  = 0; i < charList.length; i++) {
		var character = charList[i];
		var char_id = character.ID;
		if(character.mother == 0 && character.father == 0 && !kids[char_id]) {
			continue;
		}
		if(filterID && !related[char_id]) {
			continue;
		}
		node_lookup[char_id] = family_graph.nodes.length;
		var char_obj = {
			id: char_id,
			name: character.slaveName,
			dick: character.dick,
			unborn: !!unborn[char_id],
			vagina: character.vagina
		};
		if(kids[char_id]) {
			char_obj.is_mother = !!kids[char_id].mother;
			char_obj.is_father = !!kids[char_id].father;
		}	else {
			char_obj.is_mother = false;
			char_obj.is_father = false;
		}
		family_graph.nodes.push(char_obj);
	}

	for(var i = 0; i < charList.length; i++) {
		var character = charList[i];
		var char_id = character.ID;
		if(character.mother == 0 && character.father == 0 && !kids[char_id]) {
			continue;
		}
		if(filterID && !related[char_id]) {
			if(related[character.mother]) {
				console.log('wtf, mom');
			}
			if(related[character.father]) {
				console.log('wtf, dad');
			}
			continue;
		}
		if(typeof node_lookup[character.mother] != 'undefined') {
			var ltype;
			if(character.mother == character.father) {
				ltype = 'homologous';
			} else {
				ltype = 'maternal';
			}
			family_graph.links.push({
				type: ltype,
				target: node_lookup[char_id]*1,
				source: node_lookup[character.mother]*1
			});
		}
		if(character.mother == character.father) {
			continue;
		}
		if(typeof node_lookup[character.father] != 'undefined') {
			family_graph.links.push({
				type: 'paternal',
				target: node_lookup[char_id]*1,
				source: node_lookup[character.father]*1
			});
		}
	}
	return family_graph;
};

/*Old version. To use, do 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);
		}
	}
});

license: mit


code:
:: FResult [script]

// this is a port of the FResult widget
// it has been broken up into several functions, because it grew too long
// it has been wrapped in a closure so as not to polute the global namespace
// and so that nested functions are only evaluated once

window.FResult = (function() {
	"use strict";
	// we can't initialise our global variables on load, because SugarCube.State isn't initialised
	// instead, declare them and initialise on run time
	let V, incest_bonus;
	function FResult(slave) {
		V = State.variables;
		incest_bonus = V.arcologies[0].FSEgyptianRevivalist > 20 || V.arcologies[0].FSEgyptianRevivalistIncestPolicy === 1;

		calcUseWeights(slave);
		if (!slave.fuckdoll)
			calcNotFuckdoll(slave);
		else
			V.FResult += slave.fuckdoll/10;

		V.FResult += Math.max(0, slave.aphrodisiacs) * 2;

		if (slave.inflationType === "aphrodisiac")
			V.FResult += slave.inflation*4;

		if (slave.lactation > 0)
			V.FResult += 1;

		if (V.seeAge === 1)
			calcAge(slave);
		if (slave.fetish === "mindbroken")
			V.FResult = Math.trunc(V.FResult*0.4);
		else
			V.FResult = Math.trunc(V.FResult*0.7);

		if (slave.pregWeek < 0)
			V.FResult -= Math.trunc(V.FResult*slave.pregWeek/10); // reduced the most just after birth

		calcAmputation(slave);

		if (V.arcologies[0].FSHedonisticDecadence > 20)
			calcHedonismWeight(slave);
		if (V.FResult < 2) {
			if (supremeRaceP(slave))
				V.FResult = 0;
			else
				V.FResult = 2;
		}
	}

	function calcUseWeights(slave) {
		V.FResult = (3 - slave.anus)+(slave.muscles/30);
		if (slave.muscles < -95)
			V.FResult -= 5;
		else if (slave.muscle < -30)
			V.FResult -= 2;

		V.seed = V.oralUseWeight + V.vaginalUseWeight + V.analUseWeight;
		if (V.seed <= 0) return;

		V.FResult += (6+slave.tonguePiercing) * (V.oralUseWeight/V.seed) * (slave.oralSkill/30);
		if (slave.sexualFlaw === "cum addict")
			V.FResult += (V.oralUseWeight/V.seed) * (slave.oralSkill/30);
		if (canDoVaginal(slave)) {
			V.FResult += 6 * (V.vaginalUseWeight/V.seed) * (slave.vaginalSkill/30);
			V.FResult += (3 - slave.vagina);
			V.FResult += slave.vaginaLube;
		}
		if (canDoAnal(slave)) {
			V.FResult += 6 * (V.analUseWeight/V.seed) * (slave.analSkill/30);
			if (slave.sexualFlaw === "anal addict")
				V.FResult += (V.analUseWeight/V.seed) * (slave.analSkill/30);
			if (slave.inflationType === "aphrodisiac")
				V.FResult += (V.analUseWeight/V.seed) * (slave.inflation * 3);
		}
	}

	function calcWorksWithRelatives(slave) {
		V.slaves.forEach(islave => {
			if (isParentP(slave, islave) && sameAssignmentP(slave, islave)) {
				V.FResult += 1;
				if (incest_bonus) V.FResult += 1;
			}
			if (areSisters(slave, islave) > 0 && sameAssignmentP(slave, islave)) {
				V.FResult += 1;
				if (incest_bonus) V.FResult += 1;
			}
		});
	}

	function calcWorksWithRelativesVanilla(slave) {
		const fre = V.slaves.findIndex(s => {
			return haveRelationP(slave, s) && sameAssignmentP(slave, s);
		});
		if (fre !== -1) {
			V.FResult += 2;
			if (incest_bonus) V.FResult += 2;
		}
	}

	function calcWorksWithRelationship(slave) {
		const fre = V.slaves.findIndex(s => {
			return haveRelationshipP(slave, s) && sameAssignmentP(slave, s);
		});
		if (fre !== -1) V.FResult += 1;
	}

	function calcWorksWithRival(slave) {
		const en = V.slaves.findIndex(s => {
			return isRivalP(slave, s) && sameAssignmentP(slave, s);
		});
		if (en !== -1) V.FResult -= 1;
	}

	function calcHInjectionsDiet(slave) {
		if (slave.drugs === "male hormone injections" || slave.drugs === "female hormone injections")
			V.FResult -= 10;
		if (slave.diet === "XXY")
			V.FResult += 2;
		else if (slave.diet === "XY" || slave.diet === "XX")
			V.FResult += 1;
		else if (slave.diet === "cum production")
			V.FResult += 1;
	}
	function calcPreg(slave) {
		if (V.arcologies[0].FSRepopulationFocus > 20) {
			if (slave.preg > 10) V.FResult += 2;
			else V.FResult -= 2;
		} else if (V.arcologies[0].FSRestart > 20) {
			if (slave.bellyPreg >= 500 && slave.breedingMark === 1)
				V.FResult += 1;
			else if (slave.preg > 10)
				V.FResult -= 10;
			else
				V.FResult += 0;
		}
	}

	function calcRace(slave) {
		if (V.arcologies[0].FSSupremacist !== "unset" && supremeRaceP(slave))
			V.FResult -= (V.arcologies[0].FSSupremacist/5) + (V.arcologies[0].FSSupremacistLawME*10);
		if (V.arcologies[0].FSSubjugationist !== "unset" && inferiorRaceP(slave))
			V.FResult += (V.arcologies[0].FSSubjugationist/10) + (V.arcologies[0].FSSubjugationistLawME);
	}

	function calcSexAttributes(slave) {
		if (slave.clitPiercing > 2) V.FResult += 1;
		if (slave.fetishKnown === 1 && slave.fetishStrength > 60 && slave.fetish != "none")
			V.FResult += slave.fetishStrength/5;

		if (slave.attrKnown === 1) {
			V.FResult += Math.trunc(slave.attrXX/20);
			V.FResult += Math.trunc(slave.attrXY/20);
			if (slave.energy > 95) V.FResult += 3;
			else if (slave.energy > 80) V.FResult += 2;
			else if (slave.energy > 60) V.FResult += 1;
			else if (slave.energy <= 20) V.FResult -= 2;
			else if (slave.energy <= 40) V.FResult -= 1;
		}
		if (slave.sexualFlaw !== "none") V.FResult -= 2;
		if (slave.sexualQuirk !== "none") V.FResult += 2;
		if (slave.behavioralFlaw !== "none") V.FResult -= 2;
		if (slave.behavioralQuirk !== "none") V.FResult += 2;
	}

	function calcCareer(slave) {
		if (setup.whoreCareers.includes(slave.career))
			V.FResult += 1;
		else if (slave.oralCount + slave.analCount + slave.vaginalCount + slave.mammaryCount + slave.penetrativeCount > 1000)
			V.FResult += 1;
	}

	function calcSight(slave) {
		if (!canSee(slave)) V.FResult -= 3;
		else if (slave.eyes <= -1) {
			if (slave.eyewear !== "corrective glasses" && slave.eyewear !== "corrective contacts")
				V.FResult -= 1;
		} else if (slave.eyewear === "blurring glasses")
			V.FResult -= 1;
		else if (slave.eyewear === "blurring contacts")
			V.FResult -= 1;
	}

	function calcEgyptianBonus(slave) {
		if (V.racialVarieties === undefined) V.racialVarieties = [];
		V.seed = 0;
		V.racialVarieties.forEach(race => {
			if (slave.race === race) V.seed = 1;
		});
		if (V.seed === 0)
			V.racialVarieties.push(slave.race);
	}

	function calcYouthBonus(slave) {
		if (slave.visualAge < 30) {
			if (slave.actualAge > 30)
				V.FResult += 5; // experienced for her apparent age
			if (slave.physicalAge > 30)
				V.FResult -= slave.physicalAge/2; // too old :(
		}
	}

	function calcMatureBonus(slave) {
		if (slave.visualAge >= 30 && slave.actualAge >= 30 && slave.physicalAge < slave.visualAge)
			V.FResult += Math.min((slave.physicalAge - slave.visualAge) * 2, 20); // looks and acts mature, but has a body that just won't quit
	}

	function calcNotFuckdoll(slave) {
		if (V.familyTesting === 1 && totalRelatives(slave) > 0)
			calcWorksWithRelatives(slave);
		else if(!V.familyTesting && slave.relation !==0)
			calcWorksWithRelativesVanilla(slave);
		if (slave.relationship > 0) calcWorksWithRelationship(slave);
		if (slave.rivalry !== 0) calcWorksWithRival(slave);
		calcHInjectionsDiet(slave);
		calcPreg(slave);
		calcRace(slave);
		calcSexAttributes(slave);
		calcCareer(slave);
		calcSight(slave);
		if (V.arcologies[0].FSEgyptianRevivalist !== "unset")
			calcEgyptianBonus(slave);
		if (V.arcologies[0].FSYouthPreferentialist !== "unset")
			calcYouthBonus(slave);
		else if (V.arcologies[0].FSMaturityPreferentialist !== "unset")
			calcMatureBonus(slave);
	}

	function calcAge(slave) {
		if ((V.arcologies[0].FSRepopulationFocus !== "unset" || V.arcologies[0].FSGenderFundamentalist !== "unset") && slave.physicalAge === V.minimumSlaveAge && slave.physicalAge === V.fertilityAge && canGetPregnant(slave)) {
			V.FResult += 1;
			if (slave.birthWeek === 0) V.FResult += V.FResult;
			else if (slave.birthWeek < 4) V.FResult += 0.2*V.FResult;
		} else if (slave.physicalAge === V.minimumSlaveAge) {
			V.FResult += 1;
			if (slave.birthWeek === 0 ) V.FResult += 0.5*V.FResult;
			else if (slave.birthWeek < 4) V.FResult += 0.1*V.FResult;
		} else if (slave.physicalAge === V.fertilityAge && canGetPregnant(slave) && (V.arcologies[0].FSRepopulationFocus !== "unset" || V.arcologies[0].FSGenderFundamentalist !== "unset")) {
			V.FResult += 1;
			if (slave.birthWeek === 0)
				V.FResult += 0.5*V.FResult;
			else if (slave.birthWeek < 4)
				V.FResult += 0.1*V.FResult;
		}
	}

	function calcAmputation(slave) {
		switch(slave.amp) {
		case 0:
			break;
		case 1:
			V.FResult -= 2;
			break;
		case -2:
			break;
		case -5:
			break;
		default:
			V.FResult -= 1;
		}
	}

	function calcHedonismWeight(slave) {
		if (slave.weight < 10)
			V.FResult -= 2;
		else if (slave.weight > 190)
			V.FResult -= 5; // too fat
	}
	return FResult;
})();


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 >= 1000000) {
			if(slave.preg > 10) {
				return 'unfathomably distended, brimming with life';
			} else {
				return 'unfathomable';
			}
		} else 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 '';
	}
}

/* calculates and returns expected ovum count during conception*/
window.setPregType = function(actor) {
	/* IMHO rework is posssible. Can be more interesting to play, if this code will take in account more body conditions - age, fat, food, hormone levels, etc. */

	var ovum = 1;
	var fertilityStack = 0;	// adds an increasing bonus roll for stacked fertility drugs

	if(actor.broodmother < 1) { /* Broodmothers should be not processed here. Necessary now.*/
		if(typeof actor.readyOva == "number" && actor.readyOva != 0) {
			ovum = actor.readyOva;  /*just single override; for delayed impregnation cases */
		} else if(actor.ID == -1) {
			if(actor.birthMaster > 0) { // Predisposed to twins
				if(actor.fertDrugs == 1) {
					ovum += jsEither(1, 1, 2, 2, 2, 2, 3, 3);
				} else {
					ovum += jsEither(0, 0, 0, 1, 1, 1, 1, 1, 1, 2);
				}
				if(actor.forcedFertDrugs > 0) {
					ovum += jsEither(1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4);
				}
			} else {
				if(actor.fertDrugs == 1) {
					ovum += jsEither(0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3);
				} else {
					ovum += jsEither(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1);
				}
				if(actor.forcedFertDrugs > 0) {
					ovum += jsEither(0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4);
				}
			}
		} else if(actor.pregType == 0) {
			ovum += jsEither(0, 0, 0, 0, 0, 0, 0, 0, 0, 1);	//base chance for twins
			if(actor.hormones == 2) {
				ovum += jsEither(0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2);
				fertilityStack++;
			}
			if(actor.hormoneBalance >= 400) {
				ovum += jsEither(0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3);
				fertilityStack++;
			}
			if(State.variables.masterSuitePregnancyFertilitySupplements == 1 && ((actor.assignment == "serve in the master suite" || actor.assignment == "be your Concubine"))) {
				ovum += jsEither(0, 0, 0, 1, 1, 2, 2, 2, 3, 3);
				fertilityStack++;
				fertilityStack++;
			}
			if(actor.drugs == "super fertility drugs") {
				ovum += jsEither(1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5);
				fertilityStack++;
				fertilityStack++;
				fertilityStack++;
				fertilityStack++;
				fertilityStack++;
			} else if(actor.drugs == "fertility drugs") {
				ovum += jsEither(0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3);
				fertilityStack++;
			}
			if(State.variables.seeHyperPreg == 1) {
				if(actor.drugs == "super fertility drugs") {
					ovum += jsRandom(0, fertilityStack*2);
				} else {
					ovum += jsRandom(0, fertilityStack);
				}
			} else {
				ovum += jsRandom(0, fertilityStack);
				if(ovum > 12) {
					ovum = jsRandom(6,12);
				}
			}
		}
		return ovum;
	}
}


code:
:: RA JS [script]

window.ruleApplied = function(slave, ID) {
	if (!slave || !slave.currentRules)
		return null;
	return slave.currentRules.includes(ID);
};

window.ruleSlaveSelected = function(slave, rule) {
	if (!slave || !rule || !rule.selectedSlaves)
		return false;
	return rule.selectedSlaves.includes(slave.ID);
};

window.ruleSlaveExcluded = function(slave, rule) {
	if (!slave || !rule || !rule.excludedSlaves)
		return false;
	return rule.excludedSlaves.includes(slave.ID);
};

window.ruleAssignmentSelected = function(slave, rule) {
	if (!slave || !rule || (!rule.assignment && !rule.facility))
		return false;
	var assignment = rule.assignment.concat(expandFacilityAssignments(rule.facility));
	return assignment.includes(slave.assignment);
}

window.ruleAssignmentExcluded = function(slave, rule) {
	if (!slave || !rule || (!rule.excludeAssignment && !rule.excludeFacility))
		return false;
	var excludeAssignment = rule.excludeAssignment.concat(expandFacilityAssignments(rule.excludeFacility));
	return excludeAssignment.includes(slave.assignment);
}

window.hasSurgeryRule = function(slave, rules) {
	if (!slave || !rules || !slave.currentRules)
		return false;

	for (var d = rules.length-1; d >= 0; d--) {
		if (ruleApplied(slave, rules[d].ID)) {
			if (rules[d].autoSurgery > 0) {
				return true;
			}
		}
	}
	return false;
};

window.hasRuleFor = function(slave, rules, what) {
	if (!slave || !rules || !slave.currentRules)
		return false;

	for (var d = rules.length-1; d >= 0; d--) {
		if (ruleApplied(slave, rules[d].ID)) {
			if (rules[d][what] !== "no default setting") {
				return true;
			}
		}
	}
	return false;
};

window.hasHColorRule = function(slave, rules) {
	return hasRuleFor(slave, rules, "hColor");
}

window.hasHStyleRule = function(slave, rules) {
	return hasRuleFor(slave, rules, "hStyle");
};

window.hasEyeColorRule = function(slave, rules) {
	return hasRuleFor(slave, rules, "eyeColor");
};

window.lastPregRule = function(slave, rules) {
	if (!slave || !rules)
		return null;
	if (!slave.currentRules)
		return false;

	for (var d = rules.length-1; d >= 0; d--) {
		if (ruleApplied(slave, rules[d].ID)) {
			if (rules[d].preg == -1) {
				return true;
			}
		}
	}

	return null;
};

window.autoSurgerySelector = function(slave, ruleset)
{

	var appRules = ruleset.filter(function(rule){
			return (rule.autoSurgery == 1) && this.currentRules.contains(rule.ID);
		}, slave);

	var surgery = {eyes: "no default setting", lactation: "no default setting", cosmetic: "nds", accent: "no default setting", shoulders: "no default setting", shouldersImplant: "no default setting", boobs: "no default setting", hips: "no default setting", hipsImplant: "no default setting", butt: "no default setting", faceShape: "no default setting", lips: "no default setting", holes: "nds", bodyhair: "nds", hair: "nds", bellyImplant: "no default setting"};
	var i, key, ruleSurgery;

	for (i in appRules)
	{
		ruleSurgery = appRules[i].surgery;
		for (key in ruleSurgery)
		{
			if (ruleSurgery[key] != "no default setting" || ruleSurgery[key] != "nds")
			{
				surgery[key] = ruleSurgery[key];
			}
		}
	}

	return surgery;
}

window.mergeRules = function(rules) {
    var combinedRule = {};

    for (var i = 0; i < rules.length; i++) {
        for (var prop in rules[i]) {
            // A rule overrides any preceding ones if,
            //   * there are no preceding ones,
            //   * or it sets autoBrand,
            //   * or it does not set autoBrand and is not "no default setting"
            var applies = (
                combinedRule[prop] === undefined
                || (prop === "autoBrand" && rules[i][prop])
                || (prop !== "autoBrand" && rules[i][prop] !== "no default setting")
            );

            if (applies)
            {

            	//Objects in JS in operations "=" pass by reference, so we need a completely new object to avoid messing up previous rules.
            	if ("object" == typeof rules[i][prop] && "object" != typeof combinedRule[prop])
            		combinedRule[prop] = new Object();

            	//If we already have object - now we will process its properties, but object itself should be skipped.
            	if ("object" != typeof combinedRule[prop])
                	combinedRule[prop] = rules[i][prop];

            	/*Some properties of rules now have second level properties. We need to check it, and change ones in combinedRule. (Good example - growth drugs. Breasts, butt, etc...) */
            	if ( "object" == typeof rules[i][prop])
    	        {
	            	for (var subprop in rules[i][prop])
    	        	{
    	           		var subapplies = (
            	    	combinedRule[prop][subprop] === undefined
                			|| (rules[i][prop][subprop] !== "no default setting")
	            		);

		            	if (subapplies)
    		            	combinedRule[prop][subprop] = rules[i][prop][subprop];
    		        }

            	}
           	}

        }

    }

    return combinedRule;
}


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);
	}
});


code:
:: StoryJS [script]

/*config.history.tracking = false;*/
window.variableAsNumber = function(x, defaultValue, minValue, maxValue) {
	x = Number(x)
	if (x != x) {//NaN
		return defaultValue || 0;//In case the default value was not supplied.
	}
	if (x < minValue) {//Works even if minValue is undefined.
		return minValue;
	}
	if (x > maxValue) {//Works even if maxValue is undefined.
		return maxValue;
	}
	return x;
};
window.isSexuallyPure = function(slave) {
	if (!slave) {
		return null;
	}
	if (slave.vagina < 1 && slave.anus < 1 && !slave.analCount && !slave.vaginalCount && !slave.oralCount && !slave.mammaryCount && !slave.penetrativeCount) {
		return true;
	} else {
		return false;
	}
};
if (typeof interpolate == "undefined") {
	var interpolate = function(x0,y0,x1,y1,x) {
		if(x <= x0) {
			return y0;
		} else if(x >= x1) {
			return y1;
		} else {
			return (x - x0) * ((y1 - y0) / (x1 - x0)) + y0;
		}
	};
	window.interpolate = interpolate;
}
config.history.maxStates = 1;

config.saves.autosave = "autosave";
window.isFullyPotent = function(slave) {
	if (!slave) {
		return null;
	} else if (slave.dick > 0 && slave.balls > 0 && slave.hormoneBalance < 100 && slave.drugs !== 'hormone blockers') {
		return true;
	} else {
		return false;
	}
};

/* mousetrap v1.5.3 craig.is/killing/mice */
(function(C,r,g){function t(a,b,h){a.addEventListener?a.addEventListener(b,h,!1):a.attachEvent("on"+b,h)}function x(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return l[a.which]?l[a.which]:p[a.which]?p[a.which]:String.fromCharCode(a.which).toLowerCase()}function D(a){var b=[];a.shiftKey&&b.push("shift");a.altKey&&b.push("alt");a.ctrlKey&&b.push("ctrl");a.metaKey&&b.push("meta");return b}function u(a){return"shift"==a||"ctrl"==a||"alt"==a||
"meta"==a}function y(a,b){var h,c,e,g=[];h=a;"+"===h?h=["+"]:(h=h.replace(/\+{2}/g,"+plus"),h=h.split("+"));for(e=0;e<h.length;++e)c=h[e],z[c]&&(c=z[c]),b&&"keypress"!=b&&A[c]&&(c=A[c],g.push("shift")),u(c)&&g.push(c);h=c;e=b;if(!e){if(!k){k={};for(var m in l)95<m&&112>m||l.hasOwnProperty(m)&&(k[l[m]]=m)}e=k[h]?"keydown":"keypress"}"keypress"==e&&g.length&&(e="keydown");return{key:c,modifiers:g,action:e}}function B(a,b){return null===a||a===r?!1:a===b?!0:B(a.parentNode,b)}function c(a){function b(a){a=
a||{};var b=!1,n;for(n in q)a[n]?b=!0:q[n]=0;b||(v=!1)}function h(a,b,n,f,c,h){var g,e,l=[],m=n.type;if(!d._callbacks[a])return[];"keyup"==m&&u(a)&&(b=[a]);for(g=0;g<d._callbacks[a].length;++g)if(e=d._callbacks[a][g],(f||!e.seq||q[e.seq]==e.level)&&m==e.action){var k;(k="keypress"==m&&!n.metaKey&&!n.ctrlKey)||(k=e.modifiers,k=b.sort().join(",")===k.sort().join(","));k&&(k=f&&e.seq==f&&e.level==h,(!f&&e.combo==c||k)&&d._callbacks[a].splice(g,1),l.push(e))}return l}function g(a,b,n,f){d.stopCallback(b,
b.target||b.srcElement,n,f)||!1!==a(b,n)||(b.preventDefault?b.preventDefault():b.returnValue=!1,b.stopPropagation?b.stopPropagation():b.cancelBubble=!0)}function e(a){"number"!==typeof a.which&&(a.which=a.keyCode);var b=x(a);b&&("keyup"==a.type&&w===b?w=!1:d.handleKey(b,D(a),a))}function l(a,c,n,f){function e(c){return function(){v=c;++q[a];clearTimeout(k);k=setTimeout(b,1E3)}}function h(c){g(n,c,a);"keyup"!==f&&(w=x(c));setTimeout(b,10)}for(var d=q[a]=0;d<c.length;++d){var p=d+1===c.length?h:e(f||
y(c[d+1]).action);m(c[d],p,f,a,d)}}function m(a,b,c,f,e){d._directMap[a+":"+c]=b;a=a.replace(/\s+/g," ");var g=a.split(" ");1<g.length?l(a,g,b,c):(c=y(a,c),d._callbacks[c.key]=d._callbacks[c.key]||[],h(c.key,c.modifiers,{type:c.action},f,a,e),d._callbacks[c.key][f?"unshift":"push"]({callback:b,modifiers:c.modifiers,action:c.action,seq:f,level:e,combo:a}))}var d=this;a=a||r;if(!(d instanceof c))return new c(a);d.target=a;d._callbacks={};d._directMap={};var q={},k,w=!1,p=!1,v=!1;d._handleKey=function(a,
c,e){var f=h(a,c,e),d;c={};var k=0,l=!1;for(d=0;d<f.length;++d)f[d].seq&&(k=Math.max(k,f[d].level));for(d=0;d<f.length;++d)f[d].seq?f[d].level==k&&(l=!0,c[f[d].seq]=1,g(f[d].callback,e,f[d].combo,f[d].seq)):l||g(f[d].callback,e,f[d].combo);f="keypress"==e.type&&p;e.type!=v||u(a)||f||b(c);p=l&&"keydown"==e.type};d._bindMultiple=function(a,b,c){for(var d=0;d<a.length;++d)m(a[d],b,c)};t(a,"keypress",e);t(a,"keydown",e);t(a,"keyup",e)}var l={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",
20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",224:"meta"},p={106:"*",107:"+",109:"-",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},A={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"},z={option:"alt",command:"meta","return":"enter",
escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},k;for(g=1;20>g;++g)l[111+g]="f"+g;for(g=0;9>=g;++g)l[g+96]=g;c.prototype.bind=function(a,b,c){a=a instanceof Array?a:[a];this._bindMultiple.call(this,a,b,c);return this};c.prototype.unbind=function(a,b){return this.bind.call(this,a,function(){},b)};c.prototype.trigger=function(a,b){if(this._directMap[a+":"+b])this._directMap[a+":"+b]({},a);return this};c.prototype.reset=function(){this._callbacks={};this._directMap=
{};return this};c.prototype.stopCallback=function(a,b){return-1<(" "+b.className+" ").indexOf(" mousetrap ")||B(b,this.target)?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable};c.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)};c.init=function(){var a=c(r),b;for(b in a)"_"!==b.charAt(0)&&(c[b]=function(b){return function(){return a[b].apply(a,arguments)}}(b))};c.init();C.Mousetrap=c;"undefined"!==typeof module&&module.exports&&(module.exports=
c);"function"===typeof define&&define.amd&&define(function(){return c})})(window,document);

Mousetrap.bind("enter", function () {
	$("#story-caption #endWeekButton a.macro-link").trigger("click");
});
Mousetrap.bind("space", function () {
	$("#story-caption #nextButton a.macro-link").trigger("click");
});
Mousetrap.bind("c", function () {
	$("#story-caption #manageArcology a.macro-link").trigger("click");
});
Mousetrap.bind("p", function () {
	$("#story-caption #managePenthouse a.macro-link").trigger("click");
});
Mousetrap.bind("left", function () {
	$("#prevSlave a.macro-link").trigger("click");
	$("#prevRule a").trigger("click");
});
Mousetrap.bind("q", function () {
	$("#prevSlave a.macro-link").trigger("click");
	$("#prevRule a").trigger("click");
});
Mousetrap.bind("shift+left", function () {
	$("#firstRule a").trigger("click");
});
Mousetrap.bind("shift+q", function () {
	$("#firstRule a").trigger("click");
});
Mousetrap.bind("right", function () {
	$("#nextSlave a.macro-link").trigger("click");
	$("#nextRule a").trigger("click");
});
Mousetrap.bind("shift+right", function () {
	$("#lastRule a").trigger("click");
});
Mousetrap.bind("e", function () {
	$("#nextSlave a.macro-link").trigger("click");
	$("#nextRule a").trigger("click");
});
Mousetrap.bind("shift+e", function () {
	$("#lastRule a").trigger("click");
});
Mousetrap.bind("f", function () {
	$("#walkpast a.macro-link").trigger("click");
});
Mousetrap.bind("h", function () {
	$("#manageHG a.macro-link").trigger("click");
});
Mousetrap.bind("s", function () {
	$("#buySlaves a.macro-link").trigger("click");
});
Mousetrap.bind("a", function () {
	$("#managePA a.macro-link").trigger("click");
});
Mousetrap.bind("b", function () {
	$("#manageBG a.macro-link").trigger("click");
});
Mousetrap.bind("u", function () {
	$("#manageRecruiter a.macro-link").trigger("click");
});
Mousetrap.bind("o", function () {
	$("#story-caption #optionsButton a.macro-link").trigger("click");
});
Mousetrap.bind("y", function () {
	$("#story-caption #policyButton a.macro-link").trigger("click");
});
Mousetrap.bind("f", function () {
	$("#story-caption #FSButton a.macro-link").trigger("click");
});
Mousetrap.bind("t", function () {
	$("#story-caption #PAOButton a.macro-link").trigger("click");
});
Mousetrap.bind("v", function () {
	$("#story-caption #URButton a.macro-link").trigger("click");
});
Mousetrap.bind("r", function () {
	$("#RAButton a.macro-link").trigger("click");
});
Mousetrap.bind("x", function () {
	$("#story-caption #managePerson a.macro-link").trigger("click");
});
Mousetrap.bind("z", function () {
	$("#story-caption #SFMButton a.macro-link").trigger("click");
});
Mousetrap.bind("d", function () {
	$("#story-caption #edictButton a.macro-link").trigger("click");
});
Mousetrap.bind("shift+s", function () {
	$("#story-caption #securityHQ a.macro-link").trigger("click");
});
Mousetrap.bind("shift+a", function () {
	$("#story-caption #secBarracks a.macro-link").trigger("click");
});
Mousetrap.bind("shift+h", function () {
	$("#story-caption #propHub a.macro-link").trigger("click");
});
Mousetrap.bind("shift+r", function () {
	$("#story-caption #riotCenter a.macro-link").trigger("click");
});
/**
 * BoobGenerator namespace.
 */
if (typeof BoobGenerator == "undefined") {
	var BoobGenerator = {
		rollBreast: function (modif) {
			var volume = [0, 300, 500, 650, 800, 1000, 1200, 1400, 1600, 1800, 2050, 2300, 2600, 2900, 3250, 3600, 3950, 4300, 4700, 5100, 5500, 5900];
			var volume_dist = [90000, 470000, 720000, 840000, 908574, 947759, 970151, 982946, 990258, 994436, 996824, 998188, 998968, 999414, 999669, 999814, 999897, 999945, 999972, 999987, 999995, 1000000];
			var randomRoll = Math.floor(Math.random() * 1000000) + 1
			var actualSize = 0
			while (randomRoll > volume_dist[actualSize]) {
				actualSize = actualSize + 1
			}
			var minorSizeAdjustment = 0
			if (Math.random()<.5) {
				minorSizeAdjustment = (Math.floor(Math.random() * 2) + 1) * 50
			}
			var volResult = volume[actualSize] + minorSizeAdjustment + modif
			if (volResult < 0) {volResult = 0}
			return volResult
		}
	};
	// Raise namespace scope to Global.
	window.BoobGenerator = BoobGenerator;
};

/**
 * Slave checker namespace.
 */
if (typeof SlaveStatsChecker == "undefined") {
	var SlaveStatsChecker = {
		checkForLisp: function (slave) {
				/* Begin mod section: toggle whether slaves lisp. */
				if (State && State.variables.disableLisping == 1) {
					return false;
				}
				/* End mod section: toggle whether slaves lisp. */
				return ((slave.lips > 70) || (slave.lipsPiercing + slave.tonguePiercing > 2))
			}
	};
	// Raise namespace scope to Global.
	window.SlaveStatsChecker = SlaveStatsChecker;
};

window.removeFromArray = function(arr, val) {
	for (var i = 0; i < arr.length; i++) {
		if (val == arr[i])
			return arr.splice(i,1);
	}
	return null;
};

window.filterInPlace = function(arr, callback, thisArg) {
	var j = 0;

	arr.forEach(function(e, i) {
		if (callback.call(thisArg, e, i, arr))
			arr[j++] = e;
	});

	arr.length = j;
	return arr;
};

if (typeof FertilityAge == "undefined") {
	var FertilityAge = {
		setAge: function (age) {
			age = Number(age)
				if (age != age) {
					return 13;
				} else {
				return age
			}
		}
	};
	// Raise namespace scope to Global.
	window.FertilityAge = FertilityAge;
};

window.canGetPregnant = function(slave) {
	if (!slave) {
		return null;
	} else if (slave.preg == -1) { /* contraceptives check */
		return false;
	} else if (isFertile(slave) == false) { /* check other fertility factors */
		return false;
	} else if ((slave.ovaries == 1) && (canDoVaginal(slave) == true)) {
		return true;
	} else if ((slave.mpreg == 1) && (canDoAnal(slave) == true)) { /* pregmod */
		return true;
	} else {
		return false;
	}
};

/* pregmod: are slave2's sperm compatible with slave1's eggs? */
window.canBreed = function(slave1, slave2) {
	if (!slave1 || !slave2) {
		return null;
	} else if (slave1.eggType == slave2.ballType) {
		return true;
	} else {
		return false;
	}
};

/* assuming slave1 is fertile, could slave2 impregnate slave1? slave2 must have dick and balls with compatible sperm; both slaves must not be in chastity; slave2 need not achieve erection */
window.canImpreg = function(slave1, slave2) {
	if (!slave1 || !slave2) {
		return null;
	} else if (slave2.dick < 1) {
		return false;
	} else if (slave2.balls < 1) {
		return false;
	} else if (slave2.dickAccessory == "chastity") {
		return false;
	} else if (slave2.dickAccessory == "combined chastity") {
		return false;
	} else if (slave2.pubertyXY == 0) { /* pregmod start */
		return false;
	} else if (slave2.vasectomy == 1) {
		return false;
	} else if (canBreed(slave1, slave2) == false) {
		return false;                   /* pregmod end */
	} else if (canGetPregnant(slave1) == false) { /* includes chastity checks */
		return false;
	} else {
		return true;
	}
};

/* contraceptives (.preg == -1) do not negate this function */
window.isFertile = function(slave) {
	if (!slave) {
		return null;
	}

	WombInit(slave);

	if (slave.womb.length > 0 || slave.broodmother > 0) { /* currently pregnant or broodmother */
		return false;
	} else if (slave.preg < -1) { /* sterile */
		return false;
	} else if (slave.pregWeek < 0) { /* postpartum */
		return false;
	} else if (slave.pubertyXX == 0) { /* pregmod start */
		return false;
	} else if (slave.ovaryAge >= 47) {
		return false;
	} else if (slave.inflation != 0) {
		return false;
	} else if (slave.bellyImplant != -1) {
		return false;
	} else if (slave.mpreg == 1) {
		return true;                   /* pregmod end */
	} else if (slave.ovaries == 1) {
		return true;
	} else {
		return false;
	}
};

window.isPlayerFertile = function(PC) {
	if (!PC) {
		return null;
	} else if (PC.preg != 0) { /* currently pregnant, sterile, menopausal or on contraceptives */
		return false;
	} else if (PC.pregWeek < 0) { /* postpartum */
		return false;
	} else if (PC.vagina == 1) {
		return true;
	} else {
		return false;
	}
};

window.canAchieveErection = function(slave) {
	if (!slave) {
		return null;
	} else if (slave.dick < 7 && slave.dick > 0 && slave.drugs !== 'hormone blockers' && (slave.balls > 0 ? slave.hormoneBalance < 100 : slave.hormoneBalance <= -100)) {
		return true;
	} else {
		return false;
	}
};

window.canPenetrate = function(slave) {
	if (!slave) {
		return null;
	} else if (canAchieveErection(slave) == false) {
		return false;
	} else if (slave.dickAccessory == "chastity") {
		return false;
	} else if (slave.dickAccessory == "combined chastity") {
		return false;
	} else if (slave.dick > 7) {
		return false;
	}
	return true;
};

window.canSee = function(slave) {
	if (!slave) {
		return null;
	} else if (slave.eyes > -2) {
		return true;
	} else {
		return false;
	}
};

window.canWalk = function(slave) {
	if (!slave) {
		return null;
	} else if (slave.amp == 1)  {
		return false;
	} else if (tooFatSlave(slave)) {
		return false;
	} else if (tooBigBreasts(slave)) {
		return false;
	} else if (tooBigDick(slave)) {
		return false;
	} else if (tooBigBalls(slave)) {
		return false;
	} else if (tooBigButt(slave)) {
		return false;
	} else if (tooBigBelly(slave)) {
		return false;
	} else if (slave.heels == 0) {
		return true;
	} else if (slave.shoes == "heels") {
		return true;
	} else if (slave.shoes == "extreme heels") {
		return true;
	} else if (slave.shoes == "boots") {
		return true;
	} else {
		return false;
	}
};

window.canTalk = function(slave) {
	if (!slave) {
		return null;
	} else if (slave.accent > 2)  {
		return false;
	} else if (slave.voice == 0) {
		return false;
	} else if (slave.lips > 95) {
		return false;
	} else if (slave.collar == "dildo gag") {
		return false;
	} else if (slave.collar == "massive dildo gag") {
		return false;
	} else if (slave.collar == "ball gag") {
		return false;
	} else if (slave.collar == "bit gag") {
		return false;
	} else {
		return true;
	}
};

window.canDoAnal = function(slave) {
	if (!slave) {
		return null;
	} else if (slave.vaginalAccessory == "anal chastity") {
		return false;
	} else if (slave.dickAccessory == "anal chastity") {
		return false;
	} else if (slave.vaginalAccessory == "combined chastity") {
		return false;
	} else if (slave.dickAccessory == "combined chastity") {
		return false;
	}
	return true;
};

window.canDoVaginal = function(slave) {
	if (!slave) {
		return null;
	} else if (slave.vagina < 0) {
		return false;
	} else if (slave.vaginalAccessory == "chastity belt") {
		return false;
	} else if (slave.vaginalAccessory == "combined chastity") {
		return false;
	}
	return true;
};

window.tooFatSlave = function(slave){
	if (!slave) {
		return null;
	} else if (slave.weight > 190+(slave.muscles/5) && slave.physicalAge >= 18) {
		return true;
	} else if (slave.weight > 130+(slave.muscles/20) && slave.physicalAge <= 3) {
		return true;
	} else if (slave.weight > 160+(slave.muscles/15) && slave.physicalAge <= 12) {
		return true;
	} else if (slave.weight > 185+(slave.muscles/10) && slave.physicalAge < 18) {
		return true;
	} else {
		return false;
	}
};

window.tooBigBreasts = function(slave){
	if (!slave) {
		return null;
	} else if (slave.boobs > 30000+(slave.muscles*100) && slave.physicalAge >= 18) {
		return true;
	} else if (slave.boobs > 5000+(slave.muscles*10) && slave.physicalAge <= 3) {
		return true;
	} else if (slave.boobs > 10000+(slave.muscles*20) && slave.physicalAge <= 12) {
		return true;
	} else if (slave.boobs > 20000+(slave.muscles*50) && slave.physicalAge < 18) {
		return true;
	} else {
		return false;
	}
};

window.tooBigBelly = function(slave){
	if (!slave) {
		return null;
	} else if (slave.belly >= 450000+(slave.muscles*2000) && slave.physicalAge >= 18) {
		return true;
	} else if (slave.belly >= 350000+(slave.muscles*1000) && slave.physicalAge >= 13) {
		return true;
	} else if (slave.belly >= 150000+(slave.muscles*500) && slave.physicalAge <= 3) {
		return true;
	} else if (slave.belly >= 250000+(slave.muscles*800) && slave.physicalAge <= 12) {
		return true;
	} else {
		return false;
	}
};

window.tooBigBalls = function(slave){
	if (!slave) {
		return null;
	} else if (slave.balls >= 30+(slave.muscles*.3) && slave.physicalAge <= 3) {
		return true;
	} else if (slave.balls >= 60+(slave.muscles*.5) && slave.physicalAge <= 12) {
		return true;
	} else if (slave.balls >= 90+(slave.muscles*.7)) {
		return true;
	} else {
		return false;
	}
};

window.tooBigDick = function(slave){
	if (!slave) {
		return null;
	} else if (slave.dick >= 15+(slave.muscles*.1) && slave.physicalAge <= 3 && slave.dick !== 0) {
		return true;
	} else if (slave.dick >= 30+(slave.muscles*.3) && slave.physicalAge <= 12) {
		return true;
	} else if (slave.dick >= 68+(slave.muscles*.4)) {
		return true;
	} else {
		return false;
	}
};

window.tooBigButt = function(slave){
	if (!slave) {
		return null;
	} else if (slave.butt > 10 && slave.physicalAge <= 3) {
		return true;
	} else if (slave.butt > 14 && slave.physicalAge <= 12) {
		return true;
	} else {
		return false;
	}
};

window.relationTargetWord = function(slave) {
	if (!slave) {
		return null;
	} else if (slave.relation == "daughter") {
		return "mother";
	} else if (slave.relation == "mother") {
		return "daughter";
	}
	return slave.relation;
};

/* intended to condense the clothing/toy/etc availability checks into something less asinine */
window.isItemAccessible = function(string) {
	if (State.variables.cheatMode === 1){
		return true;
	} else {
		switch(string) {
		case 'attractive lingerie for a pregnant woman':
			if ((State.variables.arcologies[0].FSRepopulationFocus > 0) || (State.variables.clothesBoughtMaternityLingerie === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'a bunny outfit':
			if ((State.variables.arcologies[0].FSGenderFundamentalist > 0) || (State.variables.clothesBoughtBunny === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'body oil':
			if ((State.variables.arcologies[0].FSPhysicalIdealist > 0) || (State.variables.clothesBoughtOil === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'chains':
			if ((State.variables.arcologies[0].FSDegradationist > 0) || (State.variables.clothesBoughtChains === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'a chattel habit':
			if ((State.variables.arcologies[0].FSChattelReligionist > 0) || (State.variables.clothesBoughtHabit === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'conservative clothing':
			if ((State.variables.arcologies[0].FSPaternalist > 0) || (State.variables.clothesBoughtConservative === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'harem gauze':
			if ((State.variables.arcologies[0].FSArabianRevivalist > 0) || (State.variables.clothesBoughtHarem === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'a huipil':
			if ((State.variables.arcologies[0].FSAztecRevivalist > 0) || (State.variables.clothesBoughtHuipil === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'a kimono':
			if ((State.variables.arcologies[0].FSEdoRevivalist > 0) || (State.variables.clothesBoughtKimono === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'a maternity dress':
			if ((State.variables.arcologies[0].FSRepopulationFocus > 0) || (State.variables.clothesBoughtMaternityDress === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'a slutty qipao':
			if ((State.variables.arcologies[0].FSChineseRevivalist > 0) || (State.variables.clothesBoughtQipao === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'stretch pants and a crop-top':
			if ((State.variables.arcologies[0].FSHedonisticDecadence > 0) || (State.variables.clothesBoughtLazyClothes === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'a toga':
			if ((State.variables.arcologies[0].FSRomanRevivalist > 0) || (State.variables.clothesBoughtToga === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'Western clothing':
			if ((State.variables.arcologies[0].FSPastoralist > 0) || (State.variables.clothesBoughtWestern === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'bowtie':
			if ((State.variables.arcologies[0].FSGenderFundamentalist > 0) || (State.variables.clothesBoughtBunny === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'ancient Egyptian':
			if ((State.variables.arcologies[0].FSEgyptianRevivalist > 0) || (State.variables.clothesBoughtEgypt === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'massive dildo gag':
			if (State.variables.toysBoughtGags === 1) {
				return true;
			} else {
				return false;
			}
			break;
		case 'a small empathy belly': case 'a medium empathy belly': case 'a large empathy belly': case 'a huge empathy belly':
			if ((State.variables.arcologies[0].FSRepopulationFocus > 0) || (State.variables.clothesBoughtBelly === 1)) {
				return true;
			} else {
				return false;
			}
			break;
		case 'long dildo': case 'long, large dildo': case 'long, huge dildo':
			if (State.variables.toysBoughtDildos === 1) {
				return true;
			} else {
				return false;
			}
			break;
		case 'long plug': case 'long, large plug': case 'long, huge plug':
			if (State.variables.toysBoughtButtPlugs === 1) {
				return true;
			} else {
				return false;
			}
			break;
		case 'tail':
			if (State.variables.toysBoughtButtPlugTails === 1) {
				return true;
			} else {
				return false;
			}
			break;
		default:
			return true;
			break;
		}
	}
};

window.expandFacilityAssignments = function(facilityAssignments) {
	var assignmentPairs = {
		"serve in the club": "be the DJ",
		"rest in the spa": "be the Attendant",
		"work in the brothel": "be the Madam",
		"work in the dairy": "be the Milkmaid",
		"work as a servant": "be the Stewardess",
		"get treatment in the clinic": "be the Nurse",
		"live with your Head Girl": "be your Head Girl",
		"serve in the master suite": "be your Concubine",
		"learn in the schoolroom": "be the Schoolteacher",
		"be confined in the cellblock": "be the Wardeness",
	};

	if (!facilityAssignments || !facilityAssignments.length)
		return [];
	var fullList = facilityAssignments.map(function(a) {
		if (a in assignmentPairs)
			return [a, assignmentPairs[a]];
		return a;
	});
	return fullList.flatten();
};

window.milkAmount = function(slave) {
	var milk;
	var calcs;
	if (!slave) {
		return null;
	} else {
		calcs = slave.boobs-slave.boobsImplant
		if (calcs > 40000) {
			milk = (158+((calcs-10000)/600))
		} else if (calcs > 25000) {
			milk = (128+((calcs-10000)/500))
		} else if (calcs > 10000) {
			milk = (78+((calcs-10000)/300))
		} else if (calcs > 5000) {
			milk = (53+((calcs-5000)/200))
		} else if (calcs > 2000) {
			milk = (29+((calcs-2000)/125))
		} else if (calcs > 800) {
			milk = (16+((calcs-800)/80))
		} else {
			milk = (8+((calcs-400)/50))
		}
		if (slave.lactation == 2) {
			milk *= 1.2
		}
		milk += (milk*((slave.devotion-50)/200))
		if (slave.boobsImplant > 200) {
			milk *= 0.9
		}
		calcs = (slave.hormoneBalance/50)
		if (slave.balls != 0 && calcs > -2) {
			calcs -= 1
		} else if (slave.ovaries != 1 && calcs < 2) {
			calcs += 1
		}
		milk *= (1+(calcs*0.1))
		milk *= (1+(slave.preg/100))
		milk *= (1+(slave.health/50))
		milk *= (1+(slave.weight/500))
		milk *= (1+(slave.lactationAdaptation/500))
		milk = Math.trunc(milk)
		milk = Math.clamp(milk,1,1000000000000000000)
		return milk
	}
};

window.cumAmount = function(slave) {
	var cum = 0;
	var calcs = 0;
	if (!slave) {
		return null;
	} else {
		if (slave.drugs == "testicle enhancement") {
			cum = ((slave.balls*3.5)+1)
		} else if (slave.drugs == "hyper testicle enhancement") {
			cum = ((slave.balls*5)+1)
		} else {
			cum = ((slave.balls*2.5)+1)
		}
		if (slave.diet == "cum production") {
			cum *= 1.2
		}
		calcs = (slave.hormoneBalance/50)
		cum *= (1-(calcs*0.1))
		if (slave.scrotum == 0) {
			cum *= 0.8
		}
		if (slave.prostate == 0) {
			cum *= 0.2 // being generous here
		} else if (slave.prostate == 2) {
			cum *= 1.2
		} else if (slave.prostate == 3) {
			cum *= 1.5
		}
		if (slave.devotion > 50) {
			cum += (cum*(slave.devotion/100))
		} else if (slave.devotion < -50) {
			cum += (cum*(slave.devotion/100))
		}
		if (slave.health > 50) {
			cum += (cum*(slave.health/50))
		} else if (slave.health < -50) {
			cum += (cum*(slave.health/50))
		}
		cum = Math.trunc(cum)
		cum = Math.clamp(cum,1,1000000000000000000)
		return cum
	}
};

window.nameReplace = function(name)
{
	name = name.replace("Sh", "Th");
	name = name.replace("SS", "Th");
	name = name.replace("Ss", "Th");
	name = name.replace("sS", "Th");
	name = name.replace("S", "Th");
	name = name.replace("aX", "aKTH");
	name = name.replace("eX", "eKTH");
	name = name.replace("iX", "iKTH");
	name = name.replace("oX", "oKTH");
	name = name.replace("uX", "uKTH");
	name = name.replace("yX", "yKTH");
	name = name.replace("AX", "AKTH");
	name = name.replace("EX", "EKTH");
	name = name.replace("IX", "IKTH");
	name = name.replace("OX", "OKTH");
	name = name.replace("UX", "UKTH");
	name = name.replace("YX", "YKTH");
	name = name.replace("Xa", "Tha");
	name = name.replace("Xe", "The");
	name = name.replace("Xi", "Thi");
	name = name.replace("Xo", "Tho");
	name = name.replace("Xu", "Thu");
	name = name.replace("Xy", "Thy");
	name = name.replace("XA", "THA");
	name = name.replace("XE", "THE");
	name = name.replace("XI", "THI");
	name = name.replace("XO", "THO");
	name = name.replace("XU", "THU");
	name = name.replace("XY", "THY");
	name = name.replace("X", "EKTH");
	name = name.replace("zZ", "Th");
	name = name.replace("Zz", "Th");
	name = name.replace("ZZ", "TH");
	name = name.replace("Z", "Th");
	name = name.replace("Ci", "Thi");
	name = name.replace("Ce", "The");
	name = name.replace("Cy", "Thy");
	name = name.replace("CI", "THI");
	name = name.replace("CE", "THE");
	name = name.replace("CY", "THY");
	name = name.replace("ss", "th");
	name = name.replace("sh", "th");
	name = name.replace("s", "th");
	name = name.replace("zz", "th");
	name = name.replace("z", "th");
	name = name.replace("ax", "akth");
	name = name.replace("ex", "ekth");
	name = name.replace("ix", "ikth");
	name = name.replace("ox", "okth");
	name = name.replace("ux", "ukth");
	name = name.replace("yx", "ykth");
	name = name.replace("Ax", "Akth");
	name = name.replace("Ex", "Ekth");
	name = name.replace("Ix", "Ikth");
	name = name.replace("Ox", "Okth");
	name = name.replace("Ux", "Ukth");
	name = name.replace("Yx", "Ykth");
	name = name.replace("xa", "tha");
	name = name.replace("xe", "the");
	name = name.replace("xi", "thi");
	name = name.replace("xo", "tho");
	name = name.replace("xu", "thu");
	name = name.replace("xy", "thy");
	name = name.replace("xA", "thA");
	name = name.replace("xE", "thE");
	name = name.replace("xI", "thI");
	name = name.replace("xO", "thO");
	name = name.replace("xU", "thU");
	name = name.replace("xY", "thY");
	name = name.replace("x", "ekth");
	name = name.replace("ci", "thi");
	name = name.replace("ce", "the");
	name = name.replace("cy", "thy");
	name = name.replace("cI", "thI");
	name = name.replace("cE", "thE");
	name = name.replace("cY", "thY");
	return name;
}

window.isVegetable = function(slave) {
	slave = slave || State.variables.activeSlave;
	if(!slave) { return false; }
	return (slave.fetish === 'mindbroken');
};

window.repGainSacrifice = function(slave, arcology) {
	slave = slave || State.variables.activeSlave;
	arcology = arcology || State.variables.arcologies[0];
	if(!slave || !arcology || arcology.FSAztecRevivalist === "unset" || arcology.FSAztecRevivalist <= 0) {
		return 0;
	}
	return Math.ceil(
		(Math.min(100, Math.pow(1.0926, State.variables.week - slave.weekAcquired)) + slave.prestige * 30)
			* arcology.FSAztecRevivalist / 100 / ((State.variables.slavesSacrificedThisWeek || 0) + 1));
};

window.bodyguardSuccessorEligible = function(slave) {
	if(!slave) { return false; }
	return (slave.devotion > 50 && slave.muscles >= 0 && slave.weight < 100 && slave.boobs < 8000 && slave.butt < 10 && slave.belly < 5000 && slave.balls < 10 && slave.dick < 10 && slave.preg < 20 && slave.fuckdoll == 0 && slave.fetish != "mindbroken" && canWalk(slave));
};

window.ngUpdateGenePool = function(genePool) {
  var transferredSlaveIds = (State.variables.slaves || [])
    .filter(function(s) { return s.ID >= 1200000; })
    .map(function(s) { return s.ID - 1200000; });
  return (genePool || [])
    .filter(function(s) { return transferredSlaveIds.indexOf(s.ID) >= 0; })
    .map(function(s) {
      var result = jQuery.extend(true, {}, s);
      result.ID += 1200000;
      return result;
    });
}

window.toJson = function(obj) {
    var jsontext = JSON.stringify(obj);
    jsontext = jsontext.replace(/^{/,"");
    jsontext = jsontext.replace(/}$/,"");
    return jsontext;
};

window.nippleColor = function(slave) {
	slave = slave || State.variables.activeSlave;
	if(slave.skin === 'tanned' || slave.skin === 'fair') {
		if(slave.preg > 10 || (slave.birthsTotal > 0 && slave.lactation > 0)) {
			return 'dark brown';
		} else {
			return 'pink';
		}
	} else if((slave.skin === 'pale' || slave.race === 'white')) {
		if(slave.preg > 10 || (slave.birthsTotal > 0 && slave.lactation > 0)) {
			return 'brown';
		} else {
			return 'pink';
		}
	} else if((slave.skin === 'brown' || slave.race === 'black')) {
		if(slave.preg > 10 || (slave.birthsTotal > 0 && slave.lactation > 0)) {
			return 'black';
		} else {
			return 'dark brown';
		}
	} else {
		if(slave.preg > 10 || (slave.birthsTotal > 0 && slave.lactation > 0)) {
			return 'dark brown';
		} else {
			return 'brown';
		}
	}
}

window.jsAlert = function(obj)
{
	alert(obj);
}

window.jsConsoleInfo = function(obj)
{
	console.info(obj);
}


code:
:: textinput [script]

Macro.add("textinput", {
    // Signifies that the macro is a container macro.
    tags: null,

    handler: function() {
        if (this.args.length < 2) {
            var errors = [];
            if (this.args.length < 1) { errors.push("variable name"); }
            if (this.args.length < 2) { errors.push("default 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");
        }

        var 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 '" + varName + "' is missing its sigil ($ or _)");
        }

        var that = this;
        var defaultValue = this.args[1];
        var el = document.createElement("textarea");

        // Setup and append the textarea element to the output buffer.
        jQuery(el)
            .attr({
                rows: 4,
                // cols: 68, // instead of setting "cols" we set the `min-width` in CSS
                tabindex: 0 // for accessiblity
            })
            .addClass("macro-textarea")  // "hijack" the .macro-textarea class
            .on("input", function() {
                Wikifier.setValue(varName, this.value);
                if (that.payload[0].contents !== "")
                    Wikifier.wikifyEval(that.payload[0].contents.trim());
            })
            .appendTo(this.output);

        // Set the story variable and textarea element to the default value.
        Wikifier.setValue(varName, defaultValue);

        // Ideally, we should be setting `.defaultValue` here, but IE doesn't support it,
        // so we have to use `.textContent`, which is equivalent.
        el.textContent = defaultValue;
    }
});


code:
:: textboxJS [script]

/* Nicked off greyelf, works for replace textboxes */
window.setReplaceTextboxMaxLength = function (storyVarName, maxLength) {
	var textboxId = '#textbox-' + Util.slugify(storyVarName);
	$(textboxId)
			.attr('maxlength', maxLength)
			.css({
				'min-width' : 'initial',
				width       : maxLength + 'em',
				padding		: '3px 2px'
			});
};

/* Nicked off TheMadExile, works for non-replace textboxes */
window.setTextboxMaxLength = function (storyVarName, maxLength) {
	var textboxId = '#textbox-' + Util.slugify(storyVarName);
	postdisplay[textboxId + '-maxlength'] = function (taskName) {
		delete postdisplay[taskName];
		$(textboxId)
			.attr('maxlength', maxLength)
			.css({
				'min-width' : 'initial',
				width       : maxLength + 'em',
				padding		: '3px 2px'
			});
	};
};