code:
:: UtilJS [script]

/*
 * Height.mean(nationality, race, genes, age) - returns the mean height for the given combination and age in years (>=2)
 * Height.mean(nationality, race, genes) - returns the mean adult height for the given combination
 * Height.mean(slave) - returns the mean (expected) height for the given slave
 *
 * Height.random(nationality, race, genes, age) - returns a random height for the given combination,
 *     with Gaussian distribution (mean = 1, standard deviation = 0.05) around the mean height
 * Height.random(nationality, race, genes) - returns a random height for the given combination of an adult, as above
 * Height.random(slave[, options]) - returns a random height for the given slave, as above.
 *                                   The additional options object can modify how the values are generated
 *                                   in the same way setting them as global configuration would, but only for this
 *                                   specific generation.
 *
 *                                   Example: Only generate above-average heights based on $activeSlave:
 *                                   Height.random($activeSlave, {limitMult: [0, 5]})
 *
 * Height.forAge(height, age, genes) - returns the height adapted to the age and genes
 * Height.forAge(height, slave) - returns the height adapted to the slave's age and genes
 *
 * Height.config(configuration) - configures the random height generator globally and returns the current configuration
 *   The options and their default values are:
 *   limitMult: [-3, 3] - Limit to the values the underlying (normal) random generator returns.
 *                        In normal use, the values are almost never reached; only 0.27% of values are
 *                        outside this range and need to be regenerated. With higher skew (see below),
 *                        this might change.
 *   spread: 0.05 - The random values generated are multiplied by this and added to 1 to generate
 *                  the final height multiplier. The default value together with the default limitMult
 *                  means that the generated height will always fall within (1 - 0.05 * 3) = 85% and
 *                  (1 + 0.05 * 3) = 115% of the mean height.
 *                  Minimum value: 0.001; maximum value: 0.5
 *   skew: 0 - How much the height distribution skews to the right (positive) or left (negative) side
 *             of the height.
 *             Minimum value: -1000, maximum value: 1000
 *   limitHeight: [0, 999] - Limit the resulting height range. Warning: A small height limit range
 *                           paired with a high spread value results in the generator having to
 *                           do lots of work generating and re-generating random heights until
 *                           one "fits".
 *
 * Anon's explination:
 *limitMult: [0, -30]
 *
 *This specifies a range going up from 0 to -30. It needs to go [-30, 0] instead. Same thing with [0, -5] two lines down. note: technically, this isn't true, because for some bizarre reason Height.random reverses the numbers for you if you get them wrong. But it's important to establish good habits, so.
 *
 *Skew, spread, limitMult: These are statistics things. BTW, a gaussian distribution is a normal distribution. Just a naming thing.
 *
 *Skew: See https://brownmath.com/stat/shape.htm#Skewness. Basically a measure of how asymmetrical the curve is. It doesn't change the mean, and can't move the main mass of the distribution far from the mean! The normal distribution can't do that. You can, however, use it to fatten one or the other tail.
 *
 *Spread: Changes the average distance from the mean, making the graph wider and shorter. Moves "mass" from the center to the tail. It's basically standard deviation, but named funny because FC codebase.
 *
 *limitMult: A clamp, expressed in z-score. (z=1 is one standard dev above mean, for ex.) If it excludes too much of the distribution the other parameters describe, you're going to spend lots of CPU making and throwing away heights. Don't worry about this unless you run into it.
 *
 *There's also limitHeight which you're not using. It's basically limitMult in different units.
 */
