From 2b36130714e61d280a2ffdcc43d39ec4547ae92e Mon Sep 17 00:00:00 2001
From: ezsh <ezsh.junk@gmail.com>
Date: Thu, 7 May 2020 23:37:32 +0200
Subject: [PATCH] Migrate JobIDArray to JobIdMap and kill indices in facilities

Change the job id array type to dictionary of string to set of number.
Use IDs in the listings and kill index-based functions in the facility
classes.
---
 js/003-data/gameVariableData.js               |   5 +-
 src/004-base/facility.js                      |  61 +++---
 .../backwardsCompatibility.js                 |   2 +-
 src/facilities/nursery/nurseryWidgets.js      |   2 +-
 src/facilities/pit/pitFramework.js            |   2 +-
 src/init/storyInit.tw                         |   2 +-
 src/js/assignJS.js                            |  22 +-
 src/js/economyJS.js                           |  12 +-
 src/js/removeActiveSlave.js                   |   2 +-
 src/js/slaveListing.js                        | 196 +++++++++---------
 src/js/utilsFC.js                             |  13 +-
 src/uncategorized/coursingAssociation.tw      |   2 +-
 src/uncategorized/summaryOptions.tw           |   4 +-
 src/uncategorized/underperformingSlaves.tw    |   4 +-
 14 files changed, 154 insertions(+), 175 deletions(-)

diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js
index 8522712cc49..1382eb00f66 100644
--- a/js/003-data/gameVariableData.js
+++ b/js/003-data/gameVariableData.js
@@ -671,8 +671,8 @@ App.Data.resetOnNGPlus = {
 	upgradeMultiplierMedicine: 0,
 	upgradeMultiplierTrade: 0,
 	nationalities: {},
-	/** @type {Object.<string, number[]>} */
-	JobIDArray: {},
+	/** @type {Object.<string, Set<number>>} */
+	JobIDMap: {},
 	averageTrust: 0,
 	averageDevotion: 0,
 	enduringTrust: 0,
@@ -1101,6 +1101,7 @@ App.Data.resetOnNGPlus = {
 	week: 1,
 
 	weddingPlanned: 0,
+	/** @type {string|Array<{ID:number, trainingRegimen:string}>} */
 	personalAttention: "sex",
 	/** @type {FC.SlaveStateOrZero}  */
 	HeadGirl: 0,
diff --git a/src/004-base/facility.js b/src/004-base/facility.js
index d782fed8254..1ca29008502 100644
--- a/src/004-base/facility.js
+++ b/src/004-base/facility.js
@@ -64,13 +64,22 @@ App.Entity.Facilities.Job = class {
 		return this.checkRequirements(slave);
 	}
 
+	/**
+	 * Is slave with the given ID already assigned to this job
+	 * @param {number} id
+	 * @returns {boolean}
+	 */
+	hasEmployeeWithId(id) {
+		return this.employeesIDs().has(id);
+	}
+
 	/**
 	 * Is slave already assigned to this job
 	 * @param {App.Entity.SlaveState} slave
 	 * @returns {boolean}
 	 */
 	isEmployed(slave) {
-		return slave.assignment === this.desc.assignment;
+		return this.hasEmployeeWithId(slave.ID);
 	}
 
 	/**
@@ -118,26 +127,15 @@ App.Entity.Facilities.Job = class {
 	 * @returns {App.Entity.SlaveState[]}
 	 */
 	employees() {
-		const slaves = V.slaves;
-		const slaveIndices = V.slaveIndices;
-		return this.employeesIds().map(id => slaves[slaveIndices[id]]);
-	}
-
-	/**
-	 * Indices in the slaves array for all slaves that are employed at this job
-	 * @returns {number[]}
-	 */
-	employeesIndices() {
-		const slaveIndices = V.slaveIndices;
-		return this.employeesIds().map(id => slaveIndices[id]);
+		return [...this.employeesIDs()].map(id => slaveStateById(id));
 	}
 
 	/**
 	 * IDs for all slaves that are employed at this job
-	 * @returns {number[]}
+	 * @returns {Set<number>}
 	 */
-	employeesIds() {
-		return V.JobIDArray[this.desc.assignment];
+	employeesIDs() {
+		return V.JobIDMap[this.desc.assignment];
 	}
 
 	/**
@@ -319,7 +317,7 @@ App.Entity.Facilities.Facility = class {
 	/** Number of already hosted slaves
 	 * @returns {number} */
 	get hostedSlaves() {
-		return this.jobs.reduce((acc, job) => { return acc + job.employeesIds().length; }, 0);
+		return this.jobs.reduce((acc, job) => { return acc + job.employeesIDs().size; }, 0);
 	}
 
 	get hasFreeSpace() {
@@ -460,30 +458,23 @@ App.Entity.Facilities.Facility = class {
 		return V.slaves.filter(s => jobArray.some(j => j.isEmployed(s)));
 	}
 
-	/**
-	 * Indices in the slaves array for all slaves that are employed at this facility (excluding the manager)
-	 * @returns {number[]}
-	 */
-	employeesIndices() {
-		const jobArray = Object.values(this._jobs);
-		if (jobArray.length === 1) {
-			return this.job().employeesIndices();
-		}
-		return jobArray.reduce(
-			(acc, cur, idx) => { acc = acc.concat(cur.employeesIndices()); return acc; }, []);
-	}
-
 	/**
 	 * IDs for all slaves that are employed at this facility (excluding the manager)
-	 * @returns {number[]}
+	 * @returns {Set<number>}
 	 */
-	employeesIds() {
+	employeesIDs() {
 		const jobArray = Object.values(this._jobs);
 		if (jobArray.length === 1) {
-			return this.job().employeesIds();
+			return this.job().employeesIDs();
+		}
+		const res = new Set();
+		for (const j of jobArray) {
+			const ids = j.employeesIDs();
+			for (const id of ids) {
+				res.add(id);
+			}
 		}
-		return jobArray.reduce(
-			(acc, cur, idx) => { acc = acc.concat(cur.employeesIds()); return acc; }, []);
+		return res;
 	}
 
 	/**
diff --git a/src/data/backwardsCompatibility/backwardsCompatibility.js b/src/data/backwardsCompatibility/backwardsCompatibility.js
index d3e7d3c15ff..e0cc74cc4b2 100644
--- a/src/data/backwardsCompatibility/backwardsCompatibility.js
+++ b/src/data/backwardsCompatibility/backwardsCompatibility.js
@@ -166,7 +166,7 @@ App.Update.globalVariables = function(node) {
 
 	// Jobs
 	{
-		V.JobIDArray = resetJobIDArray();
+		V.JobIDMap = makeJobIdMap();
 	}
 	// Reminders
 	{
diff --git a/src/facilities/nursery/nurseryWidgets.js b/src/facilities/nursery/nurseryWidgets.js
index 9bfe61dfa64..d33e5cbd3a3 100644
--- a/src/facilities/nursery/nurseryWidgets.js
+++ b/src/facilities/nursery/nurseryWidgets.js
@@ -18247,7 +18247,7 @@ App.Facilities.Nursery.nurseryReport = function nurseryReport() {
 		const
 			slave = getSlave(V.NurseryiIDs[dI]);
 
-		V.i = App.Utils.slaveIndexForId(slave.ID);
+		V.i = V.slaveIndices[slave.ID];
 
 		slave.devotion += devBonus, slave.trust += trustBonus;
 		improveCondition(slave, healthBonus);
diff --git a/src/facilities/pit/pitFramework.js b/src/facilities/pit/pitFramework.js
index 82c6db8a383..f0d6be5a636 100644
--- a/src/facilities/pit/pitFramework.js
+++ b/src/facilities/pit/pitFramework.js
@@ -47,7 +47,7 @@ App.Entity.Facilities.Pit = class extends App.Entity.Facilities.SingleJobFacilit
 	}
 
 	get capacity() {
-		return this.established ? Number.MAX_VALUE : 0;
+		return super.capacity > 0 ? Number.MAX_VALUE : 0;
 	}
 };
 
diff --git a/src/init/storyInit.tw b/src/init/storyInit.tw
index 79c2ea431c5..ec134ed4080 100644
--- a/src/init/storyInit.tw
+++ b/src/init/storyInit.tw
@@ -45,7 +45,7 @@ You should have received a copy of the GNU General Public License along with thi
 <<run setup.prostheticIDs.forEach(function(id) {
 	$prosthetics[id] = {amount: 0, research: 0};
 })>>
-<<set $JobIDArray = resetJobIDArray()>>
+<<set $JobIDMap = makeJobIdMap()>>
 
 /*SFVAR*/ <<set $SF = Object.assign({}, $SF, {Toggle:0, Active: -1}), $SF.Facility = Object.assign({}, $SF.Facility, {Toggle:0, Active:0})>>
 
diff --git a/src/js/assignJS.js b/src/js/assignJS.js
index 8f988a64aab..4a6da40cbfc 100644
--- a/src/js/assignJS.js
+++ b/src/js/assignJS.js
@@ -428,7 +428,7 @@ globalThis.assignJob = function(slave, job) {
 			}
 		}
 	}
-	V.JobIDArray = resetJobIDArray();
+	V.JobIDArray = makeJobIdMap();
 	if (idx >= 0) { V.slaves[idx] = slave; }
 
 	return r;
@@ -666,7 +666,7 @@ globalThis.removeJob = function(slave, assignment, saveRecord = false) {
 		slave.sentence = 0;
 		slave.subTarget = 0;
 	}
-	V.JobIDArray = resetJobIDArray();
+	V.JobIDArray = makeJobIdMap();
 	if (idx >= 0) {
 		V.slaves[idx] = slave;
 	}
@@ -727,24 +727,24 @@ globalThis.assignmentVisible = function(slave) {
 };
 
 /**
- * @returns {Object.<string, number[]>} dictionary assignment -> slave IDs
+ * @returns {Object.<string, Set<number>>} dictionary assignment -> slave IDs
  */
-globalThis.resetJobIDArray = function() {
-	/** @type {Object.<string, number[]>} */
-	const JobIDArray = {};
+globalThis.makeJobIdMap = function() {
+	/** @type {Object.<string, Set<number>>} */
+	const res = {};
 	for (const jn of Object.values(Job)) {
-		JobIDArray[jn] = [];
+		res[jn] = new Set();
 	}
 
 	for (const slave of V.slaves) {
-		JobIDArray[slave.assignment].push(slave.ID);
+		res[slave.assignment].add(slave.ID);
 	}
 
 	// special cases
-	JobIDArray[Job.PIT] = V.fighterIDs;
-	JobIDArray[Job.LURCHER].push(V.LurcherID);
+	res[Job.PIT] = new Set(V.fighterIDs);
+	res[Job.LURCHER].add(V.LurcherID);
 
-	return JobIDArray;
+	return res;
 };
 
 /**
diff --git a/src/js/economyJS.js b/src/js/economyJS.js
index 957cd720551..42395682832 100644
--- a/src/js/economyJS.js
+++ b/src/js/economyJS.js
@@ -289,7 +289,7 @@ globalThis.calculateCosts = (function() {
 		V.ServQiIDs.forEach(ID => {
 			number += getSlaveMinorCosts(V.slaves[V.slaveIndices[ID]]);
 		});
-		V.JobIDArray[Job.HOUSE].forEach(ID => {
+		V.JobIDMap[Job.HOUSE].forEach(ID => {
 			number += getSlaveMinorCosts(V.slaves[V.slaveIndices[ID]]);
 		});
 		if (V.slaves.length > number) {
@@ -310,7 +310,7 @@ globalThis.calculateCosts = (function() {
 		V.ServQiIDs.forEach(ID => {
 			numberServed += getSlaveMinorCosts(V.slaves[V.slaveIndices[ID]]);
 		});
-		V.JobIDArray[Job.HOUSE].forEach(ID => {
+		V.JobIDMap[Job.HOUSE].forEach(ID => {
 			numberServed += getSlaveMinorCosts(V.slaves[V.slaveIndices[ID]]);
 		});
 
@@ -471,7 +471,7 @@ globalThis.calculateCosts = (function() {
 				cashX(Math.abs(slaveCostMinor), "slaveAssignmentQuarter", V.slaves[V.slaveIndices[ID]]);
 			}
 		});
-		V.JobIDArray[Job.HOUSE].forEach(ID => {
+		V.JobIDMap[Job.HOUSE].forEach(ID => {
 			if (V.slaves.length > numberServed) {
 				slaveCostMinor = Math.trunc(reducibleUpkeep / V.slaves.length * getSlaveMinorCosts(V.slaves[V.slaveIndices[ID]]));
 				cashX(Math.abs(slaveCostMinor), "slaveAssignmentHouse", V.slaves[V.slaveIndices[ID]]);
@@ -1613,7 +1613,7 @@ globalThis.slaveJobValues = function(lowerClassSexDemandRef, middleClassSexDeman
 	}
 
 	// Glory hole slaves adding to 'arcade'
-	V.JobIDArray[Job.GLORYHOLE].forEach(ID => {
+	V.JobIDMap[Job.GLORYHOLE].forEach(ID => {
 		const s = V.slaves[V.slaveIndices[ID]];
 		s.sexAmount = Math.trunc(restEffects(s, 20) * ((normalRandInt(600, 20) + (4 - s.anus) * 10 + (4 - s.vagina) * 10 + Math.trunc(s.health.condition / 2)) * 0.75));
 		tiredFucks(s);
@@ -1628,7 +1628,7 @@ globalThis.slaveJobValues = function(lowerClassSexDemandRef, middleClassSexDeman
 	});
 
 	// Public sluts adding to 'club'
-	V.JobIDArray[Job.PUBLIC].forEach(ID => {
+	V.JobIDMap[Job.PUBLIC].forEach(ID => {
 		SJVClub(V.slaves[V.slaveIndices[ID]]);
 	});
 
@@ -1643,7 +1643,7 @@ globalThis.slaveJobValues = function(lowerClassSexDemandRef, middleClassSexDeman
 	}
 
 	// Street whores adding to 'brothel'
-	V.JobIDArray[Job.WHORE].forEach(ID => {
+	V.JobIDMap[Job.WHORE].forEach(ID => {
 		SJVBrothel(V.slaves[V.slaveIndices[ID]]);
 	});
 
diff --git a/src/js/removeActiveSlave.js b/src/js/removeActiveSlave.js
index 0f70407f7fd..6cecd81599a 100644
--- a/src/js/removeActiveSlave.js
+++ b/src/js/removeActiveSlave.js
@@ -235,7 +235,7 @@ globalThis.removeActiveSlave = function() {
 		removeSlave(INDEX);
 		LENGTH--;
 		V.activeSlave = 0;
-		V.JobIDArray = resetJobIDArray(); /* need to call this once more to update count of resting slaves*/
+		V.JobIDMap = makeJobIdMap(); /* need to call this once more to update count of resting slaves*/
 	}
 };
 /**
diff --git a/src/js/slaveListing.js b/src/js/slaveListing.js
index b81ac228afc..dfa08d04690 100644
--- a/src/js/slaveListing.js
+++ b/src/js/slaveListing.js
@@ -10,19 +10,18 @@ App.UI.SlaveList = {};
 /**
  * @callback slaveTextGenerator
  * @param {App.Entity.SlaveState} slave
- * @param {number} index
  * @returns {string}
  */
 
 /**
  * @callback slaveToElement
  * @param {App.Entity.SlaveState} slave
- * @param {number} index
  * @returns {HTMLElement|DocumentFragment}
  */
 
 App.UI.SlaveList.render = function() {
-	'use strict';
+	const facilityPassages = new Set(
+		["Main", "Head Girl Suite", "Spa", "Brothel", "Club", "Arcade", "Clinic", "Schoolroom", "Dairy", "Farmyard", "Servants' Quarters" , "Master Suite", "Cellblock"]);
 
 	/** @type {string} */
 	let passageName;
@@ -43,39 +42,46 @@ App.UI.SlaveList.render = function() {
 	};
 
 	/**
-	 * @param {number[]} indices
-	 * @param {Array.<{index: number, rejects: string[]}>} rejectedSlaves
+	 * @param {number[]} IDs
+	 * @param {Array.<{id: number, rejects: string[]}>} rejectedSlaves
 	 * @param {slaveToElement} interactionLink
 	 * @param {slaveToElement} [postNote]
 	 * @returns {DocumentFragment}
 	 */
-	function listDOM(indices, rejectedSlaves, interactionLink, postNote) {
+	function listDOM(IDs, rejectedSlaves, interactionLink, postNote) {
 		passageName = passage();
 		slaves = V.slaves;
 
 		let res = document.createDocumentFragment();
 
 		if (V.useSlaveListInPageJSNavigation === 1) {
-			res.appendChild(createQuickList(indices));
+			res.appendChild(createQuickList(IDs));
 		}
 
 		const fcs = App.Entity.facilities;
+		const penthouse = fcs.penthouse;
 
-		// can't simply loop over fcs attributes, as there is the penthouse among them, which always exists
-		const anyFacilityExists = fcs.brothel.established || fcs.club.established || fcs.dairy.established || fcs.farmyard.established || fcs.servantsQuarters.established || fcs.masterSuite.established || fcs.spa.established || fcs.clinic + fcs.schoolroom.established || fcs.cellblock.established || fcs.arcade.established || fcs.headGirlSuite.established;
+		let anyFacilityExists = false;
+
+		for (const f of Object.values(fcs)) {
+			if (f !== penthouse && f.established) {
+				anyFacilityExists = true;
+				break;
+			}
+		}
 
 		let showTransfers = false;
 		if (anyFacilityExists) {
-			if (passageName === "Main" || passageName === "Head Girl Suite" || passageName === "Spa" || passageName === "Brothel" || passageName === "Club" || passageName === "Arcade" || passageName === "Clinic" || passageName === "Schoolroom" || passageName === "Dairy" || passageName === "Farmyard" || passageName === "Servants' Quarters" || passageName === "Master Suite" || passageName === "Cellblock") {
+			if (facilityPassages.has(passageName)) {
 				V.returnTo = passageName;
 				showTransfers = true;
 			}
 		}
 
-		for (const _si of indices) {
-			let ss = renderSlave(_si, interactionLink, showTransfers, postNote);
+		for (const _sid of IDs) {
+			let ss = renderSlave(_sid, interactionLink, showTransfers, postNote);
 			let slaveDiv = document.createElement("div");
-			slaveDiv.id = `slave-${slaves[_si].ID}`;
+			slaveDiv.id = `slave-${_sid}`;
 			slaveDiv.classList.add("slaveSummary");
 			if (V.slavePanelStyle === 2) {
 				slaveDiv.classList.add("card");
@@ -85,7 +91,7 @@ App.UI.SlaveList.render = function() {
 		}
 
 		for (const rs of rejectedSlaves) {
-			const slave = slaves[rs.index];
+			const slave = slaveStateById(rs.id);
 			const rejects = rs.rejects;
 			const slaveName = SlaveFullName(slave);
 			let slaveDiv = document.createElement("div");
@@ -110,15 +116,15 @@ App.UI.SlaveList.render = function() {
 	}
 
 	/**
-	 * @param {number[]} indices
-	 * @param {Array.<{index: number, rejects: string[]}>} rejectedSlaves
+	 * @param {number[]} IDs
+	 * @param {Array.<{id: number, rejects: string[]}>} rejectedSlaves
 	 * @param {slaveToElement} interactionLink
 	 * @param {slaveToElement} [postNote]
 	 * @returns {string}
 	 */
-	function listMarkup(indices, rejectedSlaves, interactionLink, postNote) {
+	function listMarkup(IDs, rejectedSlaves, interactionLink, postNote) {
 		const listIDStr = `slave-list-${listID}`;
-		readyResults[listID] = listDOM(indices, rejectedSlaves, interactionLink, postNote);
+		readyResults[listID] = listDOM(IDs, rejectedSlaves, interactionLink, postNote);
 
 		$(document).one(':passagedisplay', function() {
 			for (const e of document.querySelectorAll('[id^=slave-list]')) {
@@ -141,13 +147,13 @@ App.UI.SlaveList.render = function() {
 
 
 	/**
-	 * @param {number} index
+	 * @param {number} id
 	 * @param {slaveToElement} interactionLink
 	 * @param {boolean} showTransfers
 	 * @param {slaveToElement} [postNote]
 	 * @returns {DocumentFragment}
 	 */
-	function renderSlave(index, interactionLink, showTransfers, postNote) {
+	function renderSlave(id, interactionLink, showTransfers, postNote) {
 		let res = document.createDocumentFragment();
 		if (V.slavePanelStyle === 0) {
 			res.appendChild(document.createElement("br"));
@@ -156,7 +162,7 @@ App.UI.SlaveList.render = function() {
 			hr.style.margin = "0";
 			res.appendChild(hr);
 		}
-		const slave = slaves[index];
+		const slave = slaveStateById(id);
 
 		if ((V.seeImages === 1) && (V.seeSummaryImages === 1)) {
 			let imgDiv = document.createElement("div");
@@ -166,7 +172,7 @@ App.UI.SlaveList.render = function() {
 			res.appendChild(imgDiv);
 		}
 		// res.push(dividerAndImage(slave));
-		res.appendChild(interactionLink(slave, index));
+		res.appendChild(interactionLink(slave));
 
 		if ((slave.choosesOwnClothes === 1) && (slave.clothes === "choosing her own clothes")) {
 			const _oldDevotion = slave.devotion;
@@ -329,8 +335,8 @@ App.UI.SlaveList.render = function() {
 
 		res.appendChild(App.UI.SlaveSummary.render(slave));
 
-		if (postNote !== undefined) {
-			const pn = postNote(slave, index);
+		if (postNote) {
+			const pn = postNote(slave);
 			if (pn) {
 				let r = document.createElement("p");
 				r.classList.add("si");
@@ -343,10 +349,10 @@ App.UI.SlaveList.render = function() {
 	}
 
 	/**
-	 * @param {number[]} indices
+	 * @param {number[]} IDs
 	 * @returns {DocumentFragment}
 	 */
-	function createQuickList(indices) {
+	function createQuickList(IDs) {
 		/**
 		 *
 		 * @param {Node} container
@@ -384,7 +390,7 @@ App.UI.SlaveList.render = function() {
 		*	<<print 'pass/count/indexed/flag::[' + passageName + '/' + _Count + '/' + _indexed + '/' + $SlaveSummaryFiler + ']'>>
 		*/
 
-		if (indices.length > 1 && passageName === "Main") {
+		if (IDs.length > 1 && passageName === "Main") {
 			const _buttons = [];
 			let _offset = -50;
 			if (/Select/i.test(passageName)) {
@@ -409,8 +415,8 @@ App.UI.SlaveList.render = function() {
 			const listIndex = makeElement(res, "div", undefined, "hidden");
 			listIndex.id = `list_index${listID}`;
 
-			for (const _ssii of indices) {
-				const _IndexSlave = slaves[_ssii];
+			for (const sID of IDs) {
+				const _IndexSlave = slaveStateById(sID);
 				const _indexSlaveName = SlaveFullName(_IndexSlave);
 				const _devotionClass = getSlaveDevotionClass(_IndexSlave);
 				const _trustClass = getSlaveTrustClass(_IndexSlave);
@@ -464,17 +470,18 @@ App.UI.SlaveList.Decoration = {};
  * @param {App.Entity.SlaveState} slave
  * @returns {HTMLElement}
  */
-App.UI.SlaveList.Decoration.penthousePositions = (slave) => {
-	if (App.Data.Facilities.headGirlSuite.manager.assignment === slave.assignment) {
+App.UI.SlaveList.Decoration.penthousePositions = function(slave) {
+	const fcs = App.Entity.facilities;
+	if (fcs.headGirlSuite.manager.isEmployed(slave)) {
 		return App.UI.DOM.makeElement("span", 'HG', ['lightcoral', 'strong']);
 	}
-	if (App.Data.Facilities.penthouse.manager.assignment === slave.assignment) {
+	if (fcs.penthouse.manager.isEmployed(slave)) {
 		return App.UI.DOM.makeElement("span", 'RC', ['lightcoral', 'strong']);
 	}
-	if (App.Data.Facilities.armory.manager.assignment === slave.assignment) {
+	if (fcs.armory.manager.isEmployed(slave)) {
 		return App.UI.DOM.makeElement("span", 'BG', ['lightcoral', 'strong']);
 	}
-	if (Array.isArray(V.personalAttention) && V.personalAttention.findIndex(s => s.ID === slave.ID) !== -1) {
+	if (Array.isArray(V.personalAttention) && V.personalAttention.some(s => s.ID === slave.ID)) {
 		return App.UI.DOM.makeElement("span", 'PA', ['lightcoral', 'strong']);
 	}
 	return null;
@@ -510,21 +517,23 @@ App.UI.SlaveList.SlaveInteract = {};
 
 /**
  * @param {App.Entity.SlaveState} slave
- * @param {number} index
  * @returns {HTMLElement}
  */
-App.UI.SlaveList.SlaveInteract.stdInteract = (slave, index) =>
-	App.UI.DOM.passageLink(SlaveFullName(slave), 'Slave Interact', () => { App.UI.SlaveList.ScrollPosition.record(); App.Utils.setActiveSlaveByIndex(index); });
+App.UI.SlaveList.SlaveInteract.stdInteract = function(slave) {
+	return App.UI.DOM.passageLink(SlaveFullName(slave), 'Slave Interact', () => {
+		App.UI.SlaveList.ScrollPosition.record();
+		V.activeSlave = slave;
+	});
+};
 
 
 /**
  * @param {App.Entity.SlaveState} slave
- * @param {number} index
  * @returns {DocumentFragment|HTMLElement}
  */
-App.UI.SlaveList.SlaveInteract.penthouseInteract = (slave, index) => {
+App.UI.SlaveList.SlaveInteract.penthouseInteract = function(slave) {
 	let decoration = App.UI.SlaveList.Decoration.penthousePositions(slave);
-	let stdLink = App.UI.SlaveList.SlaveInteract.stdInteract(slave, index);
+	let stdLink = App.UI.SlaveList.SlaveInteract.stdInteract(slave);
 	if (decoration) {
 		let fr = document.createDocumentFragment();
 		fr.appendChild(decoration);
@@ -647,8 +656,8 @@ App.UI.SlaveList.listSJFacilitySlaves = function(facility, facilityPassage, show
 		'</div>';
 
 	if (facility.hostedSlaves > 0) {
-		let facilitySlaves = job.employeesIndices();
-		SlaveSort.indices(facilitySlaves);
+		const facilitySlaves = [...job.employeesIDs()];
+		SlaveSort.IDs(facilitySlaves);
 		r += App.UI.tabbar.makeTab("remove", App.UI.SlaveList.render.listMarkup(facilitySlaves, [],
 			App.UI.SlaveList.SlaveInteract.stdInteract,
 			(slave) => App.UI.DOM.link(`Retrieve ${getPronouns(slave).object} from ${facility.name}`, () => removeJob(slave, job.desc.assignment), [], facilityPassage)
@@ -658,20 +667,19 @@ App.UI.SlaveList.listSJFacilitySlaves = function(facility, facilityPassage, show
 	}
 
 	/**
-	 * @param {number[]} slaveIdxs
+	 * @param {number[]} slaveIDs
 	 * @returns {string}
 	 */
-	function assignableTabContent(slaveIdxs) {
-		SlaveSort.indices(slaveIdxs);
-		const slaves = V.slaves;
+	function assignableTabContent(slaveIDs) {
+		SlaveSort.IDs(slaveIDs);
 		let rejectedSlaves = [];
 		let passedSlaves = [];
-		slaveIdxs.forEach((idx) => {
-			const rejects = facility.canHostSlave(slaves[idx]);
+		slaveIDs.forEach((id) => {
+			const rejects = facility.canHostSlave(slaveStateById(id));
 			if (rejects.length > 0) {
-				rejectedSlaves.push({index: idx, rejects: rejects});
+				rejectedSlaves.push({id: id, rejects: rejects});
 			} else {
-				passedSlaves.push(idx);
+				passedSlaves.push(id);
 			}
 		}, []);
 		return App.UI.SlaveList.render.listMarkup(passedSlaves, rejectedSlaves,
@@ -679,10 +687,10 @@ App.UI.SlaveList.listSJFacilitySlaves = function(facility, facilityPassage, show
 			(slave) => App.UI.DOM.link(`Send ${getPronouns(slave).object} to ${facility.name}`, () => { assignmentTransition(slave, job.desc.assignment, facilityPassage); }));
 	}
 	if (facility.hasFreeSpace) {
-		const assignableSlaveIdxs = job.desc.partTime ?
-			Array.apply(null, {length: V.slaves.length}).map(Number.call, Number) : // all slaves can work here
-			App.Entity.facilities.penthouse.employeesIndices(); // only slaves from the penthouse can be transferred here
-		r += App.UI.tabbar.makeTab("assign", assignableTabContent(assignableSlaveIdxs));
+		const assignableSlaveIDs = job.desc.partTime ?
+			V.slaves.map(slave => slave.ID) : // all slaves can work here
+			[...App.Entity.facilities.penthouse.employeesIDs()]; // only slaves from the penthouse can be transferred here
+		r += App.UI.tabbar.makeTab("assign", assignableTabContent(assignableSlaveIDs));
 	} else {
 		r += App.UI.tabbar.makeTab("assign", `<strong>${capFirstChar(facility.name)} is full and cannot hold any more slaves</strong>`);
 	}
@@ -690,13 +698,13 @@ App.UI.SlaveList.listSJFacilitySlaves = function(facility, facilityPassage, show
 	if (showTransfersTab) {
 		if (facility.hasFreeSpace) {
 			// slaves from other facilities can be transferred here
-			const transferableIndices = V.slaves.reduce((acc, slave, ind) => {
+			const transferableIDs = V.slaves.reduce((acc, slave) => {
 				if (!assignmentVisible(slave) && !facility.isHosted(slave)) {
-					acc.push(ind);
+					acc.push(slave.ID);
 				}
 				return acc;
 			}, []);
-			r += App.UI.tabbar.makeTab("transfer", assignableTabContent(transferableIndices));
+			r += App.UI.tabbar.makeTab("transfer", assignableTabContent(transferableIDs));
 		} else {
 			r += App.UI.tabbar.makeTab("transfer", `<strong>${capFirstChar(facility.name)} is full and cannot hold any more slaves</strong>`);
 		}
@@ -787,7 +795,7 @@ App.UI.SlaveList.displayManager = function(facility, selectionPassage) {
 	selectionPassage = selectionPassage || `${managerCapName} Select`;
 	const manager = facility.manager.currentEmployee;
 	if (manager) {
-		return this.render.listMarkup([App.Utils.slaveIndexForId(manager.ID)], [],
+		return this.render.listMarkup([manager.ID], [],
 			App.UI.SlaveList.SlaveInteract.stdInteract,
 			() => App.UI.DOM.passageLink(`Change or remove ${managerCapName}`, selectionPassage));
 	} else {
@@ -825,7 +833,7 @@ App.UI.SlaveList.penthousePage = function() {
 			const link = App.UI.DOM.makeElement("span", App.UI.DOM.passageLink("Manage Head Girl", "HG Select"), "major-link");
 			link.id = "manageHG";
 			slaveWrapper.append(link, " ", App.UI.DOM.makeElement("span", "[H]", "hotkey"));
-			slaveWrapper.append(App.UI.SlaveList.render.listDOM([App.Utils.slaveIndexForId(HG.ID)], [],
+			slaveWrapper.append(App.UI.SlaveList.render.listDOM([HG.ID], [],
 				App.UI.SlaveList.SlaveInteract.penthouseInteract));
 		} else {
 			if (V.slaves.length > 1) {
@@ -868,7 +876,7 @@ App.UI.SlaveList.penthousePage = function() {
 			const link = App.UI.DOM.makeElement("span", App.UI.DOM.passageLink("Manage Recruiter", "Recruiter Select"), "major-link");
 			link.id = "manageRecruiter";
 			slaveWrapper.append(link, " ", App.UI.DOM.makeElement("span", "[U]", "hotkey"));
-			slaveWrapper.append(App.UI.SlaveList.render.listDOM([App.Utils.slaveIndexForId(RC.ID)], [],
+			slaveWrapper.append(App.UI.SlaveList.render.listDOM([RC.ID], [],
 				App.UI.SlaveList.SlaveInteract.penthouseInteract));
 		} else {
 			slaveWrapper.append("You have ", App.UI.DOM.makeElement("span", "not", "warning"), " selected a Recruiter. ",
@@ -891,7 +899,7 @@ App.UI.SlaveList.penthousePage = function() {
 				const link = App.UI.DOM.makeElement("span", App.UI.DOM.passageLink("Manage Bodyguard", "BG Select"), "major-link");
 				link.id = "manageBG";
 				slaveWrapper.append(link, " ", App.UI.DOM.makeElement("span", "[B]", "hotkey"));
-				slaveWrapper.append(App.UI.SlaveList.render.listDOM([App.Utils.slaveIndexForId(BG.ID)], [],
+				slaveWrapper.append(App.UI.SlaveList.render.listDOM([BG.ID], [],
 					App.UI.SlaveList.SlaveInteract.penthouseInteract));
 				slaveWrapper.append(App.MainView.useGuard());
 			} else {
@@ -914,28 +922,16 @@ App.UI.SlaveList.penthousePage = function() {
 	 * @returns {{n: number, dom: DocumentFragment}}
 	 */
 	function _slavesForJob(job) {
-		const employeesIndices = job === 'all' ? ph.employeesIndices() : ph.job(job).employeesIndices();
-		if (employeesIndices.length === 0) {
+		const employeesIDs = job === 'all' ? [...ph.employeesIDs()] : [...ph.job(job).employeesIDs()];
+		if (employeesIDs.length === 0) {
 			return {n: 0, dom: document.createDocumentFragment()};
 		}
 
-		SlaveSort.indices(employeesIndices);
-
-		if (V.fucktoyInteractionsPosition === 1 && job === "fucktoy") {
-			const fragment = document.createDocumentFragment();
-			for (const i of employeesIndices) {
-				fragment.append(App.UI.SlaveList.render.listDOM([i], [], App.UI.SlaveList.SlaveInteract.penthouseInteract));
-				fragment.append(App.MainView.useFucktoy(V.slaves[i]));
-			}
-			return {
-				n: employeesIndices.length,
-				dom: fragment
-			};
-		}
-
+		SlaveSort.IDs(employeesIDs);
 		return {
-			n: employeesIndices.length,
-			dom: App.UI.SlaveList.render.listDOM(employeesIndices, [], App.UI.SlaveList.SlaveInteract.penthouseInteract)
+			n: employeesIDs.length,
+			dom: App.UI.SlaveList.render.listDOM(employeesIDs, [], App.UI.SlaveList.SlaveInteract.penthouseInteract,
+				V.fucktoyInteractionsPosition === 1 && job === "fucktoy" ? App.MainView.useFucktoy : null)
 		};
 	}
 
@@ -1011,10 +1007,10 @@ App.UI.SlaveList.penthousePage = function() {
 	}
 
 	function allTab() {
-		const penthouseSlavesIndices = ph.employeesIndices();
-		SlaveSort.indices(penthouseSlavesIndices);
-		return makeTabDesc('all', `All${V.useSlaveSummaryTabs > 0 ? ` (${penthouseSlavesIndices.length})` : ""}`,
-			App.UI.SlaveList.render.listDOM(penthouseSlavesIndices, [], App.UI.SlaveList.SlaveInteract.penthouseInteract));
+		const penthouseSlavesIDs = [...ph.employeesIDs()];
+		SlaveSort.IDs(penthouseSlavesIDs);
+		return makeTabDesc('all', `All${V.useSlaveSummaryTabs > 0 ? ` (${penthouseSlavesIDs.length})` : ""}`,
+			App.UI.SlaveList.render.listDOM(penthouseSlavesIDs, [], App.UI.SlaveList.SlaveInteract.penthouseInteract));
 	}
 
 	let fragment = document.createDocumentFragment();
@@ -1192,33 +1188,33 @@ App.UI.SlaveList.slaveSelectionList = function() {
 	 */
 	function _listSlaves(assignmentStr, options) {
 		const slaves = V.slaves;
-		let unfilteredIndices = [];
+		let unfilteredIDs = [];
 		switch (assignmentStr) {
 			case 'all':
-				unfilteredIndices = Array.from({length: slaves.length}, (v, i) => i);
+				unfilteredIDs = slaves.map(s => s.ID);
 				break;
 			case 'experienced':
-				unfilteredIndices = slaves.reduce((acc, s, idx) => {
+				unfilteredIDs = slaves.reduce((acc, s, idx) => {
 					if (options.expCheck(s)) {
-						acc.push(idx);
+						acc.push(s.ID);
 					}
 					return acc;
 				}, []);
 				break;
 			default:
-				unfilteredIndices = App.Entity.facilities[assignmentStr].employeesIndices();
+				unfilteredIDs = [...App.Entity.facilities[assignmentStr].employeesIDs()]; // set to array
 				break;
 		}
-		SlaveSort.indices(unfilteredIndices);
-		let passingIndices = [];
+		SlaveSort.IDs(unfilteredIDs);
+		let passingIDs = [];
 		let rejects = [];
 
-		unfilteredIndices.forEach(idx => {
-			const fr = options.filter(slaves[idx]);
+		unfilteredIDs.forEach(id => {
+			const fr = options.filter(slaveStateById(id));
 			if (fr === true || (Array.isArray(fr) && fr.length === 0)) {
-				passingIndices.push(idx);
+				passingIDs.push(id);
 			} else {
-				if (Array.isArray(fr)) { rejects.push({index: idx, rejects: fr}); }
+				if (Array.isArray(fr)) { rejects.push({id: id, rejects: fr}); }
 			}
 		});
 
@@ -1226,17 +1222,17 @@ App.UI.SlaveList.slaveSelectionList = function() {
 		// done this way to test for tests presence only once
 		const listPostNote = options.expCheck ?
 			(options.postNote ?
-				(s, i) => options.expCheck(s) ? App.UI.DOM.combineNodes(
+				s => options.expCheck(s) ? App.UI.DOM.combineNodes(
 					App.UI.DOM.makeElement("span", "Has applicable career experience.", "lime"),
 					document.createElement("br"),
-					options.postNote(s, i)
-				) : options.postNote(s, i) :
-				(s) => options.expCheck(s) ? App.UI.DOM.makeElement("span", "Has applicable career experience.", "lime") : null) :
+					options.postNote(s)
+				) : options.postNote(s) :
+				s => options.expCheck(s) ? App.UI.DOM.makeElement("span", "Has applicable career experience.", "lime") : null) :
 			options.postNote ?
-				(s, i) => options.postNote(s, i) :
+				s => options.postNote(s) :
 				() => null;
 
-		return App.UI.SlaveList.render.listDOM(passingIndices, rejects, options.interactionLink, listPostNote);
+		return App.UI.SlaveList.render.listDOM(passingIDs, rejects, options.interactionLink, listPostNote);
 	}
 }();
 
diff --git a/src/js/utilsFC.js b/src/js/utilsFC.js
index 1016f35b2e0..a3ba9217eb5 100644
--- a/src/js/utilsFC.js
+++ b/src/js/utilsFC.js
@@ -2677,15 +2677,6 @@ App.Utils.setActiveSlaveByIndex = function(index) {
 	}
 };
 
-/**
- * Returns index in the slave array for the given ID
- * @param {number} id slave ID
- * @returns {number}
- */
-App.Utils.slaveIndexForId = function(id) {
-	return V.slaveIndices[id];
-};
-
 /**
  * Sets temporary variables named by the scheme, described below, to pronouns for the given slave
  * @param {App.Entity.SlaveState} slave
@@ -2836,8 +2827,8 @@ globalThis.getBestSlaves = function({part, count = 3, largest = true, filter = (
  * @param {{}} info see getBestSlaves()
  * @returns {number[]}
  */
-globalThis.getBestSlavesIndices = function(info) {
-	return getBestSlaves(info).map(slave => V.slaveIndices[slave.ID]);
+globalThis.getBestSlavesIDs = function(info) {
+	return getBestSlaves(info).map(slave => slave.ID);
 };
 
 /*
diff --git a/src/uncategorized/coursingAssociation.tw b/src/uncategorized/coursingAssociation.tw
index b6b48633449..a1d356c18ad 100644
--- a/src/uncategorized/coursingAssociation.tw
+++ b/src/uncategorized/coursingAssociation.tw
@@ -16,7 +16,7 @@ The chasing slaves are known as lurchers, the term once used for the sighthounds
 
 <<if $LurcherID != 0>>
 	<br><br>''Fire your Lurcher:''
-	<<= App.UI.SlaveList.render.listMarkup([App.Utils.slaveIndexForId($LurcherID)], [],
+	<<= App.UI.SlaveList.render.listMarkup([$LurcherID], [],
 		(slave) => App.UI.DOM.passageLink(SlaveFullName(slave), 'Coursing Association', () => removeJob(slave, Job.LURCHER)))>>
 <</if>>
 
diff --git a/src/uncategorized/summaryOptions.tw b/src/uncategorized/summaryOptions.tw
index 4999d533ca7..74411f44be2 100644
--- a/src/uncategorized/summaryOptions.tw
+++ b/src/uncategorized/summaryOptions.tw
@@ -35,7 +35,7 @@
 	.addValueList([["Ascending", "ascending"], ["Descending", "descending"]])>>
 
 	<<run _options.addOption("Slaves are sorted by", "sortSlavesBy")
-	.addValueList([["Devotion", "devotion"], ["Name", "name"], ["Date purchased", "seniority"], ["Age", "actualAge"], 
+	.addValueList([["Devotion", "devotion"], ["Name", "name"], ["Date purchased", "seniority"], ["Age", "actualAge"],
 	["How old they look", "visualAge"], ["Age of their body", "physicalAge"], ["Assignment", "assignment"], ["Weekly Income", "weeklyIncome"]])>>
 <</if>>
 
@@ -44,7 +44,7 @@
 <h2>Individual panels</h2>
 Sample summary:
 <<= App.UI.SlaveList.render.listMarkup(
-	[App.Utils.slaveIndexForId($activeSlave.ID)],
+	[$activeSlave.ID],
 	[],
 	App.UI.SlaveList.SlaveInteract.stdInteract
 )>>
diff --git a/src/uncategorized/underperformingSlaves.tw b/src/uncategorized/underperformingSlaves.tw
index ee7a8cf6587..0646e3bc070 100644
--- a/src/uncategorized/underperformingSlaves.tw
+++ b/src/uncategorized/underperformingSlaves.tw
@@ -30,7 +30,7 @@
 			Take the rough value of a slave and divide it by how much they made overall last week. This will tell you how many weeks it might take them to earn the same amount you'd get for selling them right now.
 		</div>
 		<<print App.UI.SlaveList.render.listMarkup(
-			getBestSlavesIndices(
+			getBestSlavesIDs(
 				{
 					part:(slave) => {
 						const ratio = slaveCost(slave) / (slave.lastWeeksCashIncome - getSlaveCost(slave));
@@ -59,7 +59,7 @@
 			This list looks for moochers by weighing their weekly income against the weekly cost of providing for them.
 		</div>
 		<<print App.UI.SlaveList.render.listMarkup(
-			getBestSlavesIndices(
+			getBestSlavesIDs(
 				{
 					part:(slave) => (slave.lastWeeksCashIncome - getSlaveCost(slave)),
 					largest: false,
-- 
GitLab