From 3a9177f42a5a4979f4320bf5d58f3b1f41f60bfc Mon Sep 17 00:00:00 2001
From: Arkerthan <arkerthan@mailbox.org>
Date: Tue, 22 Nov 2022 20:58:12 +0100
Subject: [PATCH] Add a combat training job to the pit, logic part

---
 devTools/types/FC/facilities.d.ts             |  3 ++
 devTools/types/FC/human.d.ts                  |  3 +-
 js/003-data/constants.js                      |  1 +
 src/002-config/fc-version.js                  |  2 +-
 .../backwardsCompatibility.js                 |  3 ++
 src/facilities/pit/pitFramework.js            | 46 +++++++++++++++++--
 src/facilities/pit/pitUtils.js                |  5 +-
 .../servantsQuarters/servantsQuarters.js      |  6 +--
 src/js/DefaultRules.js                        |  2 +-
 src/js/assignJS.js                            | 18 +++++++-
 src/npc/surgery/surgery.js                    |  1 +
 src/pregmod/eliteBreedingExam.js              | 12 +++--
 12 files changed, 85 insertions(+), 17 deletions(-)

diff --git a/devTools/types/FC/facilities.d.ts b/devTools/types/FC/facilities.d.ts
index b1c568ea241..36c3bbd4d02 100644
--- a/devTools/types/FC/facilities.d.ts
+++ b/devTools/types/FC/facilities.d.ts
@@ -178,7 +178,10 @@ declare namespace FC {
 		interface Pit {
 			/** Defaults to "the Pit" if not otherwise set. */
 			name: string;
+			// Arena section
+			trainingIDs: number[];
 
+			// Pit section
 			/** The animal fighting a slave if not null. */
 			animal: string | "random";
 			/** The type of audience the Pit has. */
diff --git a/devTools/types/FC/human.d.ts b/devTools/types/FC/human.d.ts
index 9424dc5ba43..ca374b34e87 100644
--- a/devTools/types/FC/human.d.ts
+++ b/devTools/types/FC/human.d.ts
@@ -72,7 +72,7 @@ declare global {
 			// Other
 			'choose her own job' |
 			// Pseudo-jobs
-			'@Lurcher' | '@Pit' | '@be imported' | '@lay in tank';
+			'@Lurcher' | '@Pit' | "@Arena" | '@be imported' | '@lay in tank';
 
 		interface AssignmentFreeze extends Record<string, Assignment> {
 			// Penthouse Assignments
@@ -124,6 +124,7 @@ declare global {
 			// Pseudo-jobs
 			LURCHER: '@Lurcher';
 			PIT: '@Pit';
+			ARENA: "@Arena";
 			IMPORTED: '@be imported';
 			TANK: '@lay in tank';
 		}
diff --git a/js/003-data/constants.js b/js/003-data/constants.js
index 9a2f8956ce4..602dc75c140 100644
--- a/js/003-data/constants.js
+++ b/js/003-data/constants.js
@@ -64,6 +64,7 @@ globalThis.Job = Object.freeze({
 	// Pseudo-jobs
 	LURCHER: '@Lurcher',
 	PIT: '@Pit',
+	ARENA: "@Arena",
 	IMPORTED: '@be imported',
 	TANK: '@lay in tank'
 });
diff --git a/src/002-config/fc-version.js b/src/002-config/fc-version.js
index ddb9bc7be3f..d66255ea8f4 100644
--- a/src/002-config/fc-version.js
+++ b/src/002-config/fc-version.js
@@ -2,5 +2,5 @@ App.Version = {
 	base: "0.10.7.1", // The vanilla version the mod is based off of, this should never be changed.
 	pmod: "4.0.0-alpha.21",
 	commitHash: null,
-	release: 1182, // When getting close to 2000, please remove the check located within the onLoad() function defined at line five of src/js/eventHandlers.js.
+	release: 1183, // When getting close to 2000, please remove the check located within the onLoad() function defined at line five of src/js/eventHandlers.js.
 };
diff --git a/src/data/backwardsCompatibility/backwardsCompatibility.js b/src/data/backwardsCompatibility/backwardsCompatibility.js
index e42d747b478..a372db01a7d 100644
--- a/src/data/backwardsCompatibility/backwardsCompatibility.js
+++ b/src/data/backwardsCompatibility/backwardsCompatibility.js
@@ -2448,6 +2448,9 @@ App.Update.oldVersions = function(node) {
 	if (V.releaseID <= 1123) {
 		V.plotEventWeek = App.Events.effectiveWeek();
 	}
+	if (V.releaseID < 1183 && V.pit) {
+		V.pit.trainingIDs = [];
+	}
 	node.append(`Done!`);
 };
 
diff --git a/src/facilities/pit/pitFramework.js b/src/facilities/pit/pitFramework.js
index ed2ca4086d5..d3a3d58c2b2 100644
--- a/src/facilities/pit/pitFramework.js
+++ b/src/facilities/pit/pitFramework.js
@@ -3,6 +3,13 @@ App.Data.Facilities.pit = {
 	baseName: "pit",
 	genericName: null,
 	jobs: {
+		trainee: {
+			position: "trainee",
+			assignment: Job.ARENA, /* pseudo-assignment for assignmentTransition/assignJob/removeJob */
+			publicSexUse: false,
+			fuckdollAccepted: false,
+			partTime: true
+		},
 		fighter: {
 			position: "fighter",
 			assignment: Job.PIT, /* pseudo-assignment for assignmentTransition/assignJob/removeJob */
@@ -49,19 +56,45 @@ App.Entity.Facilities.PitFighterJob = class extends App.Entity.Facilities.Facili
 		if (this.isEmployed(slave)) {
 			return [`${slave.slaveName} is already assigned to fight in ${this.facility.name}.`];
 		}
-		if (!this._facilityHasFreeSpace) {
-			return [`Capacity of ${this.facility.name} exceeded.`];
+		return this.checkRequirements(slave);
+	}
+};
+
+/**
+ * The requirements are harsher than for the fighter job, because you can't learn combat of you can't fight in the first
+ * place. That's why we inherit PitFighterJob to keep the base requirements in sync.
+ */
+App.Entity.Facilities.ArenaTraineeJob = class extends App.Entity.Facilities.PitFighterJob {
+	/**
+	 * @param {App.Entity.SlaveState} slave
+	 * @returns {string[]}
+	 */
+	checkRequirements(slave) {
+		let r = super.checkRequirements(slave);
+		if (!App.Entity.Facilities.Job._isBrokenEnough(slave, -20, -50, -20, -51)) {
+			r.push(`${slave.slaveName} is too resistant to be trusted with weapons.`);
 		}
+		return r;
+	}
 
+	/**
+	 * @param {App.Entity.SlaveState} slave
+	 * @returns {string[]}
+	 */
+	canEmploy(slave) {
+		if (this.isEmployed(slave)) {
+			return [`${slave.slaveName} is already assigned to train in ${this.facility.name}.`];
+		}
 		return this.checkRequirements(slave);
 	}
 };
 
-App.Entity.Facilities.Pit = class extends App.Entity.Facilities.SingleJobFacility {
+App.Entity.Facilities.Pit = class extends App.Entity.Facilities.Facility {
 	constructor() {
 		super(App.Data.Facilities.pit,
 			{
-				fighter: new App.Entity.Facilities.PitFighterJob()
+				trainee: new App.Entity.Facilities.ArenaTraineeJob(),
+				fighter: new App.Entity.Facilities.PitFighterJob(),
 			});
 	}
 
@@ -71,7 +104,10 @@ App.Entity.Facilities.Pit = class extends App.Entity.Facilities.SingleJobFacilit
 
 	/** @override */
 	occupancyReport(long) {
-		return `${this.hostedSlaves}${long ? ` ${this.job().desc.position}s` : ""}`;
+		if (long) {
+			return `${this.hostedSlaves("trainee")} ${this.job("trainee").desc.position}s and ${this.hostedSlaves("fighter")} ${this.job("fighter").desc.position}s`;
+		}
+		return `T:${this.hostedSlaves("trainee")}+F:${this.hostedSlaves("fighter")}`;
 	}
 };
 
diff --git a/src/facilities/pit/pitUtils.js b/src/facilities/pit/pitUtils.js
index 987315f9d05..fc3fe4d9d47 100644
--- a/src/facilities/pit/pitUtils.js
+++ b/src/facilities/pit/pitUtils.js
@@ -1,8 +1,11 @@
 App.Facilities.Pit.init = function() {
 	/** @type {FC.Facilities.Pit} */
 	V.pit = {
-		name: "the Pit",
+		name: "the Arena",
+		// Arena section
+		trainingIDs: [],
 
+		// Pit section
 		animal: null,
 		audience: "free",
 		bodyguardFights: false,
diff --git a/src/facilities/servantsQuarters/servantsQuarters.js b/src/facilities/servantsQuarters/servantsQuarters.js
index 7e0ffc0837f..781446d9385 100644
--- a/src/facilities/servantsQuarters/servantsQuarters.js
+++ b/src/facilities/servantsQuarters/servantsQuarters.js
@@ -23,9 +23,9 @@ App.Facilities.ServantsQuarters.servantsQuarters = class ServantsQuarters extend
 
 		text.push(this.facility.nameCaps, this.decorations);
 
-		if (this.facility.hostedSlaves > 2) {
+		if (this.facility.hostedSlaves() > 2) {
 			text.push(`${this.facility.nameCaps} are busy with hurrying slaves. One shift of servants is eating, cleaning the quarters, and bathing. The second is sleeping, and the third is out in the penthouse cleaning and serving.`);
-		} else if (this.facility.hostedSlaves > 0) {
+		} else if (this.facility.hostedSlaves() > 0) {
 			text.push(`A few slaves are working out of the servants' quarters. They must split their scant time between looking after their own needs and the superior needs of everyone else.`);
 		} else if (S.Stewardess) {
 			text.push(`${S.Stewardess.slaveName} is alone, and seems rather bereft without anyone to order around.`);
@@ -84,7 +84,7 @@ App.Facilities.ServantsQuarters.servantsQuarters = class ServantsQuarters extend
 	/** @returns {FC.Facilities.Expand} */
 	get expand() {
 		return {
-			desc: `${this.facility.nameCaps} has room to keep ${numberWithPluralOne(V.servantsQuarters, "slave")} while they serve. There ${this.facility.hostedSlaves === 1 ? `is currently ${num(this.facility.hostedSlaves)} slave` : `are currently ${num(this.facility.hostedSlaves)} slaves`} serving in ${V.servantsQuartersName}.`,
+			desc: `${this.facility.nameCaps} has room to keep ${numberWithPluralOne(V.servantsQuarters, "slave")} while they serve. There ${this.facility.hostedSlaves() === 1 ? `is currently ${num(this.facility.hostedSlaves())} slave` : `are currently ${num(this.facility.hostedSlaves())} slaves`} serving in ${V.servantsQuartersName}.`,
 			removeSlave: "be a servant",
 		};
 	}
diff --git a/src/js/DefaultRules.js b/src/js/DefaultRules.js
index 54635bdcf8b..af2d28ffdd0 100644
--- a/src/js/DefaultRules.js
+++ b/src/js/DefaultRules.js
@@ -1580,7 +1580,7 @@ globalThis.DefaultRules = function(slave) {
 						message(`${slave.slaveName} has been removed from the pit.`, sourceRecord.pitRules);
 					}
 				} else {
-					if (App.Entity.facilities.pit.job().checkRequirements(slave).length !== 0) {
+					if (App.Entity.facilities.pit.job("fighter").checkRequirements(slave).length !== 0) {
 						removeJob(slave, Job.PIT, true);
 						message(`${slave.slaveName} is not eligible to fight.`, sourceRecord.pitRules);
 					} else if (!App.Entity.facilities.pit.isHosted(slave)) {
diff --git a/src/js/assignJS.js b/src/js/assignJS.js
index a07a247f5c5..4f1409dcbd4 100644
--- a/src/js/assignJS.js
+++ b/src/js/assignJS.js
@@ -9,7 +9,13 @@ globalThis.assignJob = function(slave, job) {
 	const oldJob = slave.assignment;
 
 	// handle non-exclusive pseudo-assignments as special cases
-	if (job === Job.PIT) {
+	if (job === Job.ARENA) {
+		if (!V.pit.trainingIDs.includes(slave.ID)) {
+			V.pit.trainingIDs.push(slave.ID);
+		}
+		V.JobIDMap[Job.ARENA].add(slave.ID);
+		return r;
+	} else if (job === Job.PIT) {
 		if (!V.pit.fighterIDs.includes(slave.ID)) {
 			V.pit.fighterIDs.push(slave.ID);
 		}
@@ -471,7 +477,14 @@ globalThis.removeJob = function(slave, assignment, saveRecord = false) {
 		delete V.assignmentRecords[slave.ID];
 	}
 
-	if (assignment === Job.PIT) {
+	if (assignment === Job.ARENA) {
+		if (V.pit) {
+			V.pit.trainingIDs.delete(slave.ID);
+			V.JobIDMap[Job.ARENA].delete(slave.ID);
+		} else {
+			return;	// TODO: should this return or just continue?
+		}
+	} else if (assignment === Job.PIT) {
 		if (V.pit) {
 			V.pit.fighterIDs.delete(slave.ID);
 			V.JobIDMap[Job.PIT].delete(slave.ID);
@@ -730,6 +743,7 @@ globalThis.makeJobIdMap = function() {
 
 	// special cases
 	if (V.pit) {
+		res[Job.ARENA] = new Set(V.pit.trainingIDs);
 		res[Job.PIT] = new Set(V.pit.fighterIDs);
 	}
 	res[Job.LURCHER].add(V.LurcherID);
diff --git a/src/npc/surgery/surgery.js b/src/npc/surgery/surgery.js
index c5a8364d22a..eb04fa79269 100644
--- a/src/npc/surgery/surgery.js
+++ b/src/npc/surgery/surgery.js
@@ -130,6 +130,7 @@ App.Medicine.Surgery.apply = function(procedure, cheat) {
 
 	if (reaction.removeJob) {
 		removeJob(slave, Job.LURCHER, true);
+		removeJob(slave, Job.ARENA, true);
 		removeJob(slave, Job.PIT, true);
 		removeJob(slave, slave.assignment);
 	}
diff --git a/src/pregmod/eliteBreedingExam.js b/src/pregmod/eliteBreedingExam.js
index b58842e53a1..5e4ff6f2e45 100644
--- a/src/pregmod/eliteBreedingExam.js
+++ b/src/pregmod/eliteBreedingExam.js
@@ -278,9 +278,15 @@ App.Interact.eliteBreedingExam = function(slave = null) {
 				removeJob(slave, slave.assignment);
 				consequences.push(`reassigned to <span class="green">rest</span>`);
 			}
-			if (V.pit && V.pit.fighterIDs.includes(slave.ID)) {
-				removeJob(slave, Job.PIT);
-				consequences.push(`<span class="yellow">removed</span> from ${V.pit.name}'s fighting roster`);
+			if (V.pit) {
+				if (V.pit.trainingIDs.includes(slave.ID)) {
+					removeJob(slave, Job.ARENA);
+					consequences.push(`<span class="yellow">removed</span> from ${V.pit.name}'s training roster`);
+				}
+				if (V.pit.fighterIDs.includes(slave.ID)) {
+					removeJob(slave, Job.PIT);
+					consequences.push(`<span class="yellow">removed</span> from ${V.pit.name}'s fighting roster`);
+				}
 			}
 			if (consequences.length > 0) {
 				App.Events.addNode(frag, r, "div");
-- 
GitLab