window.Height = (function(){
	'use strict';

	// Global configuration (for different game modes/options/types)
	var minMult = -3.0;
	var maxMult = 3.0;
	var skew = 0.0;
	var spread = 0.05;
	var minHeight = 0;
	var maxHeight = 999;

	// Configuration method for the above values
	const _config = function(conf) {
		if(_.isUndefined(conf)) {
			return {limitMult: [minMult, maxMult], limitHeight: [minHeight, maxHeight], skew: skew, spread: spread};
		}
		if(_.isFinite(conf.skew)) { skew = Math.clamp(conf.skew, -1000, 1000); }
		if(_.isFinite(conf.spread)) { spread = Math.clamp(conf.spread, 0.001, 0.5); }
		if(_.isArray(conf.limitMult) && conf.limitMult.length === 2 && conf.limitMult[0] !== conf.limitMult[1]
			&& _.isFinite(conf.limitMult[0]) && _.isFinite(conf.limitMult[1])) {
			minMult = Math.min(conf.limitMult[0], conf.limitMult[1]);
			maxMult = Math.max(conf.limitMult[0], conf.limitMult[1]);
		}
		if(_.isArray(conf.limitHeight) && conf.limitHeight.length === 2 && conf.limitHeight[0] !== conf.limitHeight[1]
			&& _.isFinite(conf.limitHeight[0]) && _.isFinite(conf.limitHeight[1])) {
			minHeight = Math.min(conf.limitHeight[0], conf.limitHeight[1]);
			maxHeight = Math.max(conf.limitHeight[0], conf.limitHeight[1]);
		}
		return {limitMult: [minMult, maxMult], limitHeight: [minHeight, maxHeight], skew: skew, spread: spread};
	}

	/* if you can find an average for an undefined, add it in! */
	const xxMeanHeight = {
		"American.white": 165, "American.black": 163.6, "American.latina": 158.9, "American.asian": 158.4, "American": 161.8,
		"Afghan": 163.8, "Algerian": 162, "Argentinian": 159.6, "Armenian": 158.1, "Australian": 161.8, "Austrian": 166,
		"Bangladeshi": 150.6, "Belarusian": 166.8, "Belgian": 168.1, "Bolivian": 142.2, "Brazilian": 158.8,
		"British": 161.9, "Burmese": undefined, "Canadian": 162.3, "Chilean": 157.2, "Chinese": 155.8, "Colombian": 158.7,
		"Zairian": 157.7, "Cuban": 156, "Czech": 167.22, "Danish": 168.7, "Dominican": 156.4, "Dutch": 169, "Egyptian": 158.9,
		"Emirati": 158.9, "Estonian": 165.5, "Ethiopian": 157.6, "Filipina": 151.8, "Finnish": 165.3, "French": 162.5,
		"German": 162.8, "Ghanan": 158.5, "Greek": 165, "Guatemalan": 147.3, "Haitian": 158.6, "Hungarian": 164,
		"Icelandic": 168, "Indian": 151.9, "Indonesian": 147, "Iranian": 157.2, "Iraqi": 155.8, "Irish": 163, "Israeli": 166,
		"Italian": 162.5, "Jamaican": 160.8, "Japanese": 158, "Jordanian": 158.2, "Kazakh": 159.8, "Kenyan": 159.4,
		"Korean": 156.15, "Lebanese": 165, "Libyan": 160.5, "Lithuanian": 167.5, "Malaysian": 154.7, "Malian": 160.4,
		"Mexican": 154, "Moroccan": 158.5, "Nepalese": 150.8, "Nigerian": 163.8, "Norwegian": 157.8, "Omani": undefined,
		"Pakistani": 151.9, "Peruvian": 151, "Polish": 165.1, "Portuguese": 165.1, "Puerto Rican": 158.9, "Romanian": 157,
		"Russian": 164.1, "Saudi": 156.3, "Scottish": 163, "Serbian": 166.8, "Slovak": 165.6, "South African": 159,
		"Spanish": 162.6, "Sudanese": 180.0, "Swedish": 166.8, "Swiss": 162.5, "Tanzanian": 156.6, "Thai": 159,
		"Tunisian": 160, "Turkish": 161.9, "Ugandan": 159.2, "Ukrainian": 164.8, "Uzbek": 159.9, "Venezuelan": 159,
		"Vietnamese": 155.2, "Yemeni": undefined, "a New Zealander": 164, "Zimbabwean": 160.3, "Sammarinese": undefined,
		"Marshallese": undefined, "Syrian": 156.3, "Bermudian": undefined, "Uruguayan": 158.0, "Monégasque": undefined,
		"Montenegrin": 168.4, "Cambodian": 152.4, "Cameroonian": 161.3, "Gabonese": 158.4, "Djiboutian": undefined,
		"Greenlandic": undefined, "Tuvaluan": undefined, "Zambian": 158.5, "Albanian": 161.8, "Bruneian": undefined,
		"Singaporean": 160.0, "Croatian": 166.3, "Bulgarian": 163.2, "Laotian": undefined, "Mongolian": 157.7, "Taiwanese": 160.4,
		"Belizean": undefined, "Grenadian": undefined, "I-Kiribati": undefined, "Malagasy": 154.3, "Maldivian": undefined,
		"Bosnian": 171.1, "Kosovan": undefined, "Macedonian": undefined, "Honduran": 152.0, "Maltese": 159.9,
		"Nauruan": undefined, "Micronesian": undefined, "Costa Rican": undefined, "Salvadoran": 160.3, "Nicaraguan": 153.7,
		"Panamanian": 152.0, "Nigerien": 157.8, "Andorran": undefined, "Bulgarian": 163.2, "Luxembourgian": 164.8,
		"Moldovan": 161.2, "Antiguan": 156.8, "Aruban": 158, "Azerbaijani": 162.4, "Bahamian": 157.1, "Bahraini": 165.2,
		"Barbadian": 158.5, "Bhutanese": 153.4, "Burundian": 157, "a Cook Islander": 162.3, "Cypriot": 163.4,
		"Dominiquais": 157.2, "East Timorese": 149.1, "Ecuadorian": 153.4, "Fijian": 161.8, "French Guianan": 157,
		"Georgian": 160.5, "Guyanese": 157.9, "Kuwaiti": 156.1, "Kyrgyz": 158.6, "Latvian": 165.7, "a Liechtensteiner": 164.3,
		"Niuean": 160.4, "Palauan": 161.4, "Palestinian": 158.2, "Papua New Guinean": 150.7, "Paraguayan": 158.3, "Qatari": 155,
		"Kittitian": 156.9, "Saint Lucian": 157.1, "Vincentian": 156.2, "Samoan": 161, "Seychellois": 155.8,
		"Slovene": 167.4, "a Solomon Islander": 151.8, "Sri Lankan": 151.4, "Surinamese": 155.7, "Tajik": 161.2, "Tongan": 159.5,
		"Trinidadian": 157.4, "Turkmen": 158.2, "Ni-Vanuatu": 149.8, "Vatican": 162.5, "Angolan": undefined, "Beninese": 159.3,
		"Bissau-Guinean": undefined, "Burkinabé": 161.6, "Cape Verdean": undefined, "Catalan": undefined, "Central African": 158.9,
		"Chadian": 162.6, "Comorian": 154.8, "Congolese": 159, "Equatoguinean": undefined, "Eritrean": undefined,
		"French Polynesian": undefined, "Gambian": 157.8, "Guinean": 158.8, "Ivorian": 158.9, "Kurdish": undefined, "Liberian": 157.3,
		"Malawian": 155, "Mauritanian": undefined, "Mauritian": undefined, "Mosotho": 157.6, "Motswana": undefined, "Mozambican": 156,
		"Namibian": 160.7, "Rwandan": 157.7, "Sahrawi": undefined, "São Toméan": undefined, "Senegalese": 163, "Sierra Leonean": undefined,
		"Somali": undefined, "South Sudanese": undefined, "Swazi": 159.1, "Tibetan": undefined, "Togolese": 159,
		"": 162.5 // default
	};
	const xyMeanHeight = {
		"American.white": 178.2, "American.black": 177.4, "American.latina": 172.5, "American.asian": 172.5, "American": 176.4,
		"Afghan": undefined, "Algerian": 172.2, "Argentinian": 174.46, "Armenian": undefined, "Australian": 175.6,
		"Austrian": 179, "Bangladeshi": 150.8, "Belarusian": 176.9, "Belgian": 178.7, "Bolivian": 160, "Brazilian": 170.7,
		"British": 175.3, "Burmese": 168.0, "Canadian": 175.1, "Chilean": 169.6, "Chinese": 167.1, "Colombian": 170.6,
		"Zairian": 158.9, "Cuban": 168, "Czech": 180.31, "Danish": 180.4, "Dominican": 168.4, "Dutch": 181, "Egyptian": 170.3,
		"Emirati": 170.3, "Estonian": 179.1, "Ethiopian": undefined, "Filipina": 163.5, "Finnish": 178.9, "French": 175.6,
		"German": 175.4, "Ghanan": 169.5, "Greek": 177, "Guatemalan": 157.5, "Haitian": undefined, "Hungarian": 176,
		"Icelandic": 181, "Indian": 164.7, "Indonesian": 158, "Iranian": 170.3, "Iraqi": 165.4, "Irish": 177, "Israeli": 177,
		"Italian": 176.5, "Jamaican": 171.8, "Japanese": 172, "Jordanian": undefined, "Kazakh": 169, "Kenyan": 170.0,
		"Korean": 168.15, "Lebanese": 176, "Libyan": 171.3, "Lithuanian": 177.2, "Malaysian": 166.3, "Malian": 171.3,
		"Mexican": 167, "Moroccan": 172.7, "Nepalese": 163, "Nigerian": 163.8, "Norwegian": 179.63, "Omani": undefined,
		"Pakistani": 164.7, "Peruvian": 164, "Polish": 178.7, "Portuguese": 173.9, "Puerto Rican": 172.5, "Romanian": 172,
		"Russian": 177.2, "Saudi": 168.9, "Scottish": 177.6, "Serbian": 182, "Slovak": 179.4, "South African": 168,
		"Spanish": 173.1, "Sudanese": 190.0, "Swedish": 181.5, "Swiss": 178.2, "Tanzanian": undefined, "Thai": 170.3,
		"Tunisian": 172.3, "Turkish": 173.6, "Ugandan": undefined, "Ukrainian": 176.5, "Uzbek": 175.4, "Venezuelan": 169,
		"Vietnamese": 165.7, "Yemeni": 159.9, "a New Zealander": 177, "Zimbabwean": undefined, "Sammarinese": undefined,
		"Marshallese": undefined, "Syrian": 173.0, "Bermudian": undefined, "Uruguayan": 170.0, "Monégasque": undefined,
		"Montenegrin": 183.2, "Cambodian": 162.5, "Cameroonian": 170.6, "Gabonese": undefined, "Djiboutian": undefined,
		"Greenlandic": undefined, "Tuvaluan": undefined, "Zambian": undefined, "Albanian": 174.0, "Bruneian": undefined,
		"Singaporean": 170.6, "Andorran": undefined, "Croatian": 180.5, "Bulgarian": 175.2, "Laotian": 160.5,
		"Mongolian": 168.4, "Taiwanese": 171.4, "Belizean": undefined, "Grenadian": undefined, "I-Kiribati": undefined,
		"Malagasy": 161.5, "Maldivian": undefined, "Bosnian": 183.9, "Kosovan": undefined, "Macedonian": undefined,
		"Honduran": undefined, "Maltese": 169.9, "Nauruan": undefined, "Micronesian": undefined, "Costa Rican": undefined,
		"Salvadoran": undefined, "Nicaraguan": undefined, "Panamanian": 165.0, "Nigerien": 163.8, "Luxembourgian": 179.9,
		"Moldovan": undefined, ".white": 177.6, "Antiguan": 164.8, "Aruban": 165.1, "Azerbaijani": 171.8, "Bahamian": 167.2, "Bahraini": 165.1,
		"Barbadian": 169.3, "Bhutanese": 167.2, "Burundian": 164.1, "a Cook Islander": 173.4, "Cypriot": 177.2,
		"Dominiquais": 168.1, "East Timorese": 161.2, "Ecuadorian": 167.5, "Fijian": 170.4, "French Guianan": 168,
		"Georgian": 172.7, "Guyanese": 168.6, "Kuwaiti": 169.2, "Kyrgyz": 170.4, "Latvian": 174.2, "a Liechtensteiner": 175.4,
		"Niuean": 169.7, "Palauan": 174.6, "Palestinian": 169.7, "Papua New Guinean": 163.5, "Paraguayan": 168.8, "Qatari": 166.2,
		"Kittitian": 164.4, "Saint Lucian": 168.3, "Vincentian": 165.4, "Samoan": 173, "Seychellois": 168.5,
		"Slovene": 180.3, "a Solomon Islander": 163.1, "Sri Lankan": 163.6, "Surinamese": 165.2, "Tajik": 175.2, "Tongan": 168.2,
		"Trinidadian": 170.8, "Turkmen": 171.4, "Ni-Vanuatu": 160.5, "Vatican": 176.5, "Angolan": undefined, "Beninese": undefined,
		"Bissau-Guinean": undefined, "Burkinabé": undefined, "Cape Verdean": undefined, "Catalan": undefined, "Central African": undefined,
		"Chadian": undefined, "Comorian": undefined, "Congolese": undefined, "Equatoguinean": undefined, "Eritrean": undefined,
		"French Polynesian": undefined, "Gambian": 168, "Guinean": undefined, "Ivorian": undefined, "Kurdish": undefined, "Liberian": undefined,
		"Malawian": 166, "Mauritanian": undefined, "Mauritian": undefined, "Mosotho": undefined, "Motswana": undefined, "Mozambican": undefined,
		"Namibian": undefined, "Rwandan": undefined, "Sahrawi": undefined, "São Toméan": undefined, "Senegalese": undefined, "Sierra Leonean": undefined,
		"Somali": undefined, "South Sudanese": undefined, "Swazi": undefined, "Tibetan": undefined, "Togolese": undefined,
		"": 172.5 // defaults
	};

	// Helper method - table lookup for nationality/race combinations
	const nationalityMeanHeight = function(table, nationality, race, def) {
		return table[nationality + "." + race] || table[nationality] || table["." + race] || table[""] || def;
	};

	// Helper method - generate two independent Gaussian numbers using Box-Muller transform
	const gaussianPair = function() {
		let r = Math.sqrt(-2.0 * Math.log(1 - Math.random()));
		let sigma = 2.0 * Math.PI * (1 - Math.random());
		return [r * Math.cos(sigma), r * Math.sin(sigma)];
	};

	// Helper method: Generate a skewed normal random variable with the skew s
	// Reference: http://azzalini.stat.unipd.it/SN/faq-r.html
	const skewedGaussian = function(s) {
		let randoms = gaussianPair();
		if(s === 0) {
			// Don't bother, return an unskewed normal distribution
			return randoms[0];
		}
		let delta = s / Math.sqrt(1 + s * s);
		let result = delta * randoms[0] + Math.sqrt(1 - delta * delta) * randoms[1];
		return randoms[0] >= 0 ? result : -result;
	};

	// Height multiplier generator; skewed gaussian according to global parameters
	const multGenerator = function() {
		let result = skewedGaussian(skew);
		while(result < minMult || result > maxMult) {
			result = skewedGaussian(skew);
		}
		return result;
	};

	// Helper method: Generate a height based on the mean one and limited according to config.
	const heightGenerator = function(mean) {
		let result = mean * (1 + multGenerator() * spread);
		while(result < minHeight || result > maxHeight) {
			result = mean * (1 + multGenerator() * spread);
		}
		return result;
	};

	// Helper method - apply age and genes to the adult height
	const applyAge = function(height, age, genes) {
		if(!_.isFinite(age) || age < 2 || age >= 20) {
			return height;
		}
		let minHeight = 0, midHeight = 0, midAge = 15;
		switch(genes) {
			case 'XX': // female
			case 'XXX': // Triple X syndrome female
				minHeight = 85; midHeight = height * 157 / 164; midAge = 13;
				break;
			case 'XY': // male
			case 'XXY': // Kinefelter syndrome male
			case 'XYY': // XYY syndrome male
				minHeight = 86; midHeight = height * 170 / 178; midAge = 15;
				break;
			case 'X0': case 'X': // Turner syndrome female
				minHeight = 85 * 0.93; midHeight = height * 157 / 164; midAge = 13;
				break;
			default:
				minHeight = 85.5, midHeight = height * 327 / 342, midAge = 14;
				break;
		}
		if(age > midAge) {
			// end of puberty to 20
			return interpolate(midAge, midHeight, 20, height, age);
		} else {
			// 2 to end of puberty
			return interpolate(2, minHeight, midAge, midHeight, age);
		}
	};

	const _meanHeight = function(nationality, race, genes, age) {
		if(_.isObject(nationality)) {
			// We got called with a single slave as the argument
			return _meanHeight(nationality.nationality, nationality.race, nationality.genes, nationality.physicalAge + nationality.birthWeek / 52.0);
		}
		let result = 0;
		switch(genes) {
			case 'XX': // female
				result = nationalityMeanHeight(xxMeanHeight, nationality, race);
				break;
			case 'XY': // male
				result = nationalityMeanHeight(xyMeanHeight, nationality, race);
				break;
			// special cases. Extra SHOX genes on X and Y chromosomes make for larger people
			case 'X0': case 'X': // Turner syndrome female
				result = nationalityMeanHeight(xxMeanHeight, nationality, race) * 0.93;
				break;
			case 'XXX': // Triple X syndrome female
				result = nationalityMeanHeight(xxMeanHeight, nationality, race) * 1.03;
				break;
			case 'XXY': // Kinefelter syndrome male
				result = nationalityMeanHeight(xyMeanHeight, nationality, race) * 1.03;
				break;
			case 'XYY': // XYY syndrome male
				result = nationalityMeanHeight(xyMeanHeight, nationality, race) * 1.04;
				break;
			case 'Y': case 'Y0': case 'YY': case 'YYY':
				console.log("Height.mean(): non-viable genes " + genes);
				break;
			default:
				console.log("Height.mean(): unknown genes " + genes + ", returning mean between XX and XY");
				result = nationalityMeanHeight(xxMeanHeight, nationality, race) * 0.5
					+ nationalityMeanHeight(xyMeanHeight, nationality, race) * 0.5;
				break;
		}
		return applyAge(result, age, genes);
	};

	const _randomHeight = function(nationality, race, genes, age) {
		const mean = _meanHeight(nationality, race, genes, age);
		// If we got called with a slave object and options, temporarily modify
		// our configuration.
		if(_.isObject(nationality) && _.isObject(race)) {
			const currentConfig = _config();
			_config(race);
			const result = heightGenerator(mean);
			_config(currentConfig);
			return result;
		}
		return heightGenerator(mean);
	};

	const _forAge = function(height, age, genes) {
		if(_.isObject(age)) {
			// We got called with a slave as a second argument
			return applyAge(height, age.physicalAge + age.birthWeek / 52.0, age.genes);
		} else {
			return applyAge(height, age, genes);
		}
	};

	return {
		mean: _meanHeight,
		random: _randomHeight,
		forAge: _forAge,
		config: _config,
	};
})();

if(!Array.prototype.findIndex) {
	Array.prototype.findIndex = function(predicate) {
		if (this == null) {
			throw new TypeError('Array.prototype.find called on null or undefined');
		}
		if (typeof predicate !== 'function') {
			throw new TypeError('predicate must be a function');
		}
		var list = Object(this);
		var length = list.length >>> 0;
		var thisArg = arguments[1];
		var value;

		for (var i = 0; i < length; i++) {
			value = list[i];
			if (predicate.call(thisArg, value, i, list)) {
				return i;
			}
		}
		return -1;
	};
};

