From 2538351ad4addeb1882c0a678576d2a4e6436bcd Mon Sep 17 00:00:00 2001
From: lowercasedonkey <lowercasedonkey@gmail.com>
Date: Wed, 23 Dec 2020 16:28:47 -0500
Subject: [PATCH] unit related functions to new file

---
 src/js/utilsFC.js    | 506 -------------------------------------------
 src/js/utilsUnits.js | 505 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 505 insertions(+), 506 deletions(-)
 create mode 100644 src/js/utilsUnits.js

diff --git a/src/js/utilsFC.js b/src/js/utilsFC.js
index 14f26e503cc..dc02af8842c 100644
--- a/src/js/utilsFC.js
+++ b/src/js/utilsFC.js
@@ -948,380 +948,6 @@ globalThis.Categorizer = class {
 	}
 };
 
-/**
- * Returns numbers as text, e.g. 10 as "ten", according to the player's settings
- * @param {number} x
- * @param {boolean} [printText=false] (optional)
- * @returns {string}
- */
-globalThis.num = function(x, printText = false) {
-	const max = V.showNumbersMax;
-
-	const ONE_TO_NINETEEN = [
-		"one", "two", "three", "four", "five",
-		"six", "seven", "eight", "nine", "ten",
-		"eleven", "twelve", "thirteen", "fourteen", "fifteen",
-		"sixteen", "seventeen", "eighteen", "nineteen",
-	];
-
-	const TENS = [
-		"ten", "twenty", "thirty", "forty", "fifty",
-		"sixty", "seventy", "eighty", "ninety",
-	];
-
-	const SCALES = ["thousand", "million", "billion", "trillion", "quadrillion", "quintillion", "sextillion", "septillion", "octillion", "nonillion", "decillion"];
-
-	/**
-	 * helper function for use with Array.filter
-	 * @param {any} item
-	 * @returns {boolean}
-	 */
-	function isTruthy(item) {
-		return !!item;
-	}
-
-	/**
-	 * convert a number into "chunks" of 0-999
-	 * @param {number} number
-	 * @returns {number[]}
-	 */
-	function chunk(number) {
-		const thousands = [];
-
-		while (number > 0) {
-			thousands.push(number % 1000);
-			number = Math.floor(number / 1000);
-		}
-
-		return thousands;
-	}
-
-	/**
-	 * translate a number from 1-999 into English
-	 * @param {number} number
-	 * @returns {string}
-	 */
-	function inEnglish(number) {
-		let hundreds;
-		let tens;
-		let ones;
-		const words = [];
-
-		if (number === 0) {
-			return "zero";
-		}
-
-		if (number < 20) {
-			return ONE_TO_NINETEEN[number - 1]; // may be undefined
-		}
-
-		if (number < 100) {
-			ones = number % 10;
-			tens = number / 10 | 0; // equivalent to Math.floor(number / 10)
-
-			words.push(TENS[tens - 1]);
-			words.push(inEnglish(ones));
-
-			return words.filter(isTruthy).join("-");
-		}
-
-		hundreds = number / 100 | 0;
-		words.push(inEnglish(hundreds));
-		words.push("hundred");
-		words.push(inEnglish(number % 100));
-
-		return words.filter(isTruthy).join(" ");
-	}
-
-	if (printText) {
-		return inEnglish(x);
-	}
-
-	/**
-	 * append the word for a scale. Made for use with Array.map
-	 * @param {string} chunk
-	 * @param {number} exp
-	 * @returns {string}
-	 */
-	function appendScale(chunk, exp) {
-		let scale;
-		if (!chunk) {
-			return null;
-		}
-		scale = SCALES[exp - 1];
-		return [chunk, scale].filter(isTruthy).join(" ");
-	}
-
-	if (V.showNumbers === 2) {
-		return commaNum(x);
-	} else {
-		if (x === 0) {
-			return "zero";
-		}
-
-		if (V.showNumbers === 1 && Math.abs(x) > max) {
-			return commaNum(x);
-		}
-
-		let numberAsString = chunk(Math.abs(x))
-			.map(inEnglish)
-			.map(appendScale)
-			.filter(isTruthy)
-			.reverse()
-			.join(" ");
-
-		if (x > 0) {
-			return numberAsString;
-		} else {
-			return `negative ${numberAsString}`;
-		}
-	}
-};
-
-globalThis.asPlural = function(single, plural) {
-	if (typeof single !== 'string') {
-		let asObj = single;
-		single = asObj.single;
-		plural = asObj.plural;
-	}
-	if (plural == null) {
-		plural = single + "s";
-	}
-	return plural;
-};
-globalThis.asSingular = function(single) {
-	if (typeof single !== 'string') {
-		let asObj = single;
-		single = asObj.single;
-	}
-	return single;
-};
-// When 1, shows "a (slave)"
-globalThis.numberWithPlural = function(number, single, plural) {
-	if (number === 0) {
-		return "no " + asPlural(single, plural);
-	} else if (number === 1) {
-		return addA(asSingular(single));
-	} else if (number > 0 && number < 1) {
-		return "less than one " + asSingular(single);
-	} else {
-		return number + " " + asPlural(single, plural);
-	}
-};
-
-// when 1, shows "one (slave)"
-globalThis.numberWithPluralOne = function(number, single, plural) {
-	if (number === 0) {
-		return "no " + asPlural(single, plural);
-	} else if (number === 1) {
-		return "one " + asSingular(single);
-	} else if (number > 0 && number < 1) {
-		return "less than one " + asSingular(single);
-	} else {
-		return number + " " + asPlural(single, plural);
-	}
-};
-// shows "less than one (slave)" instead of "no (slaves)" when number is 0.
-globalThis.numberWithPluralNonZero = function(number, single, plural) {
-	if (number === 0) {
-		number = 0.1;
-	}
-	return numberWithPlural(number, single, plural);
-};
-globalThis.onlyPlural = function(number, single, plural) {
-	if (number > 0 && number <= 1) {
-		return asSingular(single);
-	}
-	return asPlural(single, plural);
-};
-globalThis.Separator = function(SeparatorObject) {
-	if (SeparatorObject.need) {
-		return SeparatorObject.text;
-	}
-	SeparatorObject.need = true;
-	return "";
-};
-/**
- * Returns numbers with comma, e.g. 10000 as "10,000", according to the player's settings
- * @param {number} s
- * @returns {string}
- */
-globalThis.commaNum = function(s) {
-	// Separated from num because some places in code (like long lists, tables) should never have numbers spelled out, but still benefit from commas
-	if (!s) {
-		return "0";
-	}
-	if (V.formatNumbers !== 1) {
-		return s.toString();
-	} else {
-		return s.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
-	}
-};
-
-/**
- * Returns the number of weeks in a years / months / weeks format
- * @param {number} weeks
- * @returns {string}
- */
-globalThis.years = function(weeks) {
-	let years = 0;
-	let quarters = 0; // needed for calc, not user facing
-	let months = 0;
-	let array = [];
-
-	// A year is always 52 weeks
-	// that could be 13 months, but lets say 4 quarters each getting an extra week (13 weeks)
-
-	// Find years
-	years = Math.trunc(weeks / 52);
-
-	if (years >= 1) { // Is there at least 1 year
-		weeks = weeks - (years * 52); // Find leftover weeks
-	}
-	if (weeks && weeks / 13 >= 1) { // Is there at least 1 quarter
-		quarters = Math.trunc(weeks / 13); // How many quarters?
-		weeks = weeks - (quarters * 13); // A quarter contains 13 weeks, how many extra weeks do we have?
-	}
-	if (weeks && weeks / 4 >= 1) { // Is there at least 1 month
-		months = Math.trunc(weeks / 4); // How many months?
-		if (months === 3) { // Almost a quarter of a year
-			months--; // Quarters have 13 weeks though, so let's be sure the extra is in weeks. Otherwise 51 will return "12 months" instead of "11 months and 4 weeks."
-		}
-		weeks = weeks - (months * 4); // A month contains 4 weeks, how many extra weeks do we have?
-	}
-
-	// So we have years, quarters, months, and weeks.
-
-	// Quarters are useless so:
-
-	months += quarters * 3; // Each quarter has three months.
-
-	if (years) {
-		array.push(`${num(years)} year${years !== 1 ? `s` : ``}`);
-	}
-
-	if (months) {
-		array.push(`${num(months)} month${months !== 1 ? `s` : ``}`);
-	}
-
-	if (weeks) {
-		array.push(`${num(weeks)} week${weeks !== 1 ? `s` : ``}`);
-	}
-
-	return array.toStringExt();
-};
-/**
- * @param {number} [weeks]
- * @param {number} [bonusDay]
- * @returns {Date}
- */
-globalThis.asDate = function(weeks = null, bonusDay = 0) {
-	if (weeks == null) {
-		weeks = V.week;
-	}
-	let d = new Date(2037, 0, 12);
-	d.setDate(d.getDate() + weeks * 7 + bonusDay);
-	return d;
-};
-/**
- * @param {number} [weeks]
- * @param {number} [bonusDay]
- * @returns {string}
- */
-globalThis.asDateString = function(weeks = null, bonusDay = 0) {
-	return asDate(weeks, bonusDay).toLocaleString(undefined, {year: 'numeric', month: 'long', day: 'numeric'});
-};
-
-/**
- * @param {number} s
- * @returns {string}
- */
-globalThis.cashFormat = function(s) {
-	if (s < 0) {
-		return `-¤${commaNum(Math.abs(s))}`;
-	}
-	return `¤${commaNum(s)}`;
-};
-globalThis.cashFormatColor = function(s, invert = false) {
-	if (invert) {
-		s = -1 * s;
-	}
-	// Display red if the value is negative, unless invert is true
-	if (s < 0) {
-		return `<span class='red'>${cashFormat(s)}</span>`;
-		// White for exactly zero
-	} else if (s === 0) {
-		return `<span>${cashFormat(s)}</span>`;
-		// Yellow for positive
-	} else {
-		return `<span class='yellowgreen'>${cashFormat(s)}</span>`;
-	}
-};
-
-/**
- * @param {number} s
- * @returns {string}
- */
-globalThis.repFormat = function(s) {
-	/* if (!s) { s = 0; }*/
-	if (V.cheatMode === 1 || V.debugMode === 1) {
-		if (s > 0) {
-			return `<span class="green">${commaNum(Math.round(s * 100) / 100)} rep</span>`;
-		} else if (s < 0) {
-			return `<span class="red">${commaNum(Math.round(s * 100) / 100)} rep</span>`;
-		} else {
-			return `${commaNum(Math.round(s * 100) / 100)} rep`;
-		}
-	} else {
-		/* In order to calculate just how much any one category matters so we can show a "fuzzy" symbolic value to the player, we need to know how "busy" reputation was this week. To calculate this, I ADD income to expenses. Why? 100 - 100 and 10000 - 10000 BOTH are 0, but a +50 event matters a lot more in the first case than the second. I exclude overflow from the calculation because it's not a "real" expense for our purposes, and divide by half just to make percentages a bit easier. */
-		let weight = s / (((V.lastWeeksRepIncome.Total - V.lastWeeksRepExpenses.Total) + V.lastWeeksRepExpenses.overflow) / 2);
-		if (weight > 0.60) {
-			return `<span class="green">+++++ rep</span>`;
-		} else if (weight > 0.45) {
-			return `<span class="green">++++ rep</span>`;
-		} else if (weight > 0.30) {
-			return `<span class="green">+++ rep</span>`;
-		} else if (weight > 0.15) {
-			return `<span class="green">++ rep</span>`;
-		} else if (weight > 0.0) {
-			return `<span class="green">+ rep</span>`;
-		} else if (weight === 0) {
-			return "0 rep";
-		} else if (weight < -0.60) {
-			return `<span class="red">&minus;&minus;&minus;&minus;&minus; rep</span>`;
-		} else if (weight < -0.45) {
-			return `<span class="red">&minus;&minus;&minus;&minus; rep</span>`;
-		} else if (weight < -0.30) {
-			return `<span class="red">&minus;&minus;&minus; rep</span>`;
-		} else if (weight < -0.15) {
-			return `<span class="red">&minus;&minus; rep</span>`;
-		} else if (weight < 0) {
-			return `<span class="red">&minus; rep</span>`;
-		}
-		/* return weight;*/
-	}
-};
-
-/**
- * @param {number} s
- * @returns {string}
- */
-globalThis.massFormat = function(s) {
-	if (!s) {
-		s = 0;
-	}
-	if (Math.abs(s) >= 1000) {
-		s = Math.trunc(s / 1000);
-		if (s !== 1) {
-			return `${num(s)} tons`;
-		} else {
-			return `${num(s)} ton`;
-		}
-	} else {
-		return `${num(s)} kg`;
-	}
-};
-
 /**
  * @param {string} category
  * @param {string} title
@@ -1441,138 +1067,6 @@ globalThis.getSlaveTrustClass = function(slave) {
 	}
 };
 
-/**
- * Takes an integer e.g. slave.hLength, returns a string in the format 10 inches
- * @param {number} cm
- * @returns {string}
- */
-globalThis.cmToInchString = function(cm) {
-	let inches = cm / 2.54;
-	if (inches > 0 && inches < 1) {
-		return "less than an inch";
-	}
-	inches = Math.round(inches);
-	if (inches === 1) {
-		return "1 inch";
-	}
-	return `${inches} inches`;
-};
-
-/**
- * takes an integer e.g. slave.height, returns a string in the format 6'5"
- * @param {number} cm
- * @returns {string}
- */
-globalThis.cmToFootInchString = function(cm) {
-	if (Math.round(cm / 2.54) < 12) {
-		return cmToInchString(cm);
-	}
-	return `${Math.trunc(Math.round(cm / 2.54) / 12)}'${Math.round(cm / 2.54) % 12}"`;
-};
-
-/**
- * takes a dick value e.g. slave.dick, returns a string in the format 6 inches
- * @param {number} dick
- * @returns {string}
- */
-globalThis.dickToInchString = function(dick) {
-	return cmToInchString(dickToCM(dick));
-};
-
-/**
- * takes a dick value e.g. slave.dick, returns an int of the dick length in cm
- * @param {number} dick
- * @returns {number}
- */
-globalThis.dickToCM = function(dick) {
-	if (dick < 9) {
-		return dick * 5;
-	} else if (dick === 9) {
-		return 50;
-	}
-	return dick * 6;
-};
-/**
- * takes a ball value e.g. slave.balls, returns a string in the format 3 inches
- * @param {number} balls
- * @returns {string}
- */
-globalThis.ballsToInchString = function(balls) {
-	return cmToInchString(ballsToCM(balls));
-};
-
-/**
- * takes a ball value e.g. slave.balls, returns an int of the ball size in cm
- * @param {number} balls
- * @returns {number}
- */
-globalThis.ballsToCM = function(balls) {
-	if (balls < 2) {
-		return 0;
-	}
-	return (balls < 10 ? (balls - 1) * 2 : balls * 2);
-};
-
-/**
- * takes a dick value e.g. slave.dick, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm`
- * @param {number} dick
- * @returns {string}
- */
-globalThis.dickToEitherUnit = function(dick) {
-	if (V.showInches === 1) {
-		return `${dickToCM(dick)}cm (${dickToInchString(dick)})`;
-	}
-	if (V.showInches === 2) {
-		return dickToInchString(dick);
-	}
-	return `${dickToCM(dick)}cm`;
-};
-
-/**
- * takes a ball value e.g. slave.balls, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm`
- * @param {number} balls
- * @returns {string}
- */
-globalThis.ballsToEitherUnit = function(balls) {
-	if (V.showInches === 1) {
-		return `${ballsToCM(balls)}cm (${ballsToInchString(balls)})`;
-	}
-	if (V.showInches === 2) {
-		return ballsToInchString(balls);
-	}
-	return `${ballsToCM(balls)}cm`;
-};
-
-/**
- * takes an int in centimeters e.g. slave.height, returns a string in the format of either `200cm (6'7")`, `6'7"`, or `200cm`
- * @param {number} height
- * @returns {string}
- */
-globalThis.heightToEitherUnit = function(height) {
-	if (V.showInches === 1) {
-		return `${height}cm (${cmToFootInchString(height)})`;
-	}
-	if (V.showInches === 2) {
-		return cmToFootInchString(height);
-	}
-	return `${height}cm`;
-};
-
-/**
- * takes an int in centimeters e.g. slave.hLength, returns a string in the format of either `30cm (12 inches)`, `12 inches`, or `30cm`
- * @param {number} length
- * @returns {string}
- */
-globalThis.lengthToEitherUnit = function(length) {
-	if (V.showInches === 1) {
-		return `${length}cm (${cmToInchString(length)})`;
-	}
-	if (V.showInches === 2) {
-		return cmToInchString(length);
-	}
-	return `${length}cm`;
-};
-
 /**
  * @param {App.Entity.SlaveState} slave
  * @param {number} [induce]
diff --git a/src/js/utilsUnits.js b/src/js/utilsUnits.js
new file mode 100644
index 00000000000..bb2ca3278f4
--- /dev/null
+++ b/src/js/utilsUnits.js
@@ -0,0 +1,505 @@
+/**
+ * Returns numbers as text, e.g. 10 as "ten", according to the player's settings
+ * @param {number} x
+ * @param {boolean} [printText=false] (optional)
+ * @returns {string}
+ */
+globalThis.num = function(x, printText = false) {
+	const max = V.showNumbersMax;
+
+	const ONE_TO_NINETEEN = [
+		"one", "two", "three", "four", "five",
+		"six", "seven", "eight", "nine", "ten",
+		"eleven", "twelve", "thirteen", "fourteen", "fifteen",
+		"sixteen", "seventeen", "eighteen", "nineteen",
+	];
+
+	const TENS = [
+		"ten", "twenty", "thirty", "forty", "fifty",
+		"sixty", "seventy", "eighty", "ninety",
+	];
+
+	const SCALES = ["thousand", "million", "billion", "trillion", "quadrillion", "quintillion", "sextillion", "septillion", "octillion", "nonillion", "decillion"];
+
+	/**
+	 * helper function for use with Array.filter
+	 * @param {any} item
+	 * @returns {boolean}
+	 */
+	function isTruthy(item) {
+		return !!item;
+	}
+
+	/**
+	 * convert a number into "chunks" of 0-999
+	 * @param {number} number
+	 * @returns {number[]}
+	 */
+	function chunk(number) {
+		const thousands = [];
+
+		while (number > 0) {
+			thousands.push(number % 1000);
+			number = Math.floor(number / 1000);
+		}
+
+		return thousands;
+	}
+
+	/**
+	 * translate a number from 1-999 into English
+	 * @param {number} number
+	 * @returns {string}
+	 */
+	function inEnglish(number) {
+		let hundreds;
+		let tens;
+		let ones;
+		const words = [];
+
+		if (number === 0) {
+			return "zero";
+		}
+
+		if (number < 20) {
+			return ONE_TO_NINETEEN[number - 1]; // may be undefined
+		}
+
+		if (number < 100) {
+			ones = number % 10;
+			tens = number / 10 | 0; // equivalent to Math.floor(number / 10)
+
+			words.push(TENS[tens - 1]);
+			words.push(inEnglish(ones));
+
+			return words.filter(isTruthy).join("-");
+		}
+
+		hundreds = number / 100 | 0;
+		words.push(inEnglish(hundreds));
+		words.push("hundred");
+		words.push(inEnglish(number % 100));
+
+		return words.filter(isTruthy).join(" ");
+	}
+
+	if (printText) {
+		return inEnglish(x);
+	}
+
+	/**
+	 * append the word for a scale. Made for use with Array.map
+	 * @param {string} chunk
+	 * @param {number} exp
+	 * @returns {string}
+	 */
+	function appendScale(chunk, exp) {
+		let scale;
+		if (!chunk) {
+			return null;
+		}
+		scale = SCALES[exp - 1];
+		return [chunk, scale].filter(isTruthy).join(" ");
+	}
+
+	if (V.showNumbers === 2) {
+		return commaNum(x);
+	} else {
+		if (x === 0) {
+			return "zero";
+		}
+
+		if (V.showNumbers === 1 && Math.abs(x) > max) {
+			return commaNum(x);
+		}
+
+		let numberAsString = chunk(Math.abs(x))
+			.map(inEnglish)
+			.map(appendScale)
+			.filter(isTruthy)
+			.reverse()
+			.join(" ");
+
+		if (x > 0) {
+			return numberAsString;
+		} else {
+			return `negative ${numberAsString}`;
+		}
+	}
+};
+
+globalThis.asPlural = function(single, plural) {
+	if (typeof single !== 'string') {
+		let asObj = single;
+		single = asObj.single;
+		plural = asObj.plural;
+	}
+	if (plural == null) {
+		plural = single + "s";
+	}
+	return plural;
+};
+globalThis.asSingular = function(single) {
+	if (typeof single !== 'string') {
+		let asObj = single;
+		single = asObj.single;
+	}
+	return single;
+};
+// When 1, shows "a (slave)"
+globalThis.numberWithPlural = function(number, single, plural) {
+	if (number === 0) {
+		return "no " + asPlural(single, plural);
+	} else if (number === 1) {
+		return addA(asSingular(single));
+	} else if (number > 0 && number < 1) {
+		return "less than one " + asSingular(single);
+	} else {
+		return number + " " + asPlural(single, plural);
+	}
+};
+
+// when 1, shows "one (slave)"
+globalThis.numberWithPluralOne = function(number, single, plural) {
+	if (number === 0) {
+		return "no " + asPlural(single, plural);
+	} else if (number === 1) {
+		return "one " + asSingular(single);
+	} else if (number > 0 && number < 1) {
+		return "less than one " + asSingular(single);
+	} else {
+		return number + " " + asPlural(single, plural);
+	}
+};
+// shows "less than one (slave)" instead of "no (slaves)" when number is 0.
+globalThis.numberWithPluralNonZero = function(number, single, plural) {
+	if (number === 0) {
+		number = 0.1;
+	}
+	return numberWithPlural(number, single, plural);
+};
+globalThis.onlyPlural = function(number, single, plural) {
+	if (number > 0 && number <= 1) {
+		return asSingular(single);
+	}
+	return asPlural(single, plural);
+};
+globalThis.Separator = function(SeparatorObject) {
+	if (SeparatorObject.need) {
+		return SeparatorObject.text;
+	}
+	SeparatorObject.need = true;
+	return "";
+};
+/**
+ * Returns numbers with comma, e.g. 10000 as "10,000", according to the player's settings
+ * @param {number} s
+ * @returns {string}
+ */
+globalThis.commaNum = function(s) {
+	// Separated from num because some places in code (like long lists, tables) should never have numbers spelled out, but still benefit from commas
+	if (!s) {
+		return "0";
+	}
+	if (V.formatNumbers !== 1) {
+		return s.toString();
+	} else {
+		return s.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+	}
+};
+
+/**
+ * Returns the number of weeks in a years / months / weeks format
+ * @param {number} weeks
+ * @returns {string}
+ */
+globalThis.years = function(weeks) {
+	let years = 0;
+	let quarters = 0; // needed for calc, not user facing
+	let months = 0;
+	let array = [];
+
+	// A year is always 52 weeks
+	// that could be 13 months, but lets say 4 quarters each getting an extra week (13 weeks)
+
+	// Find years
+	years = Math.trunc(weeks / 52);
+
+	if (years >= 1) { // Is there at least 1 year
+		weeks = weeks - (years * 52); // Find leftover weeks
+	}
+	if (weeks && weeks / 13 >= 1) { // Is there at least 1 quarter
+		quarters = Math.trunc(weeks / 13); // How many quarters?
+		weeks = weeks - (quarters * 13); // A quarter contains 13 weeks, how many extra weeks do we have?
+	}
+	if (weeks && weeks / 4 >= 1) { // Is there at least 1 month
+		months = Math.trunc(weeks / 4); // How many months?
+		if (months === 3) { // Almost a quarter of a year
+			months--; // Quarters have 13 weeks though, so let's be sure the extra is in weeks. Otherwise 51 will return "12 months" instead of "11 months and 4 weeks."
+		}
+		weeks = weeks - (months * 4); // A month contains 4 weeks, how many extra weeks do we have?
+	}
+
+	// So we have years, quarters, months, and weeks.
+
+	// Quarters are useless so:
+
+	months += quarters * 3; // Each quarter has three months.
+
+	if (years) {
+		array.push(`${num(years)} year${years !== 1 ? `s` : ``}`);
+	}
+
+	if (months) {
+		array.push(`${num(months)} month${months !== 1 ? `s` : ``}`);
+	}
+
+	if (weeks) {
+		array.push(`${num(weeks)} week${weeks !== 1 ? `s` : ``}`);
+	}
+
+	return array.toStringExt();
+};
+/**
+ * @param {number} [weeks]
+ * @param {number} [bonusDay]
+ * @returns {Date}
+ */
+globalThis.asDate = function(weeks = null, bonusDay = 0) {
+	if (weeks == null) {
+		weeks = V.week;
+	}
+	let d = new Date(2037, 0, 12);
+	d.setDate(d.getDate() + weeks * 7 + bonusDay);
+	return d;
+};
+/**
+ * @param {number} [weeks]
+ * @param {number} [bonusDay]
+ * @returns {string}
+ */
+globalThis.asDateString = function(weeks = null, bonusDay = 0) {
+	return asDate(weeks, bonusDay).toLocaleString(undefined, {year: 'numeric', month: 'long', day: 'numeric'});
+};
+
+/**
+ * @param {number} s
+ * @returns {string}
+ */
+globalThis.cashFormat = function(s) {
+	if (s < 0) {
+		return `-¤${commaNum(Math.abs(s))}`;
+	}
+	return `¤${commaNum(s)}`;
+};
+globalThis.cashFormatColor = function(s, invert = false) {
+	if (invert) {
+		s = -1 * s;
+	}
+	// Display red if the value is negative, unless invert is true
+	if (s < 0) {
+		return `<span class='red'>${cashFormat(s)}</span>`;
+		// White for exactly zero
+	} else if (s === 0) {
+		return `<span>${cashFormat(s)}</span>`;
+		// Yellow for positive
+	} else {
+		return `<span class='yellowgreen'>${cashFormat(s)}</span>`;
+	}
+};
+
+/**
+ * @param {number} s
+ * @returns {string}
+ */
+globalThis.repFormat = function(s) {
+	/* if (!s) { s = 0; }*/
+	if (V.cheatMode === 1 || V.debugMode === 1) {
+		if (s > 0) {
+			return `<span class="green">${commaNum(Math.round(s * 100) / 100)} rep</span>`;
+		} else if (s < 0) {
+			return `<span class="red">${commaNum(Math.round(s * 100) / 100)} rep</span>`;
+		} else {
+			return `${commaNum(Math.round(s * 100) / 100)} rep`;
+		}
+	} else {
+		/* In order to calculate just how much any one category matters so we can show a "fuzzy" symbolic value to the player, we need to know how "busy" reputation was this week. To calculate this, I ADD income to expenses. Why? 100 - 100 and 10000 - 10000 BOTH are 0, but a +50 event matters a lot more in the first case than the second. I exclude overflow from the calculation because it's not a "real" expense for our purposes, and divide by half just to make percentages a bit easier. */
+		let weight = s / (((V.lastWeeksRepIncome.Total - V.lastWeeksRepExpenses.Total) + V.lastWeeksRepExpenses.overflow) / 2);
+		if (weight > 0.60) {
+			return `<span class="green">+++++ rep</span>`;
+		} else if (weight > 0.45) {
+			return `<span class="green">++++ rep</span>`;
+		} else if (weight > 0.30) {
+			return `<span class="green">+++ rep</span>`;
+		} else if (weight > 0.15) {
+			return `<span class="green">++ rep</span>`;
+		} else if (weight > 0.0) {
+			return `<span class="green">+ rep</span>`;
+		} else if (weight === 0) {
+			return "0 rep";
+		} else if (weight < -0.60) {
+			return `<span class="red">&minus;&minus;&minus;&minus;&minus; rep</span>`;
+		} else if (weight < -0.45) {
+			return `<span class="red">&minus;&minus;&minus;&minus; rep</span>`;
+		} else if (weight < -0.30) {
+			return `<span class="red">&minus;&minus;&minus; rep</span>`;
+		} else if (weight < -0.15) {
+			return `<span class="red">&minus;&minus; rep</span>`;
+		} else if (weight < 0) {
+			return `<span class="red">&minus; rep</span>`;
+		}
+		/* return weight;*/
+	}
+};
+
+/**
+ * @param {number} s
+ * @returns {string}
+ */
+globalThis.massFormat = function(s) {
+	if (!s) {
+		s = 0;
+	}
+	if (Math.abs(s) >= 1000) {
+		s = Math.trunc(s / 1000);
+		if (s !== 1) {
+			return `${num(s)} tons`;
+		} else {
+			return `${num(s)} ton`;
+		}
+	} else {
+		return `${num(s)} kg`;
+	}
+};
+
+/**
+ * Takes an integer e.g. slave.hLength, returns a string in the format 10 inches
+ * @param {number} cm
+ * @returns {string}
+ */
+globalThis.cmToInchString = function(cm) {
+	let inches = cm / 2.54;
+	if (inches > 0 && inches < 1) {
+		return "less than an inch";
+	}
+	inches = Math.round(inches);
+	if (inches === 1) {
+		return "1 inch";
+	}
+	return `${inches} inches`;
+};
+
+/**
+ * takes an integer e.g. slave.height, returns a string in the format 6'5"
+ * @param {number} cm
+ * @returns {string}
+ */
+globalThis.cmToFootInchString = function(cm) {
+	if (Math.round(cm / 2.54) < 12) {
+		return cmToInchString(cm);
+	}
+	return `${Math.trunc(Math.round(cm / 2.54) / 12)}'${Math.round(cm / 2.54) % 12}"`;
+};
+
+/**
+ * takes a dick value e.g. slave.dick, returns a string in the format 6 inches
+ * @param {number} dick
+ * @returns {string}
+ */
+globalThis.dickToInchString = function(dick) {
+	return cmToInchString(dickToCM(dick));
+};
+
+/**
+ * takes a dick value e.g. slave.dick, returns an int of the dick length in cm
+ * @param {number} dick
+ * @returns {number}
+ */
+globalThis.dickToCM = function(dick) {
+	if (dick < 9) {
+		return dick * 5;
+	} else if (dick === 9) {
+		return 50;
+	}
+	return dick * 6;
+};
+/**
+ * takes a ball value e.g. slave.balls, returns a string in the format 3 inches
+ * @param {number} balls
+ * @returns {string}
+ */
+globalThis.ballsToInchString = function(balls) {
+	return cmToInchString(ballsToCM(balls));
+};
+
+/**
+ * takes a ball value e.g. slave.balls, returns an int of the ball size in cm
+ * @param {number} balls
+ * @returns {number}
+ */
+globalThis.ballsToCM = function(balls) {
+	if (balls < 2) {
+		return 0;
+	}
+	return (balls < 10 ? (balls - 1) * 2 : balls * 2);
+};
+
+/**
+ * takes a dick value e.g. slave.dick, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm`
+ * @param {number} dick
+ * @returns {string}
+ */
+globalThis.dickToEitherUnit = function(dick) {
+	if (V.showInches === 1) {
+		return `${dickToCM(dick)}cm (${dickToInchString(dick)})`;
+	}
+	if (V.showInches === 2) {
+		return dickToInchString(dick);
+	}
+	return `${dickToCM(dick)}cm`;
+};
+
+/**
+ * takes a ball value e.g. slave.balls, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm`
+ * @param {number} balls
+ * @returns {string}
+ */
+globalThis.ballsToEitherUnit = function(balls) {
+	if (V.showInches === 1) {
+		return `${ballsToCM(balls)}cm (${ballsToInchString(balls)})`;
+	}
+	if (V.showInches === 2) {
+		return ballsToInchString(balls);
+	}
+	return `${ballsToCM(balls)}cm`;
+};
+
+/**
+ * takes an int in centimeters e.g. slave.height, returns a string in the format of either `200cm (6'7")`, `6'7"`, or `200cm`
+ * @param {number} height
+ * @returns {string}
+ */
+globalThis.heightToEitherUnit = function(height) {
+	if (V.showInches === 1) {
+		return `${height}cm (${cmToFootInchString(height)})`;
+	}
+	if (V.showInches === 2) {
+		return cmToFootInchString(height);
+	}
+	return `${height}cm`;
+};
+
+/**
+ * takes an int in centimeters e.g. slave.hLength, returns a string in the format of either `30cm (12 inches)`, `12 inches`, or `30cm`
+ * @param {number} length
+ * @returns {string}
+ */
+globalThis.lengthToEitherUnit = function(length) {
+	if (V.showInches === 1) {
+		return `${length}cm (${cmToInchString(length)})`;
+	}
+	if (V.showInches === 2) {
+		return cmToInchString(length);
+	}
+	return `${length}cm`;
+};
-- 
GitLab