/*
A categorizer is used to "slice" a value range into distinct categories in an efficient manner.

If the values are objects their property named 'value' will be set to whatever
the value used for the choice was. This is important for getters, where it can be accessed
via this.value.

--- Example ---
Original SugarCube code
<<if _Slave.muscles > 95>>
	Musc++
<<elseif _Slave.muscles > 30>>
	Musc+
<<elseif _Slave.muscles > 5>>
	Toned
<<elseif _Slave.muscles > -6>>
<<elseif _Slave.muscles > -31>>
	@@.red;weak@@
<<elseif _Slave.muscles > -96>>
	@@.red;weak+@@
<<else>>
	@@.red;weak++@@
<</if>>

As a categorizer
<<if ndef $cats>><<set $cats = {}>><</if>>
<<if ndef $cats.muscleCat>>
	<!-- This only gets set once, skipping much of the code evaluation, and can be set outside of the code in an "init" passage for further optimization -->
	<<set $cats.muscleCat = new Categorizer([96, 'Musc++'], [31, 'Musc+'], [6, 'Toned'], [-5, ''], [-30, '@@.red;weak@@'], [-95, '@@.red;weak+@@'], [-Infinity, '@@.red;weak++@@'])>>
<</if>>
<<print $cats.muscleCat.cat(_Slave.muscles)>>
*/
window.Categorizer = function() {
	this.cats = Array.prototype.slice.call(arguments)
		.filter(function(e, i, a) {
			return e instanceof Array && e.length == 2 && typeof e[0] === 'number' && !isNaN(e[0])
				&& a.findIndex(function(val) { return e[0] === val[0]; }) === i; /* uniqueness test */ })
		.sort(function(a, b) { return b[0] - a[0]; /* reverse sort */ });
};
window.Categorizer.prototype.cat = function(val, def) {
	var result = def;
	if(typeof val === 'number' && !isNaN(val)) {
		var foundCat = this.cats.find(function(e) { return val >= e[0]; });
		if(foundCat) {
			result = foundCat[1];
		}
	}
	// Record the value for the result's getter, if it is an object
	// and doesn't have the property yet
	if(result === Object(result)) {
		result['value'] = val;
	}
	return result;
};

window.commaNum = function(s) {
	if(!s) { return 0; }
	if(State.variables.formatNumbers != 1) { return s; }
	return s.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

window.cashFormat = function(s) {
	if(!s) { s = 0; }
	return "¤"+commaNum(s);
}

window.isFloat = function(n){
    return n === +n && n !== (n|0);
}
window.isInt = function(n) {
    return n === +n && n === (n|0);
}
window.numberWithCommas = function(x) {
    return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

window.jsRandom = function(min,max) {
    return Math.floor(Math.random()*(max-min+1)+min);
}

window.jsRandomMany = function (arr, count) {
	var result = [];
	var _tmp = arr.slice();
	for (var i = 0; i < count; i++) {
		var index = Math.ceil(Math.random() * 10) % _tmp.length;
		result.push(_tmp.splice(index, 1)[0]);
	}
	return result;
}

//This function wants an array - which explains why it works like array.random(). Give it one or you'll face a NaN
window.jsEither = function(choices) {
	var index = Math.floor(Math.random() * choices.length);
	return choices[index];
}

//This function is alternative to clone - usage needed if nested objects present. Slower but result is separate object tree, not with reference to source object.
window.deepCopy = function (o) {
   var output, v, key;
   output = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       output[key] = (typeof v === "object") ? deepCopy(v) : v;
   }
   return output;
}

/*
Make everything waiting for this execute. Usage:

let doSomething = function() {
	... your initialization code goes here ...
};
if(typeof Categorizer === 'function') {
	doSomething();
} else {
	jQuery(document).one('categorizer.ready', doSomething);
}
*/
jQuery(document).trigger('categorizer.ready');

window.hashChoice = function hashChoice(obj) {
	let randint = Math.floor(Math.random()*hashSum(obj));
	let ret;
	Object.keys(obj).some(key => {
		if (randint < obj[key]) {
			ret = key;
			return true;
		} else {
			randint -= obj[key];
			return false;
		}
	});
	return ret;
};

window.hashSum = function hashSum(obj) {
	let sum = 0;
	Object.keys(obj).forEach(key => { sum += obj[key]; });
	return sum;
};

window.arr2obj = function arr2obj(arr) {
	const obj = {};
	arr.forEach(item => { obj[item] = 1; });
	return obj;
};

window.hashPush = function hashPush(obj, ...rest) {
	rest.forEach(item => {
		if (obj[item] === undefined) obj[item] = 1;
		else obj[item] += 1;
	});
};

window.weightedArray2HashMap = function weightedArray2HashMap(arr) {
	const obj = {};
	arr.forEach(item => {
		if (obj[item] === undefined) obj[item] = 1;
		else obj[item] += 1;
	})
	return obj;
};


code:
:: Revamped Art Control [script]

class ArtStyleEntry {
	constructor(name) {
		this.name = name;
	}

	toString()
	{
		var keys = Object.keys(this);
		var styleArray = [];
		if (keys !== null && keys !== undefined)
		{
			var styleObj = this;
			keys.forEach(function(key) {
				if(key !== "name") {
					var value = styleObj[key];
					styleArray.push(key + ":" + value);
				}
			});

			var styleValue = styleArray.join(";") + ";";

			var cssDef = "{" + styleValue + "}";

			if (this.name.length > 0)
				cssDef = "." + this.name + cssDef;

			return cssDef;
		}

		return "";
	}
}

class ArtStyleControl {
	constructor(artDisplayClass, artSlave) {
		this.artDisplayClass = artDisplayClass;
        this.artSlave = artSlave;
		this.styles = [];

		this.initDefaultValues();
		this.applyMakeup();
		this.applyCalcMuscleTone();
		this.applyEyeLensStyle();
    }

	applyMakeup() {
		var lipsColor = this.lips.fill;
		var lipsOpacity = this.lips["fill-opacity"];

		switch(this.artSlave.makeup)
		{
			case 1:
				//Nice
				lipsColor = "#ff69b4";
				lipsOpacity = 0.5;
				break;
			case 2:
				//Gorgeous
				lipsColor = "#8b008b";
				lipsOpacity = 0.7;
				break;
			case 3:
				//Hair coordinated
				lipsColor = this.hair.fill;
				lipsOpacity = 0.3;
				break;
			case 4:
				//Slutty
				lipsColor = "#ff0000";
				lipsOpacity = 1;
				break;
			case 5:
				//Neon
				lipsColor = "#DC143C";
				lipsOpacity = 1;
				break;
			case 6:
				//Neon hair coordinated
				lipsColor = this.hair.fill;
				lipsOpacity = 1;
				break;
			case 7:
				//Metallic
				lipsColor = "#b22222";
				lipsOpacity = 0.7;
				break;
			case 8:
				//Metallic hair coordinated
				lipsColor = this.hair.fill;
				lipsOpacity = 0.7;
				break;
			default:
		}

		this.lips.fill = lipsColor;
		this.lips["fill-opacity"] = lipsOpacity;
	}

	applyCalcMuscleTone() {
		var musclesValue = this.artSlave.muscles + 100;
		var musclesVisibility = 0.910239*Math.log(0.02*musclesValue);

		this.muscleTone["fill-opacity"] = musclesVisibility;
	}

	applyEyeLensStyle() {
		if (this.artSlave.eyeColor == null || this.artSlave.eyeColor == undefined)
			return;

		var eyeColorSplit = this.artSlave.eyeColor.split(" ");

		if (eyeColorSplit == 1)
			return;

		var eyeLens = eyeColorSplit[0];

		switch(eyeLens)
		{
			case "demonic":
				this.eyeball.fill = this.iris.fill;
				break;
			case "devilish":
				this.eyeball.fill = "#010101";
		}
	}

	parseColorFromName(colorName) {
		if (colorName == null)
			return "#000000";

		var colorPalette = [
			["auburn", "#7e543e"],
			["black", "#3F4040"],
			["blonde", "#F4F1A3"],
			["blue", "#4685C5"],
			["brown", "#8D4F21"],
			["burgundy", "#5f3946"],
			["chestnut", "#663622"],
			["chocolate", "#6e4937"],
			["copper", "#a16145"],
			["dark brown", "#463325"],
			["ginger", "#da822d"],
			["golden", "#ffdf31"],
			["green", "#5FBA46"],
			["grey", "#9e9fa4"],
			["hazel", "#8d6f1f"],
			["pink", "#D18CBC"],
			["platinum blonde", "#fcf3c1"],
			["red", "#BB2027"],
			["silver", "#cdc9c6"],
			["strawberry-blonde", "#e5a88c"],
			["blazing red", "#E00E2B"],
			["neon green", "#25d12b"],
			["neon blue", "#2284C3"],
			["neon pink", "#cc26aa"],
			["demonic", "#aa0000"],
			["devilish", "#ffd42a"],
			["hypnotic", "#ff5599"],
			["catlike", "#555555"],
			["serpent-like", "#555555"],
			["heart-shaped", "#555555"],
			["wide-eyed", "#555555"],
			["almond-shaped", "#555555"],
			["bright", "#555555"],
			["teary", "#555555"],
			["vacant", "#555555"],
		];

		var colorPaletteMap = new Map(colorPalette);
		colorName = colorName.toLowerCase();
		var colorValue = colorPaletteMap.get(colorName);
		if (!colorValue) {
			if (extractHairColor !== undefined) {
				return extractHairColor(colorName)
			}
			else {
				return colorName;
			}
		}

		return colorValue;
	}

	getLipsColorBySkinColorName(colorName) {
		if (colorName == null)
			return "#000000";

		var lipsPalette = [
			["light", "#ce6876"],
			["white", "#ce6876"],
			["fair", "#ce6876"],
			["lightened", "#ce6876"],
			["extremely pale", "#ffb9ca"],
			["pale", "#ffb9ca"],
			["tanned", "#9e4c44"],
			["natural", "#9e4c44"],
			["olive", "#c1a785"],
			["light brown", "#5d2f1b"],
			["dark", "#714536"],
			["brown", "#714536"],
			["black", "#403030"],
			["camouflage patterned", "#708050"],
			["red dyed", "#b04040"],
			["dyed red", "#b04040"],
			["green dyed", "#A0C070"],
			["dyed green", "#A0C070"],
			["blue dyed", "#5080b0"],
			["dyed blue", "#5080b0"],
			["tiger striped", "#e0d050"]
		];

		var lipsPaletteMap = new Map(lipsPalette);
		colorName = colorName.toLowerCase();
		var colorValue = lipsPaletteMap.get(colorName);
		if (!colorValue) {
			return this.parseColorFromName(colorName);
		}

		return colorValue;
	}

	parseSkinColorFromName(colorName) {
		if (colorName == null)
			return "#000000";

		var skinPalette = [
			["light", "#feebe5"],
			["white", "#feebe5"],
			["fair", "#feebe5"],
			["lightened", "#feebe5"],
			["extremely pale", "#fff8f8"],
			["pale", "#f9ebf0"],
			["tanned", "#e8b693"],
			["natural", "#e8b693"],
			["olive", "#d4c6bb"],
			["light brown", "#a46237"],
			["dark", "#845c44"],
			["brown", "#845c44"],
			["black", "#463a3a"],
			["camouflage patterned", "#78875a"],
			["red dyed", "#bc4949"],
			["dyed red", "#bc4949"],
			["green dyed", "#A6C373"],
			["dyed green", "#A6C373"],
			["blue dyed", "#5b8eb7"],
			["dyed blue", "#5b8eb7"],
			["tiger striped", "#e2d75d"]
		];

		var skinPaletteMap = new Map(skinPalette);
		colorName = colorName.toLowerCase();
		var colorValue = skinPaletteMap.get(colorName);
		if (!colorValue) {
			return this.parseColorFromName(colorName);
		}

		return colorValue;
	}

	initDefaultValues() {
		this.white = new ArtStyleEntry("white");
		this.white.fill = "#FFFFFF";

		this.skin = new ArtStyleEntry("skin");
		this.skin.fill = this.parseSkinColorFromName(this.artSlave.skin);

		this.arm = new ArtStyleEntry("arm.skin");
		this.arm.fill = this.skin.fill;

		this.head = new ArtStyleEntry("head");
		this.head.fill = this.skin.fill;

		this.torso = new ArtStyleEntry("torso");
		this.torso.fill = this.skin.fill;

		this.boob = new ArtStyleEntry("boob");
		this.boob.fill = this.skin.fill;

		this.penis = new ArtStyleEntry("penis");
		this.penis.fill = this.skin.fill;

		this.scrotum = new ArtStyleEntry("scrotum");
		this.scrotum.fill = this.skin.fill;

		this.areola = new ArtStyleEntry("areola");
		this.areola.fill = this.getLipsColorBySkinColorName(this.artSlave.skin);

		this.labia = new ArtStyleEntry("labia");
		this.labia.fill = this.areola.fill;

		this.hair = new ArtStyleEntry("hair");
		this.hair.fill = this.parseColorFromName(this.artSlave.hColor);

		this.shoe = new ArtStyleEntry("shoe");
		this.shoe.fill = "#3E65B0";

		this.smartPiercing = new ArtStyleEntry("smart_piercing");
		this.smartPiercing.fill = "#4DB748";

		this.steelPiercing = new ArtStyleEntry("steel_piercing");
		this.steelPiercing.fill = "#c0c6c7";

		this.steelChastity = new ArtStyleEntry("steel_chastity");
		this.steelChastity.fill = "#c0c6c7";

		this.labiaSkin = new ArtStyleEntry("labia.skin");
		this.labiaSkin.fill = this.skin.fill;

		this.outfitBase = new ArtStyleEntry("outfit_base");
		this.outfitBase.fill = "#515351";

		this.shadow = new ArtStyleEntry("shadow");
		this.shadow.fill = "#010101";

		this.muscleTone = new ArtStyleEntry("muscle_tone");
		this.muscleTone.fill = "#010101";
		this.muscleTone["fill-opacity"] = 1;

		this.glasses = new ArtStyleEntry("glasses");
		this.glasses.fill = "#010101";

		this.lips = new ArtStyleEntry("lips");
		this.lips.fill = this.getLipsColorBySkinColorName(this.artSlave.skin);
		this.lips["fill-opacity"] = 0.6;

		this.eyeball = new ArtStyleEntry("eyeball");
		this.eyeball.fill = "#dfdfdf";

		this.iris = new ArtStyleEntry("iris");
		this.iris.fill = this.parseColorFromName(this.artSlave.eyeColor);

		this.highlight1 = new ArtStyleEntry("highlight1");
		this.highlight1.fill = "#ffffff";
		this.highlight1["fill-opacity"] = 0.9;
		this.highlight1["mix-blend-mode"] = "soft-light";

		this.highlight2 = new ArtStyleEntry("highlight2");
		this.highlight2.fill = "#ffffff";
		this.highlight2["fill-opacity"] = 0.9;
		this.highlight2["mix-blend-mode"] = "soft-light";

		this.highlightStrong = new ArtStyleEntry("highlightStrong");
		this.highlightStrong.fill = "#ffffff";

		this.armpitHair = new ArtStyleEntry("armpit_hair");
		this.armpitHair.fill = this.parseColorFromName(this.artSlave.underArmHColor);;

		this.pubicHair = new ArtStyleEntry("pubic_hair");
		this.pubicHair.fill = this.parseColorFromName(this.artSlave.pubicHColor);

		this.bellyDetails = new ArtStyleEntry("belly_details");
		this.bellyDetails["fill-opacity"] = 1;

		this.bellySkin = new ArtStyleEntry("belly.skin");
		this.bellySkin.fill = this.skin.fill;

		this.bellyUpperSkin = new ArtStyleEntry("belly_upper.skin");
		this.bellyUpperSkin.fill = this.skin.fill;

		this.neckSkin = new ArtStyleEntry("neck.skin");
		this.neckSkin.fill = this.skin.fill;

		this.legsSkin = new ArtStyleEntry("legs.skin");
		this.legsSkin.fill = this.skin.fill;

		this.buttSkin = new ArtStyleEntry("butt.skin");
		this.buttSkin.fill = this.skin.fill;

		this.feetSkin = new ArtStyleEntry("feet.skin");
		this.feetSkin.fill = this.skin.fill;

		this.shadowBoobInnerLowerShadow = new ArtStyleEntry("shadow.boob_inner_lower_shadow");
		this.shadowBoobInnerLowerShadow["fill-opacity"] = 1;

		this.shadowBoobInnerUpperShadow = new ArtStyleEntry("shadow.boob_inner_upper_shadow");
		this.shadowBoobInnerUpperShadow["fill-opacity"] = 1;

		this.gag = new ArtStyleEntry("gag");
		this.gag.fill = "#bf2126";
	}

	get StylesCss() {
		var mainStyle = new ArtStyleEntry("");
		mainStyle.position = "absolute";
    	mainStyle.height= "100%";
    	mainStyle["margin-left"] = "auto";
    	mainStyle["margin-right"] = "auto";
    	mainStyle.left = "0";
    	mainStyle.right = "0";

		this.styles.push(this.white);
		this.styles.push(this.arm);
		this.styles.push(this.skin);
		this.styles.push(this.head);
		this.styles.push(this.torso);
		this.styles.push(this.boob);
		this.styles.push(this.penis);
		this.styles.push(this.scrotum);
		this.styles.push(this.areola);
		this.styles.push(this.labia);
		this.styles.push(this.hair);
		this.styles.push(this.shoe);
		this.styles.push(this.smartPiercing);
		this.styles.push(this.steelPiercing);
		this.styles.push(this.steelChastity);
		this.styles.push(this.labiaSkin);
		this.styles.push(this.outfitBase);
		this.styles.push(this.shadow);
		this.styles.push(this.muscleTone);
		this.styles.push(this.glasses);
		this.styles.push(this.lips);
		this.styles.push(this.eyeball);
		this.styles.push(this.iris);
		this.styles.push(this.highlight1);
		this.styles.push(this.highlight2);
		this.styles.push(this.highlightStrong);
		this.styles.push(this.armpitHair);
		this.styles.push(this.pubicHair);
		this.styles.push(this.bellyDetails);
		this.styles.push(this.bellySkin);
		this.styles.push(this.bellyUpperSkin);
		this.styles.push(this.neckSkin);
		this.styles.push(this.legsSkin);
		this.styles.push(this.buttSkin);
		this.styles.push(this.feetSkin);
		this.styles.push(this.shadowBoobInnerLowerShadow);
		this.styles.push(this.shadowBoobInnerUpperShadow);
		this.styles.push(this.gag);
		var stylesValues = [];

		var artDisplayClass = this.artDisplayClass;

		stylesValues.push("." + artDisplayClass + " " + mainStyle.toString());
		this.styles.forEach(function(style) {
			stylesValues.push("." + artDisplayClass + " " + style.toString());
		});

		return stylesValues.join(" ");
	}

}

class ClothingControl {
	get fuckdoll() {
		var defaultOutfitColor = "#515351";
		var areolaColor = "#383838";
		return {
			bodySettings: {
				showEyes: false,
				showPubic: false,
				showArmHair: false,
				showHair: false,
				showLegHighlight: true,
				showTorsoHighlight: true,
				showBoobsHighlight: true,
				showHeadHighlight: true
			},
			styleSettings: {
				skin: { fill: defaultOutfitColor },
				arm: { fill: defaultOutfitColor },
				penis: { fill: defaultOutfitColor },
				legsSkin: {fill: defaultOutfitColor },
				feetSkin: {fill: defaultOutfitColor },
				torso: {fill: defaultOutfitColor },
				buttSkin: {fill: defaultOutfitColor },
				head: { fill: defaultOutfitColor },
				neckSkin: { fill: defaultOutfitColor },
				boob: { fill: defaultOutfitColor },
				areola: { fill: areolaColor },
				labia: { fill: areolaColor },
				bellySkin: { fill: defaultOutfitColor },
				bellyUpperSkin: { fill: defaultOutfitColor },
				shoe: { fill: defaultOutfitColor },
				labiaSkin: { fill: defaultOutfitColor },
				lips: { fill: areolaColor }
			}
		}
	}

	get niceMaid() {
		return {
			bodySettings: {
				showPubic: false,
				showBoobs: false,
				showNipples: false,
				showPenis: false,
				showBalls: false,
				showBellyPiercings: false,
				showNipplesPiercings: false
			},
			styleSettings: {
				bellySkin: { fill: "#FFFFFF" },
				bellyUpperSkin: { fill: "#FFFFFF" },
				bellyDetails: { "fill-opacity": 0 }
			}
		}
	}

	get sluttyMaid() {
		return {
			bodySettings: {
				showBellyPiercings: false
			},
			styleSettings: {
				bellySkin: { fill: "#FFFFFF" },
				bellyDetails: { "fill-opacity": 0 }
			}
		}
	}

	get restrictiveLatex() {
		var defaultOutfitColor = "#515351";
		var areolaColor = "#383838";
		return {
			bodySettings: {
				showEyes: false,
				showPubic: false,
				showArmHair: false,
				showHair: false,
				showLegHighlight: true,
				showTorsoHighlight: true,
				showBoobsHighlight: true,
				showHeadHighlight: true,
				showHeadPiercings: false,
				showBellyPiercings: false
			},
			styleSettings: {
				skin: { fill: defaultOutfitColor },
				arm: { fill: defaultOutfitColor },
				legsSkin: {fill: defaultOutfitColor },
				feetSkin: {fill: defaultOutfitColor },
				torso: {fill: defaultOutfitColor },
				buttSkin: {fill: defaultOutfitColor },
				head: { fill: defaultOutfitColor },
				neckSkin: { fill: defaultOutfitColor },
				bellySkin: { fill: defaultOutfitColor },
				bellyUpperSkin: { fill: defaultOutfitColor },
				shoe: { fill: defaultOutfitColor }
			}
		}
	}

	get latexCatsuit() {
		var defaultOutfitColor = "#515351";
		var areolaColor = "#383838";
		return {
			bodySettings: {
				showPubic: false,
				showArmHair: false,
				showLegHighlight: true,
				showTorsoHighlight: true,
				showBoobsHighlight: true,
				showHeadHighlight: true,
				showPussyPiercings: false,
				showPenisPiercings: false,
				showBellyPiercings: false,
				showNipplesPiercings: false
			},
			styleSettings: {
				skin: { fill: defaultOutfitColor },
				scrotum: { fill: defaultOutfitColor },
				arm: { fill: defaultOutfitColor },
				penis: { fill: defaultOutfitColor },
				legsSkin: {fill: defaultOutfitColor },
				feetSkin: {fill: defaultOutfitColor },
				torso: {fill: defaultOutfitColor },
				buttSkin: {fill: defaultOutfitColor },

				neckSkin: { fill: defaultOutfitColor },
				boob: { fill: defaultOutfitColor },
				areola: { fill: areolaColor },
				labia: { fill: areolaColor },
				bellySkin: { fill: defaultOutfitColor },
				bellyUpperSkin: { fill: defaultOutfitColor },
				shoe: { fill: defaultOutfitColor },
				labiaSkin: { fill: defaultOutfitColor }
			}
		}
	}

	get bodyOil() {
		return {
			bodySettings: {
				showLegHighlight: true,
				showTorsoHighlight: true,
				showBoobsHighlight: true,
				showHeadHighlight:true,
			},
			styleSettings: {

			}
		}
	}

	get bodysuit() {
		var defaultOutfitColor = "#162d50";
		return {
			bodySettings: {
				showPubic: false,
				showArmHair: false,
				showPussy: false,
				showPussyPiercings: false,
				showPenisPiercings: false,
				showBellyPiercings: false,
				showNipplesPiercings: false
			},
			styleSettings: {
				skin: { fill: defaultOutfitColor },
				scrotum: { fill: defaultOutfitColor },
				arm: { fill: defaultOutfitColor },
				penis: { fill: defaultOutfitColor },
				legsSkin: {fill: defaultOutfitColor },
				feetSkin: {fill: defaultOutfitColor },
				torso: {fill: defaultOutfitColor },
				buttSkin: {fill: defaultOutfitColor },
				boob: { fill: defaultOutfitColor },
				areola: { fill: defaultOutfitColor },
				bellySkin: { fill: defaultOutfitColor },
				bellyUpperSkin: { fill: defaultOutfitColor },
				shoe: { fill: defaultOutfitColor },
				muscleTone: { "fill-opacity": 0 },
				bellyDetails: { "fill-opacity": 0 }
			}
		}
	}

	get leotard() {
		var defaultOutfitColor = "#d35f8d";
		return {
			bodySettings: {
				showPubic: false,
				showArmHair: false,
				showPussy: false,
				showPussyPiercings: false,
				showPenisPiercings: false,
				showBellyPiercings: false,
				showNipplesPiercings: false,
				showNipples: false
			},
			styleSettings: {
				skin: { fill: defaultOutfitColor },
				scrotum: { fill: defaultOutfitColor },
				penis: { fill: defaultOutfitColor },
				torso: {fill: defaultOutfitColor },
				boob: { fill: defaultOutfitColor },
				areola: { fill: defaultOutfitColor },
				bellySkin: { fill: defaultOutfitColor },
				bellyUpperSkin: { fill: defaultOutfitColor },
				muscleTone: { "fill-opacity": 0 },
				bellyDetails: { "fill-opacity": 0 }
			}
		}
	}
}

class RevampedArtControl {
	constructor(artDisplayClass, artSlave, globalShowHighlights, globalShowBodyMods) {

		this.artDisplayClass = artDisplayClass;
        this.artSlave = artSlave;
		this.styleControl = new ArtStyleControl(artDisplayClass, artSlave);
		this.clothingControl = new ClothingControl();
		this.artTransform = "";
		this.boobRightArtTransform = "";
		this.boobLeftArtTransform = "";
		this.boobOutfitArtTransform = "";

		this.showArmHair = true;
		this.showHair = true;
		this.showPussy = true;
		this.showPubic = true;
		this.showChastityVaginal = true;
		this.showChastityAnal = true;
		this.showBalls = true;
		this.showPenis = true;
		this.showNipples = true;
		this.showBoobs = true;
		this.showEyes = true;
		this.showMouth = true;

		this.leftArmType = this.getLeftArmPosition;
		this.rightArmType = this.getRightArmPosition;

		this.hairLength = this.getHairLength;
		this.torsoSize = this.getTorsoSize;
		this.bellyLevel = this.getBellyLevel;

		this.showLegHighlight = false;
		this.showTorsoHighlight = false;
		this.showBoobsHighlight = false;
		this.showHeadHighlight = false;

		this.showPussyPiercings = true;
		this.showBellyPiercings = true;
		this.showPenisPiercings = true;
		this.showNipplesPiercings = true;
		this.showHeadPiercings = true;

		this.pubicTattooText = "";

		var clothing = this.getClothing();
		if (clothing != null)
		{
			if (clothing.bodySettings != null)
				this.applyClothingSettings(clothing.bodySettings);
			if (clothing.styleSettings != null)
				this.applyClothingStyleSettings(clothing.styleSettings);
		}

		if (!globalShowHighlights)
		{
			this.showLegHighlight = false;
			this.showTorsoHighlight = false;
			this.showBoobsHighlight = false;
			this.showHeadHighlight = false;
		}
		if (!globalShowBodyMods)
		{
			this.showPussyPiercings = false;
			this.showBellyPiercings = false;
			this.showPenisPiercings = false;
			this.showNipplesPiercings = false;
			this.showHeadPiercings = false;
		}

    }

	copyProperties(objTo, objFrom) {
		for (var attr in objFrom) {
			if (objFrom.hasOwnProperty(attr))
				objTo[attr] = objFrom[attr];
		}
	}

	applyClothingSettings(clothingSettings) {
		this.copyProperties(this, clothingSettings);
	}

	applyClothingStyleSettings(styleSettings) {
		for (var attr in styleSettings) {
			if (styleSettings.hasOwnProperty(attr))
				this.copyProperties(this.styleControl[attr], styleSettings[attr]);
		}
	}

	getClothing() {
		if (this.artSlave.fuckdoll > 0)
			return this.clothingControl.fuckdoll;

		switch(this.artSlave.clothes)
		{
			case "a nice maid outfit":
				return this.clothingControl.niceMaid;
				break;
			case "a slutty maid outfit":
				return this.clothingControl.sluttyMaid;
				break;
			case "restrictive latex":
				return this.clothingControl.restrictiveLatex;
				break;
			case "a latex catsuit":
				return this.clothingControl.latexCatsuit;
				break;
			case "body oil":
				return this.clothingControl.bodyOil;
				break;
			case "a comfortable bodysuit":
				return this.clothingControl.bodysuit;
				break;
			case "a leotard":
				/*return this.clothingControl.leotard;*/
				break;
			default:
		}

		return null;
	}

	get getHairLength() {
		var result = "Short";

		if (this.artSlave.hLength >= 80)
			result = "Long";
		else if (this.artSlave.hLength >= 40)
			result = "Medium";
		else
			result = "Short";

		return result;
	}

	get getTorsoSize() {
		var torsoSize = "Normal";

		if (this.artSlave.waist < -40) {
			torsoSize = this.artSlave.weight > 30 ? "Hourglass" : "Unnatural";
		}
		else if (this.artSlave.waist <= 10) {
			torsoSize = this.artSlave.weight > 30 ? "Normal" : "Hourglass";
		}
		else {
			torsoSize = "Normal";
		}

		return torsoSize;
	}

	get getLeftArmPosition() {
		var leftArmType = "Low";

		if (this.artSlave.devotion > 50) {
			leftArmType = "High";
		}
		else if (this.artSlave.trust >= -20) {
			if (this.artSlave.devotion <= 20) {
				leftArmType = "Low";
			}
			else
			{
				leftArmType = "Mid";
			}
		}
		else {
			leftArmType = "Mid";
		}

		return leftArmType;
	}

	get getRightArmPosition() {
		var rightArmType = "Low";

		if (this.artSlave.devotion > 50) {
			rightArmType = "High";
		}
		else if (this.artSlave.trust >= -20) {
			if (this.artSlave.devotion <= 20) {
				rightArmType = "Low";
			}
			else
			{
				rightArmType = "High";
			}
		}
		else {
			rightArmType = "Mid";
		}

		return rightArmType;
	}

	get getBellyLevel() {
		var bellyLevel = 0;
		if (this.artSlave.belly >= 120000)
			bellyLevel = 9;
		else if (this.artSlave.belly >= 90000)
			bellyLevel = 8;
		else if (this.artSlave.belly >= 50000)
			bellyLevel = 7;
		else if (this.artSlave.belly >= 30000)
			bellyLevel = 6;
		else if (this.artSlave.belly >= 15000)
			bellyLevel = 5;
		else if (this.artSlave.belly >= 10000)
			bellyLevel = 4;
		else if (this.artSlave.belly >= 5000)
			bellyLevel = 3;
		else if (this.artSlave.belly >= 1500)
			bellyLevel = 2;
		else if (this.artSlave.belly >= 500)
			bellyLevel = 1;
		else
			bellyLevel = 0;

		return bellyLevel;
	}

	get hairBackLayer() {
		var result = [];

		if (!this.showHair)
			return result;

		if (this.artSlave.hLength == 0)
			return result;

		switch(this.artSlave.hStyle)
		{
			case "neat":
				result.push("Art_Vector_Revamp_Hair_Back_Neat_" + this.hairLength);
				break;
			case "bun":
			case "up":
				result.push("Art_Vector_Revamp_Hair_Back_Bun_" + this.hairLength);
				break;
			case "tails":
				result.push("Art_Vector_Revamp_Hair_Back_Tails_" + this.hairLength);
				break;
			case "ponytail":
				result.push("Art_Vector_Revamp_Hair_Back_Ponytail_" + this.hairLength);
				break;
			case "braided":
				result.push("Art_Vector_Revamp_Hair_Back_Braids_" + this.hairLength);
				break;
			case "curled":
				result.push("Art_Vector_Revamp_Hair_Back_Curled_" + this.hairLength);
				break;
			case "cornrows":
				result.push("Art_Vector_Revamp_Hair_Back_Cornrows_" + this.hairLength);
				break;
			case "strip":
				result.push("Art_Vector_Revamp_Hair_Back_Shaved_Sides_" + this.hairLength);
				break;
			case "luxurious":
				result.push("Art_Vector_Revamp_Hair_Back_Luxurious_" + this.hairLength);
				break;
			case "afro":
				result.push("Art_Vector_Revamp_Hair_Back_Afro_" + this.hairLength);
				break;
			case "permed":
				result.push("Art_Vector_Revamp_Hair_Back_Permed_" + this.hairLength);
				break;
			case "dreadlocks":
				result.push("Art_Vector_Revamp_Hair_Back_Dreadlocks_" + this.hairLength);
				break;
			default:
				result.push("Art_Vector_Revamp_Hair_Back_Messy_" + this.hairLength);
		}

		return result;
	}

	get armLayer() {
		var result = [];

		var leftArmType = "";
		var rightArmType = "";

		if (this.artSlave.amp == 1) {
			result.push("Art_Vector_Revamp_Arm_Stump");
		}
		else {
			result.push("Art_Vector_Revamp_Arm_Right_"+this.rightArmType);
			result.push("Art_Vector_Revamp_Arm_Left_"+this.leftArmType);
		}

		if (this.showArmHair && (this.artSlave.amp == 1 || (this.leftArmType == "High" && this.artSlave.amp != 1)))
		{
			switch(this.artSlave.underArmHStyle)
			{
				case "bushy":
					result.push("Art_Vector_Revamp_Arm_Up_Hair_Bushy");
					break;
				case "neat":
					result.push("Art_Vector_Revamp_Arm_Up_Hair_Neat");
					break;
				default:
			}
		}

		return result;
	}

	get buttLayer() {
		var result = [];
		var buttSize = 0;
		if (this.artSlave.amp)
			return result;

		if (this.artSlave.butt > 6)
			buttSize = 3;
		else if (this.artSlave.butt > 4)
			buttSize = 2;
		else if (this.artSlave.butt > 2)
			buttSize = 1;
		else
			buttSize = 0;

		result.push("Art_Vector_Revamp_Butt_" + buttSize);

		return result;
	}

	get legLayer() {
		var result = [];

		if (this.artSlave.amp == 1) {
			result.push("Art_Vector_Revamp_Stump");
		}
		else {
			var legSize = "Normal";

			if (this.artSlave.hips < 0) {
				legSize = this.artSlave.weight > 95 ? "Normal" : "Narrow";
			}
			else if (this.artSlave.hips == 0) {
				legSize = this.artSlave.weight > 95 ? "Wide" : "Normal";
			}
			else if (this.artSlave.hips > 0) {
				legSize = "Wide";
			}

			result.push("Art_Vector_Revamp_Leg_" + legSize);

			if (this.showLegHighlight)
			{
				result.push("Art_Vector_Revamp_Leg_Highlights2");
				result.push("Art_Vector_Revamp_Leg_Highlights1");
			}
		}

		return result;
	}

	get feetLayer() {
		var result = [];

		if (this.artSlave.amp == 1) {
			return result;
		}

		switch(this.artSlave.shoes)
		{
			case "heels":
				result.push("Art_Vector_Revamp_Shoes_Heel");
				break;
			case "extreme heels":
				result.push("Art_Vector_Revamp_Shoes_Exterme_Heel");
				break;
			case "boots":
				result.push("Art_Vector_Revamp_Shoes_Boot");
				break;
			case "flats":
				result.push("Art_Vector_Revamp_Shoes_Flat");
				break;
			default:
				result.push("Art_Vector_Revamp_Feet");
		}

		return result;
	}

	get torsoLayer() {
		var result = [];

		result.push("Art_Vector_Revamp_Torso_" + this.torsoSize);
		result.push("Art_Vector_Revamp_Clavicle");
		if (this.showTorsoHighlight)
		{
			result.push("Art_Vector_Revamp_Torso_Highlights2");
			result.push("Art_Vector_Revamp_Torso_Highlights1");
		}

		if (this.showArmHair && this.leftArmType != "High" && this.artSlave.amp != 1)
		{
			switch(this.artSlave.underArmHStyle)
			{
				case "bushy":
					result.push("Art_Vector_Revamp_Arm_Down_Hair_Bushy");
					break;
				case "neat":
					result.push("Art_Vector_Revamp_Arm_Down_Hair_Neat");
					break;
				default:
			}
		}

		return result;
	}

	get pussyLayer() {
		var result = [];

		if (this.showPussy && this.artSlave.vagina >= 0)
			result.push("Art_Vector_Revamp_Pussy");

		return result;
	}

	get pubicLayer() {
		var result = [];

		if (!this.showPubic)
			return result;

		if (this.artSlave.physicalAge < 12)
			return result;

		if (this.artSlave.vaginaTat == "rude words")
		{
			this.pubicTattooText = this.artSlave.dick != 0 ? "Useless" : "Fucktoy";
			result.push("Art_Vector_Revamp_Pussy_Tattoo");
		}

		switch(this.artSlave.pubicHStyle)
		{
			case "strip":
			case "in a strip":
				result.push("Art_Vector_Revamp_Pubic_Hair_Strip");
				break;
			case "bushy":
			case "bushy in the front and neat in the rear":
			case "very bushy":
				result.push("Art_Vector_Revamp_Pubic_Hair_Bush");
				break;
			case "neat":
				result.push("Art_Vector_Revamp_Pubic_Hair_Neat");
				break;
			case "waxed":
				break;
			default:
		}

		return result;
	}

	get pussyPiercingsLayer() {
		var result = [];

		if (!this.showPussyPiercings)
			return result;

		if (this.artSlave.vaginaPiercing == 1)
			result.push("Art_Vector_Revamp_Pussy_Piercing");
		else if (this.artSlave.vaginaPiercing == 2)
			result.push("Art_Vector_Revamp_Pussy_Piercing_Heavy");

		if (this.artSlave.clitPiercing == 1)
			result.push("Art_Vector_Revamp_Clit_Piercing");
		else if (this.artSlave.clitPiercing == 2)
			result.push("Art_Vector_Revamp_Clit_Piercing_Heavy");
		else if (this.artSlave.clitPiercing == 1)
			result.push("Art_Vector_Revamp_Clit_Piercing_Smart");

		return result;
	}

	get chastityBeltLayer() {
		var result = [];

		var isChastityAnalWorn = this.artSlave.dickAccessory == "anal chastity" || this.artSlave.dickAccessory == "combined chastity" || this.artSlave.vaginalAccessory == "anal chastity" || this.artSlave.vaginalAccessory == "combined chastity";

		isChastityAnalWorn = isChastityAnalWorn && this.showChastityAnal;

		var isChastityVaginalWorn = this.artSlave.vaginalAccessory == "chastity belt" || this.artSlave.vaginalAccessory == "combined chastity";

		isChastityVaginalWorn = isChastityVaginalWorn && this.showChastityVaginal;

		if (isChastityAnalWorn)
			result.push("Art_Vector_Revamp_Chastity_Anus");

		if (isChastityVaginalWorn)
			result.push("Art_Vector_Revamp_Chastity_Vagina");

		if (isChastityAnalWorn || isChastityVaginalWorn)
			result.push("Art_Vector_Revamp_Chastity_Base");

		return result;
	}

	get torsoOutfitLayer() {
		var result = [];

		switch(this.artSlave.clothes)
		{
			case "uncomfortable straps":
				result.push("Art_Vector_Revamp_Torso_Outfit_Straps_" + this.torsoSize);
				break;
			case "a nice maid outfit":
				result.push("Art_Vector_Revamp_Torso_Outfit_Maid_" + this.torsoSize);
				break;
			case "a slutty maid outfit":
				result.push("Art_Vector_Revamp_Torso_Outfit_Maid_Lewd_" + this.torsoSize);
				break;
			default:
		}

		return result;
	}

	get ballsLayer() {
		var result = [];

		if (!this.showBalls || this.artSlave.scrotum <= 0 || this.artSlave.balls <= 0)
			return result;

		var ballsSize = 0;

		if (this.artSlave.scrotum >= 6)
			ballsSize = 4;
		else if (this.artSlave.scrotum >= 4)
			ballsSize = 3;
		else if (this.artSlave.scrotum >= 3)
			ballsSize = 2;
		else if (this.artSlave.scrotum >= 2)
			ballsSize = 1;
		else
			ballsSize = 0;

		result.push("Art_Vector_Revamp_Balls_" + ballsSize);

		return result;
	}

	get bellyLayer() {
		var result = [];

		if (this.bellyLevel > 0)
		{
			result.push("Art_Vector_Revamp_Belly_" + this.bellyLevel);

			if (this.showBellyPiercings)
			{
				if (this.artSlave.navelPiercing >= 1)
					result.push("Art_Vector_Revamp_Belly_" + this.bellyLevel + "_Piercing");

				if (this.artSlave.navelPiercing == 2)
					result.push("Art_Vector_Revamp_Belly_" + this.bellyLevel + "_Piercing_Heavy");
			}
		}
		else
		{
			if (this.showBellyPiercings)
			{
				if (this.artSlave.navelPiercing >= 1)
					result.push("Art_Vector_Revamp_Navel_Piercing");

				if (this.artSlave.navelPiercing == 2)
					result.push("Art_Vector_Revamp_Navel_Piercing_Heavy");
			}
		}

		return result;
	}

	get penisLayer() {
		var result = [];

		if (this.getBellyLevel >= 8)
			return result;

		var penisSize = -1;

		if (this.artSlave.dick >= 8)
			penisSize = 6;
		else if (this.artSlave.dick >= 7)
			penisSize = 5;
		else if (this.artSlave.dick >= 6)
			penisSize = 4;
		else if (this.artSlave.dick >= 5)
			penisSize = 3;
		else if (this.artSlave.dick >= 4)
			penisSize = 2;
		else if (this.artSlave.dick >= 2)
			penisSize = 1;
		else if (this.artSlave.dick >= 1)
			penisSize = 0;
		else
			penisSize = -1;

		if (!this.showPenis || penisSize < 0)
			return result;

		if (canAchieveErection(this.artSlave) && (this.artSlave.dickAccessory != "chastity") && (this.artSlave.dickAccessory != "combined chastity"))
		{
			result.push("Art_Vector_Revamp_Penis_" + penisSize);

			if (!this.showPenisPiercings)
				return result;

			if (this.artSlave.dickPiercing == 1)
				result.push("Art_Vector_Revamp_Penis_" + penisSize + "_Piercing");
			else if (this.artSlave.dickPiercing == 2)
				result.push("Art_Vector_Revamp_Penis_" + penisSize + "_Piercing_Heavy");
		}
		else
		{
			result.push("Art_Vector_Revamp_Flaccid_" + penisSize);

			if ((this.artSlave.dickAccessory == "chastity") || (this.artSlave.dickAccessory == "combined chastity")) {
				result.push("Art_Vector_Revamp_Chastity_Cage_" + penisSize);
			}
		}

		return result;
	}

	get boobLayer() {
		var result = [];

		var artScaleFactor = 0.804354*Math.log(0.00577801*this.artSlave.boobs);

		var boobRightArtTranslationX = 270*((-1*artScaleFactor) + 1);
		var boobLeftArtTranslationX = 320*((-1*artScaleFactor) + 1);
		var artTranslationX = -283.841*artScaleFactor+285.349;
		var artTranslationY = 198*((-1*artScaleFactor) + 1);
		var artBoobTransform = "matrix(" + artScaleFactor +",0,0," + artScaleFactor + "," + artTranslationX + "," + artTranslationY + ")";

		this.artTransform = artBoobTransform;

		if (this.artSlave.boobs < 300)
		{
			if (this.showNipples)
			{
				var areolaeShape = "Normal";

				switch(this.artSlave.areolae)
				{
					case 0:
						areolaeShape = "Normal";
						break;
					case 1:
						areolaeShape = "Large";
						break;
					case 2:
						areolaeShape = "Wide";
						break;
					case 3:
						areolaeShape = "Huge";
						break;
					case 4:
						areolaeShape = "Heart";
						break;
					case 5:
						areolaeShape = "Star";
						break;
				}

				result.push("Art_Vector_Revamp_Boob_None_Areola_" + areolaeShape);
			}

		}
		else
		{
			if (!this.showBoobs)
				return result;

			var size = "Small";

			if (this.artSlave.boobs < 600)
			{
				artScaleFactor = 0.360674*Math.log(0.0266667*this.artSlave.boobs);

				boobRightArtTranslationX = 240*((-1*artScaleFactor) + 1);
				boobLeftArtTranslationX = 300*((-1*artScaleFactor) + 1);
				artTranslationY = 250*((-1*artScaleFactor) + 1);
				size = "Small";
			}
			else if (this.artSlave.boobs < 15000)
			{
				size = "Medium";
			}
			else {
				size = "Huge";
				boobRightArtTranslationX = 252*((-1*artScaleFactor) + 1);
				boobLeftArtTranslationX = 315*((-1*artScaleFactor) + 1);
			}

			this.boobRightArtTransform = "matrix(" + artScaleFactor +",0,0," + artScaleFactor + "," + boobRightArtTranslationX + "," + artTranslationY + ")";
			this.boobLeftArtTransform = "matrix(" + artScaleFactor +",0,0," + artScaleFactor + "," + boobLeftArtTranslationX + "," + artTranslationY + ")";
			result.push("Art_Vector_Revamp_Boob_" + size);

			if (this.showNipples)
			{
				var areolaeShape = "Normal";

				switch(this.artSlave.areolae)
				{
					case 0:
						areolaeShape = "Normal";
						break;
					case 1:
						areolaeShape = "Large";
						break;
					case 2:
						areolaeShape = "Wide";
						break;
					case 3:
						areolaeShape = "Huge";
						break;
					case 4:
						areolaeShape = "Heart";
						break;
					case 5:
						areolaeShape = "Star";
						break;
				}

				result.push("Art_Vector_Revamp_Boob_" + size + "_Areolae_" + areolaeShape);
				result.push("Art_Vector_Revamp_Boob_" + size + "_Nipples");
			}
			if (this.showBoobsHighlight)
			{
				result.push("Art_Vector_Revamp_Boob_" + size + "_Highlights2");
				result.push("Art_Vector_Revamp_Boob_" + size + "_Highlights1");
			}
		}

		return result;
	}

	get boobAddonLayer() {
		var result = [];

		if (this.showNipplesPiercings)
		{
			var size = "Small";

			if (this.artSlave.boobs < 600)
			{
				size = "Small";
			}
			else if (this.artSlave.boobs < 15000)
			{
				size = "Medium";
			}
			else {
				size = "Huge";
			}

			if (this.artSlave.nipplesPiercing == 1)
			{
				if (this.artSlave.boobs < 300)
					result.push("Art_Vector_Revamp_Boob_None_Piercing");
				else
					result.push("Art_Vector_Revamp_Boob_" + size + "_Piercing");
			}
			else if (this.artSlave.nipplesPiercing == 2)
			{
				if (this.artSlave.boobs < 300)
					result.push("Art_Vector_Revamp_Boob_None_Piercing_Heavy");
				else
					result.push("Art_Vector_Revamp_Boob_" + size + "_Piercing_Heavy");
			}

			if (this.artSlave.areolaePiercing == 1)
			{
				if (this.artSlave.boobs < 300)
					result.push("Art_Vector_Revamp_Boob_None_Areola_Piercing");
				else
					result.push("Art_Vector_Revamp_Boob_" + size + "_Areola_Piercing");
			}
			else if (this.artSlave.areolaePiercing == 2)
			{
				if (this.artSlave.boobs < 300)
					result.push("Art_Vector_Revamp_Boob_None_Areola_Piercing_Heavy");
				else
					result.push("Art_Vector_Revamp_Boob_" + size + "_Areola_Piercing_Heavy");
			}
		}

		switch(this.artSlave.clothes)
		{
			case "uncomfortable straps":
				if (this.artSlave.boobs >= 300)
					//result.push("Art_Vector_Revamp_Boob_Outfit_Straps");
				break;
			case "a nice maid outfit":
				if (this.artSlave.boobs >= 300)
				{
					if (this.artSlave.boobs < 600)
					{
						var artScaleFactor = 0.288539*Math.log(0.106667*this.artSlave.boobs);
						var artTranslationX = 270*((-1*artScaleFactor) + 1);
						var artTranslationY = 198*((-1*artScaleFactor) + 1);//-198.438*artScaleFactor+203.274;
						var artBoobTransform = "matrix(" + artScaleFactor +",0,0," + artScaleFactor + "," + artTranslationX + "," + artTranslationY + ")";

						this.boobOutfitArtTransform = artBoobTransform;

						result.push("Art_Vector_Revamp_Boob_Small_Outfit_Maid");
					}
					else if (this.artSlave.boobs < 15000)
					{
						var artScaleFactor = 0.155334*Math.log(1.04167*this.artSlave.boobs);
						var artTranslationX = 270*((-1.25*artScaleFactor) + 1.25);
						var artTranslationY = 198*((-0.8*artScaleFactor) + 0.8);//-198.438*artScaleFactor+203.274;
						var artBoobTransform = "matrix(" + artScaleFactor +",0,0," + artScaleFactor + "," + artTranslationX + "," + artTranslationY + ")";

						this.boobOutfitArtTransform = artBoobTransform;
						result.push("Art_Vector_Revamp_Boob_Medium_Outfit_Maid");
					}
					else {
						var artScaleFactor = 1.56609*Math.log(0.00017373*this.artSlave.boobs);
						var artTranslationX = 340*((-1*artScaleFactor) + 1);
						var artTranslationY = 153*((-1*artScaleFactor) + 1);//-198.438*artScaleFactor+203.274;
						var artBoobTransform = "matrix(" + artScaleFactor +",0,0," + artScaleFactor + "," + artTranslationX + "," + artTranslationY + ")";

						this.boobOutfitArtTransform = artBoobTransform;
						result.push("Art_Vector_Revamp_Boob_Huge_Outfit_Maid");
					}

				}
				break;

			default:
		}

		return result;
	}

	get clavicleLayer() {
		var result = [];

		result.push("Art_Vector_Revamp_Clavicle");

		return result;
	}

	get collarLayer() {
		var result = [];

		switch(this.artSlave.collar)
		{
			case "leather with cowbell":
				result.push("Art_Vector_Revamp_Collar_Cowbell");
				break;
			case "heavy gold":
				result.push("Art_Vector_Revamp_Collar_Gold_Heavy");
				break;
			case "neck corset":
				result.push("Art_Vector_Revamp_Collar_Neck_Corset");
				break;
			case "pretty jewelry":
				result.push("Art_Vector_Revamp_Collar_Pretty_Jewelry");
				break;
			case "cruel retirement counter":
				result.push("Art_Vector_Revamp_Collar_Retirement_Cruel");
				break;
			case "nice retirement counter":
				result.push("Art_Vector_Revamp_Collar_Retirement_Nice");
				break;
			case "satin choker":
				result.push("Art_Vector_Revamp_Collar_Satin_Choker");
				break;
			case "shock punishment":
				result.push("Art_Vector_Revamp_Collar_Shock_Punishment");
				break;
			case "stylish leather":
				result.push("Art_Vector_Revamp_Collar_Stylish_Leather");
				break;
			case "tight steel":
				result.push("Art_Vector_Revamp_Collar_Tight_Steel");
				break;
			default:
		}

		return result;
	}

	get headLayer() {
		var result = [];

		result.push("Art_Vector_Revamp_Head");

		if (this.showHeadHighlight)
			result.push("Art_Vector_Revamp_Face_Highlights");

		if (!this.showHeadPiercings)
			return result;

		if (this.artSlave.earPiercing == 1)
			result.push("Art_Vector_Revamp_Head_Ear_Piercing");
		else if (this.artSlave.earPiercing == 2)
			result.push("Art_Vector_Revamp_Head_Ear_Piercing_Heavy");

		if (this.artSlave.nosePiercing == 1)
			result.push("Art_Vector_Revamp_Head_Nose_Piercing");
		else if (this.artSlave.nosePiercing == 2)
			result.push("Art_Vector_Revamp_Head_Nose_Piercing_Heavy");

		return result;
	}

	get eyesLayer() {
		var result = [];

		if (!this.showEyes)
			return result;

		if (this.artSlave.devotion > 50)
		{
			result.push("Art_Vector_Revamp_Eyes_Happy");
			result.push("Art_Vector_Revamp_Eyes_Happy_Highlights");

			if (this.showHeadPiercings)
			{
				if (this.artSlave.eyebrowPiercing == 1)
					result.push("Art_Vector_Revamp_Head_Eyebrow_Happy_Piercing");
				else if (this.artSlave.eyebrowPiercing == 2)
					result.push("Art_Vector_Revamp_Head_Eyebrow_Happy_Piercing_Heavy");
			}
		}
		else if (this.artSlave.devotion >= 0)
		{
			result.push("Art_Vector_Revamp_Eyes_Shy");
			result.push("Art_Vector_Revamp_Eyes_Shy_Highlights");

			if (this.showHeadPiercings)
			{
				if (this.artSlave.eyebrowPiercing == 1)
					result.push("Art_Vector_Revamp_Head_Eyebrow_Shy_Piercing");
				else if (this.artSlave.eyebrowPiercing == 2)
					result.push("Art_Vector_Revamp_Head_Eyebrow_Shy_Piercing_Heavy");
			}
		}
		else if (this.artSlave.devotion >= -50)
		{
			result.push("Art_Vector_Revamp_Eyes_Closed");

			if (this.showHeadPiercings)
			{
				if (this.artSlave.eyebrowPiercing == 1)
					result.push("Art_Vector_Revamp_Head_Eyebrow_Closed_Piercing");
				else if (this.artSlave.eyebrowPiercing == 2)
					result.push("Art_Vector_Revamp_Head_Eyebrow_Closed_Piercing_Heavy");
			}
		}
		else
		{
			result.push("Art_Vector_Revamp_Eyes_Angry");
			result.push("Art_Vector_Revamp_Eyes_Angry_Highlights");
			if (this.showHeadPiercings)
			{
				if (this.artSlave.eyebrowPiercing == 1)
					result.push("Art_Vector_Revamp_Head_Eyebrow_Angry_Piercing");
				else if (this.artSlave.eyebrowPiercing == 2)
					result.push("Art_Vector_Revamp_Head_Eyebrow_Angry_Piercing_Heavy");
			}
		}

		return result;
	}

	get mouthLayer() {
		var result = [];
		var lipsSize = "Normal";

		if (!this.showMouth)
			return result;

		if (this.artSlave.lips > 95)
			lipsSize = "Enormous";
		else if (this.artSlave.lips > 70)
			lipsSize = "Huge";
		else if (this.artSlave.lips > 50)
			lipsSize = "Big";
		else if (this.artSlave.lips > 30)
			lipsSize = "Plush";
		else if (this.artSlave.lips > 10)
			lipsSize = "Normal";
		else
			lipsSize = "Thin";

		if (this.artSlave.trust > 20)
		{
			result.push("Art_Vector_Revamp_Makeup_Mouth_Happy_" + lipsSize);
			result.push("Art_Vector_Revamp_Mouth_Happy_" + lipsSize);

			if (this.showHeadPiercings)
			{
				if (this.artSlave.lipsPiercing == 1)
					result.push("Art_Vector_Revamp_Head_Mouth_Happy_Piercing");
				else if (this.artSlave.lipsPiercing == 2)
					result.push("Art_Vector_Revamp_Head_Mouth_Happy_Piercing_Heavy");
			}
		}
		else
		{
			result.push("Art_Vector_Revamp_Makeup_Mouth_Angry_" + lipsSize);
			result.push("Art_Vector_Revamp_Mouth_Angry_" + lipsSize);

			if (this.showHeadPiercings)
			{
				if (this.artSlave.lipsPiercing == 1)
					result.push("Art_Vector_Revamp_Head_Mouth_Angry_Piercing");
				else if (this.artSlave.lipsPiercing == 2)
					result.push("Art_Vector_Revamp_Head_Mouth_Angry_Piercing_Heavy");
			}
		}

		return result;
	}

	get headAddonLayer() {
		var result = [];

		if (this.showEyes) {
			switch(this.artSlave.eyewear)
			{
				case "corrective glasses":
				case "glasses":
				case "blurring glasses":
					result.push("Art_Vector_Revamp_Glasses");
					break;
				default:
			}
		}

		if (this.showMouth)
		{
			switch(this.artSlave.collar)
			{
				case "dildo gag":
					result.push("Art_Vector_Revamp_Dildo_Gag");
					break;
				case "bit gag":
					result.push("Art_Vector_Revamp_Bit_Gag");
					break;
				case "ball gag":
					result.push("Art_Vector_Revamp_Ball_Gag");
					break;
				default:

			}
		}
		return result;
	}

	get hairForeLayer() {
		var result = [];

		if (!this.showHair)
			return result;

		if (this.artSlave.hLength == 0)
			return result;

		switch(this.artSlave.hStyle)
		{
			case "neat":
				result.push("Art_Vector_Revamp_Hair_Fore_Neat");
				break;
			case "bun":
			case "up":
			case "tails":
			case "ponytail":
				result.push("Art_Vector_Revamp_Hair_Fore_Bun");
				break;
			case "braided":
				result.push("Art_Vector_Revamp_Hair_Fore_Braids");
				break;
			case "curled":
				result.push("Art_Vector_Revamp_Hair_Fore_Curled");
				break;
			case "cornrows":
				result.push("Art_Vector_Revamp_Hair_Fore_Cornrows");
				break;
			case "strip":
				result.push("Art_Vector_Revamp_Hair_Fore_Shaved_Sides");
				break;
			case "luxurious":
				result.push("Art_Vector_Revamp_Hair_Fore_Luxurious");
				break;
			case "afro":
				result.push("Art_Vector_Revamp_Hair_Fore_Afro");
				break;
			case "permed":
				result.push("Art_Vector_Revamp_Hair_Fore_Permed");
				break;
			case "dreadlocks":
				result.push("Art_Vector_Revamp_Hair_Fore_Dreadlocks");
				break;
			default:
				result.push("Art_Vector_Revamp_Hair_Fore_Messy");
		}

		return result;
	}

	get StylesCss() {
		return this.styleControl.StylesCss;
	}

	get Layers() {
		var layers = [];

		Array.prototype.push.apply(layers, this.hairBackLayer);
		Array.prototype.push.apply(layers, this.armLayer);
		Array.prototype.push.apply(layers, this.buttLayer);
		Array.prototype.push.apply(layers, this.legLayer);
		Array.prototype.push.apply(layers, this.feetLayer);
		Array.prototype.push.apply(layers, this.torsoLayer);
		Array.prototype.push.apply(layers, this.clavicleLayer);
		Array.prototype.push.apply(layers, this.pussyLayer);
		Array.prototype.push.apply(layers, this.pubicLayer);
		Array.prototype.push.apply(layers, this.pussyPiercingsLayer);
		Array.prototype.push.apply(layers, this.chastityBeltLayer);
		Array.prototype.push.apply(layers, this.torsoOutfitLayer);
		Array.prototype.push.apply(layers, this.ballsLayer);
		Array.prototype.push.apply(layers, this.bellyLayer);
		Array.prototype.push.apply(layers, this.penisLayer);
		Array.prototype.push.apply(layers, this.boobLayer);
		Array.prototype.push.apply(layers, this.boobAddonLayer);
		Array.prototype.push.apply(layers, this.collarLayer);
		Array.prototype.push.apply(layers, this.headLayer);
		Array.prototype.push.apply(layers, this.eyesLayer);
		Array.prototype.push.apply(layers, this.mouthLayer);
		Array.prototype.push.apply(layers, this.headAddonLayer);
		Array.prototype.push.apply(layers, this.hairForeLayer);

		return layers;
	}
}

window.getVectorArtRevampedControl = function(artDisplayClass, artSlave, globalShowHighlights, globalShowBodyMods) {
	return new RevampedArtControl(artDisplayClass, artSlave, globalShowHighlights, globalShowBodyMods);
};

license: mit


code:
:: wombJS [script]

/*
This is womb processor/simulator script. It's take care about calculation of belly sizes based on individual foetus sizes,
with full support of broodmothers implant random turning on and off possibility. Also this can be expanded to store more parents data in each individual fetus in future.
Design limitations:
- Mother can't gestate children with different speeds at same time. All speed changes apply to all fetuses.
- Sizes of inividual fetuses updated only on call of WombGetVolume - not every time as called WombProgress. This is for better overail code speed.
- For broodmothers we need actual "new ova release" code now. But it's possible to control how many children will be added each time, and so - how much children is ready to birth each time.

Usage from sugarcube code (samples):

WombInit($slave) - before first pregnancy, at slave creation, of as backward compatibility update.

WombImpregnate($slave, $fetus_count, $fatherID, $initial_age) - should be added after normal impregnation code, with already calcualted fetus count. ID of father - can be used in future for prcess children from different fathers in one pregnancy. Initial age normally 1 (as .preg normally set to 1), but can be raised if needed. Also should be called at time as broodmother implant add another fetus(es), or if new fetuses added from other sources in future (transplanting maybe?)

WombProgress($slave, $time_to_add_to_fetuses) - after code that update $slave.preg, time to add should be the same.

$isReady = WombBirthReady($slave, $birth_ready_age) - how many children ready to be birthed if their time to be ready is $birth_ready_age (40 is for normal length pregnancy). Return int - count of ready to birth children, or 0 if no ready exists.

$children = WombBirth($slave, $birth_ready_age) - for actual birth. Return array with fetuses objects that birthed (can be used in future) and remove them from womb array of $slave. Should be called at actual birth code in sugarcube. fetuses that not ready remained in womb (array).

WombFlush($slave) - clean womb (array). Can be used at broodmother birthstorm or abortion situations in game. But birthstorm logicaly should use WombBirth($slave, 35) or so before - some children in this event is live capable, others is not.

$slave.bellyPreg = WombGetWolume($slave) - return double, with current womb volume in CC - for updating $slave.bellyPreg, or if need to update individual fetuses sizes.
*/

window.WombInit = function(actor) //Init womb system.
{

    if (!Array.isArray(actor.womb))
    {
       //alert("creating new womb"); //debugging
	   actor.womb = [];
    }

//    console.log("broodmother:" + typeof actor.broodmother);

    if ( typeof actor.broodmother != "number" )
    {
          actor.broodmother = 0;
          actor.broodmotherFetuses = 0;
    }

    if ( typeof actor.readyOva != "number" )
    {
          actor.readyOva = 0;
    }

    if (actor.womb.length == 0 && actor.pregType != 0 && actor.broodmother == 0) //backward compatibility setup. Fully accurate for normal pregnancy only.
    {
        WombImpregnate(actor, actor.pregType, actor.pregSource, actor.preg);
    }
    else if (actor.womb.length == 0 && actor.pregType != 0 && actor.broodmother > 0 && actor.broodmotherOnHold < 1) //sorry but for already present broodmothers it's impossible to calculate fully, aproximation used.
    {
        var i, pw = actor.preg, bCount, bLeft;
        if (pw > 40)
            pw = 40; //to avoid disaster.
        bCount = Math.floor(actor.pregType/pw);
        bLeft = actor.pregType - (bCount*pw);
        if (pw > actor.pregType)
        {
            pw = actor.pregType // low children count broodmothers not supported here. It's emergency/backward compatibility code, and they not in game anyway. So minimum is 1 fetus in week.
            actor.preg = pw; // fixing initial pregnancy week.
        }
        for (i=0; i<pw; i++)
        {
            WombImpregnate(actor, bCount, actor.pregSource, i); // setting fetuses for every week, up to 40 week at max.
        }

        if (bLeft > 0)
        {
            WombImpregnate(actor, bLeft, actor.pregSource, i+1); // setting up leftower of fetuses.
        }
    }
}

window.WombImpregnate = function(actor, fCount, fatherID, age)
{
    var i;
    var tf;
    for (i=0; i<fCount; i++)
    {
	tf = {}; //new Object
    tf.age = age; //initial age
    tf.fatherID = fatherID; //We can store who is father too.
	tf.sex = Math.round(Math.random())+1; // 1 = male, 2 = female. For possible future usage, just as concept now.
    tf.volume = 1; //Initial, to create property. Updated with actual data after WombGetVolume call.

    try {
            if (actor.womb.length == 0)
            {
                actor.pregWeek = age;
                actor.preg = age;
            }

	        actor.womb.push(tf);
        }catch(err){
            WombInit(actor);
            actor.womb.push(tf);
            alert("WombImpregnate warning - " + actor.slaveName+" "+err);
        }

    }

}

window.WombProgress = function(actor, ageToAdd)
{
    var i, ft;
    ageToAdd = Math.ceil(ageToAdd*10)/10;
    try {
            for (i in actor.womb)
            {
                ft = actor.womb[i];
                ft.age += ageToAdd;
            }
    }catch(err){
            WombInit(actor);
            alert("WombProgress warning - " + actor.slaveName+" "+err);
    }
}

window.WombBirth = function(actor, readyAge)
{
    try{
        actor.womb.sort(function (a, b){return b.age - a.age}); //For normal processing fetuses that more old should be first. Now - they are.
        }catch(err){
            WombInit(actor);
            alert("WombBirth warning - " + actor.slaveName+" "+err);
        }

    var birthed = [];
    var ready = WombBirthReady(actor, readyAge);
    var i;

    for (i=0; i<ready; i++) //here can't be used "for .. in .." syntax.
    {
        birthed.push(actor.womb.shift());
    }

    return birthed;
}

window.WombFlush = function(actor)
{
    actor.womb = [];
}

window.WombBirthReady = function(actor, readyAge)
{

    var i, ft;
    var readyCnt = 0;
    try {
        for (i in actor.womb)
        {
            ft = actor.womb[i];
            if (ft.age >= readyAge)
                  readyCnt++;
        }
    }catch(err){
        WombInit(actor);
        alert("WombBirthReady warning - " + actor.slaveName+" "+err);

        return 0;
    }

    return readyCnt;
}

window.WombGetVolume = function(actor) //most code from pregJS.tw with minor adaptation.
{
    var i, ft;
    var gestastionWeek;
    var phi = 1.618;
    var targetLen;
    var wombSize = 0;

    try{

        for (i in actor.womb)
        {
            ft = actor.womb[i];
            gestastionWeek = ft.age;

            if (gestastionWeek <= 32)
            {
                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)
            {
                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
            {
                targetLen = (-0.00003266 * Math.pow(gestastionWeek,2)) + (0.076 * gestastionWeek) + 43.843;
            }

            ft.volume = ((4 / 3) * (Math.PI) * (phi / 2) * (Math.pow((targetLen / 2), 3)));

            wombSize += ft.volume;
        }
    }catch(err){
        WombInit(actor);
        alert("WombGetVolume warning - " + actor.slaveName + " " + err);
    }

    if (wombSize < 0) //catch for strange cases, to avoid messing with outside code.
        wombSize = 0;

    return wombSize;
}

window.WombUpdatePregVars = function(actor) {

    actor.womb.sort(function (a, b){return b.age - a.age});
    if (actor.womb.length > 0)
    {
        if (actor.preg > 0 && actor.womb[0].age > 0)
        {
            actor.preg = actor.womb[0].age;
        }

        actor.pregType = actor.womb.length;

        actor.bellyPreg = WombGetVolume(actor);

    }

}

window.WombMinPreg = function(actor)
{
    actor.womb.sort(function (a, b){return b.age - a.age});

    if (actor.womb.length > 0)
        return actor.womb[actor.womb.length-1].age;
    else
        return 0;
}

window.WombMaxPreg = function(actor)
{
    actor.womb.sort(function (a, b){return b.age - a.age});
    if (actor.womb.length > 0)
        return actor.womb[0].age;
    else
        return 0;
}

window.WombNormalizePreg = function(actor)
{
//    console.log("New actor: " + actor.slaveName + " ===============" + actor.name);
    WombInit(actor);

    if (actor.womb.length == 0 && actor.broodmother >= 1) // this is broodmother on hold.
    {
        actor.pregType = 0;
        actor.pregKnown = 0;

        if (actor.preg >= 0)
            actor.preg = 0.1; //to avoid legacy code conflicts - broodmother on hold can't be impregnated, but she not on normal contraceptives. So we set this for special case.

        if (actor.pregSource > 0)
            actor.pregSource = 0;

        if (actor.pregWeek > 0)
            actor.pregWeek = 0;

        actor.broodmotherCountDown = 0;
    }

    if (actor.womb.length > 0)
    {
        var max = WombMaxPreg(actor);
//        console.log("max: " + max);
//        console.log(".preg: "+ actor.preg);
        if (actor.pregWeek < 1 )
            actor.pregWeek = 1

        if (max < actor.preg)
        {
            WombProgress(actor, actor.preg - max);
//            console.log("progressin womb");
        }
        else if ( max > actor.preg)
        {
            actor.preg = max;
//            console.log("advancing .preg");
        }

        actor.pregType = actor.womb.length;
        actor.pregSource = actor.womb[0].fatherID;
    }
    else if (actor.womb.length == 0 && actor.broodmother < 1) //not broodmother
    {
//        console.log("preg fixing");
        actor.pregType = 0;
        actor.pregKnown = 0;

        if (actor.preg > 0)
            actor.preg = 0;

        if (actor.pregSource > 0)
            actor.pregSource = 0;

        if (actor.pregWeek > 0) // We can't properly set postpartum here, but can normalize obvious error with forgotten property.
            actor.pregWeek = 0;
    }

    actor.bellyPreg = WombGetVolume(actor);
}

window.WombZeroID = function(actor, id)
{
    WombInit(actor);
    var i, ft;
    for (i in actor.womb)
    {
        ft = actor.womb[i];
        if (ft.fatherID == id)
            ft.fatherID = 0;
    }

    WombNormalizePreg(actor);
}


code:
:: Abort [nobr]

<<set $nextButton = "Back", $nextLink = "Slave Interact">>
<<ClearSummaryCache $activeSlave>>

The remote surgery makes aborting a pregnancy quick and efficient. $activeSlave.slaveName is
<<if $activeSlave.fetish == "pregnancy">>
	@@.red;fundamentally broken.@@ Her entire concept of self and sexuality was wrapped up in the life growing within her, and now it is gone.
	<<set $activeSlave.fetish = "mindbroken">>
<<elseif $activeSlave.devotion < -50>>
	@@.mediumorchid;filled with violent, consuming hatred@@. Even though she knew her baby was destined for a slave orphanage, it seems she cared for it and views you as its killer. She is @@.gold;terrified of your power@@ over her body.
	<<set $activeSlave.trust -= 10, $activeSlave.devotion -= 25>>
<<elseif $activeSlave.devotion < -20>>
	@@.mediumorchid;afflicted by desperate, inconsolable sobbing@@. Even though she knew her baby was destined for a slave orphanage, it seems she cared for it. She is @@.gold;terrified of your power@@ over her body.
	<<set  $activeSlave.trust -= 10, $activeSlave.devotion -= 10>>
<<elseif $activeSlave.devotion <= 20>>
	@@.mediumorchid;consumed by muted weeping and enduring sorrow@@. Even though she knew her baby was destined for a slave orphanage, it seems she cared for it. She is @@.gold;terrified of your power@@ over her body.
	<<set $activeSlave.trust -= 10, $activeSlave.devotion -= 5>>
<<elseif $activeSlave.devotion <= 50>>
	dully obedient. She has been broken to slave life so thoroughly that even this is neither surprising nor affecting. She is @@.gold;terrified of your power@@ over her body.
	<<set $activeSlave.trust -= 10>>
<<else>>
	@@.hotpink;pleased by this stark development@@, since she is so attentive to your will. She also expects she'll be able to fuck better now.
	<<set $activeSlave.devotion += 4>>
<</if>>

<<if lastPregRule($activeSlave,$defaultRules)>><<set $activeSlave.preg = -1>><<else>><<set $activeSlave.preg = 0>><</if>>

<<if $activeSlave.reservedChildren > 0>>
	<<set $reservedChildren -= $activeSlave.reservedChildren>>
<</if>>
<<set $activeSlave.pregType = 0>>
<<set $activeSlave.pregSource = 0>>
<<set $activeSlave.pregKnown = 0>>
<<set $activeSlave.pregWeek = -2>>
<<set WombFlush($activeSlave)>>
<<SetBellySize $activeSlave>>