From c444ba5256d02fd1cf2a5d19ea152b51dd65fc35 Mon Sep 17 00:00:00 2001
From: Svornost <11434-svornost@users.noreply.gitgud.io>
Date: Sun, 1 Aug 2021 13:26:15 -0700
Subject: [PATCH] Art rendering improvements: 1. Use batch rendering for death,
 expire, retire, burst, and birth. 2. Add `App.UI.DOM.drawOneSlaveRight` to
 draw a single slave floating to the right of a block of text, since we do
 that exact thing all over the place. 3. Fix some broken stuff in matchmaking
 and provide a slave description dialog link in case you've forgotten the
 slave's fetishes and so on. 4. Fix some inconsistencies in the layout of the
 HG's slave's penthouse report entry. 5. A few little type fixes

---
 js/003-data/gameVariableData.js               |  1 +
 src/art/artJS.js                              |  2 +-
 src/endWeek/events/death.js                   |  9 ++++++--
 src/endWeek/events/expire.js                  |  9 ++++++--
 src/endWeek/events/retire.js                  | 11 +++++++---
 src/endWeek/reports/penthouseReport.js        | 21 +++++++++----------
 src/events/scheduled/burst/burst.js           | 13 +++++++-----
 .../dairy/freeRangeDairyAssignmentScene.js    | 16 ++++----------
 src/js/birth/birth.js                         | 14 +++++++++----
 src/js/utilsDOM.js                            | 16 ++++++++++++++
 src/js/utilsSC.js                             |  4 ++--
 src/npc/interaction/passage/matchmaking.js    |  9 +++-----
 12 files changed, 77 insertions(+), 48 deletions(-)

diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js
index ebdae0d3450..c53a37bbaea 100644
--- a/js/003-data/gameVariableData.js
+++ b/js/003-data/gameVariableData.js
@@ -808,6 +808,7 @@ App.Data.resetOnNGPlus = {
 	milkPipeline: 0,
 	cumPipeline: 0,
 	wcPiping: 0,
+	/** @type {Map<number, "oldAge"|"overdosed"|"lowHealth">} */
 	slaveDeath: new Map(),
 	playerBred: 0,
 	playerBredTube: 0,
diff --git a/src/art/artJS.js b/src/art/artJS.js
index 1ae87f071f5..6c759a84739 100644
--- a/src/art/artJS.js
+++ b/src/art/artJS.js
@@ -13,7 +13,7 @@ Macro.add("SlaveArt", {
 App.Art.SlaveArtBatch = class {
 	/** Prepare to render art in the same format for multiple slaves within a single passage context.
 	 * Do not persist this object across passage contexts.
-	 * @param {number[]} artSlaveIDs
+	 * @param {Iterable<number>} artSlaveIDs
 	 * @param {number} artSize Image size/center:
 	 * * 3: Large, right. Example: long slave description.
 	 * * 2: Medium, right. Example: random events.
diff --git a/src/endWeek/events/death.js b/src/endWeek/events/death.js
index 6b3b124ebca..af9ed44407a 100644
--- a/src/endWeek/events/death.js
+++ b/src/endWeek/events/death.js
@@ -18,6 +18,11 @@ App.Events.SEDeath = class SEDeath extends App.Events.BaseEvent {
 	}
 
 	execute(node) {
+		const artRenderer = V.seeImages && V.seeReportImages ? new App.Art.SlaveArtBatch(V.slaveDeath.keys(), 0, 0) : null;
+		if (artRenderer) {
+			node.append(artRenderer.writePreamble());
+		}
+
 		for (const [id, deathType] of V.slaveDeath) {
 			const deceased = getSlave(id);
 			if (deceased) {
@@ -47,8 +52,8 @@ App.Events.SEDeath = class SEDeath extends App.Events.BaseEvent {
 				He, His,
 				he, his
 			} = getPronouns(slave);
-			if (V.seeImages) {
-				App.UI.DOM.appendNewElement("div", el, App.Art.SlaveArtElement(slave, 0, 0), ["imageRef", "tinyImg"]);
+			if (artRenderer) {
+				App.UI.DOM.appendNewElement("div", el, artRenderer.render(slave), ["imageRef", "tinyImg"]);
 			}
 
 			switch (deathType) {
diff --git a/src/endWeek/events/expire.js b/src/endWeek/events/expire.js
index ef2c58d67e0..85cb09d24eb 100644
--- a/src/endWeek/events/expire.js
+++ b/src/endWeek/events/expire.js
@@ -12,6 +12,11 @@ App.Events.SEExpiration = class SEExpiration extends App.Events.BaseEvent {
 	execute(node) {
 		V.encyclopedia = "Indentured Servants";
 		const _this = this;
+		const artRenderer = V.seeImages && V.seeReportImages ? new App.Art.SlaveArtBatch(_this.actors, 0, 0) : null;
+		if (artRenderer) {
+			node.append(artRenderer.writePreamble());
+		}
+
 		for (const id of _this.actors) {
 			const slave = getSlave(id);
 			if (slave) {
@@ -45,8 +50,8 @@ App.Events.SEExpiration = class SEExpiration extends App.Events.BaseEvent {
 				he, his, him, himself, woman
 			} = getPronouns(slave);
 			const {title: Master} = getEnunciation(slave);
-			if (V.seeImages) {
-				App.UI.DOM.appendNewElement("div", el, App.Art.SlaveArtElement(slave, 0, 0), ["imageRef", "tinyImg"]);
+			if (artRenderer) {
+				App.UI.DOM.appendNewElement("div", el, artRenderer.render(slave), ["imageRef", "tinyImg"]);
 			}
 
 			r.push(`${slave.slaveName}'s indentured servitude is ending this week, meaning that your arcology is gaining a citizen.`);
diff --git a/src/endWeek/events/retire.js b/src/endWeek/events/retire.js
index ccaaf57d8f2..8448bf6549f 100644
--- a/src/endWeek/events/retire.js
+++ b/src/endWeek/events/retire.js
@@ -10,6 +10,11 @@ App.Events.SERetire = class SERetire extends App.Events.BaseEvent {
 	}
 
 	execute(node) {
+		const artRenderer = V.seeImages && V.seeReportImages ? new App.Art.SlaveArtBatch(this.actors, 2, 0) : null;
+		if (artRenderer) {
+			node.append(artRenderer.writePreamble());
+		}
+
 		for (const id of this.actors) {
 			const slave = getSlave(id);
 			if (slave) {
@@ -27,10 +32,10 @@ App.Events.SERetire = class SERetire extends App.Events.BaseEvent {
 };
 
 /**
- *
  * @param {App.Entity.SlaveState} originalSlave
+ * @param {App.Art.SlaveArtBatch} [artRenderer]
  */
-globalThis.retireScene = function(originalSlave) {
+globalThis.retireScene = function(originalSlave, artRenderer) {
 	const el = new DocumentFragment();
 	const slave = clone(originalSlave);
 	removeSlave(originalSlave);
@@ -49,7 +54,7 @@ globalThis.retireScene = function(originalSlave) {
 	}
 
 	const {heU, hisU, himU, girlU} = getNonlocalPronouns(V.seeDicks).appendSuffix('U');
-	const art = (V.seeImages) ? App.UI.DOM.appendNewElement("div", el, App.Art.SlaveArtElement(slave, 0, 0), ["imageRef", "tinyImg"]) : document.createElement("div");
+	const art = (V.seeImages) ? App.UI.DOM.drawOneSlaveRight(el, slave, artRenderer) : document.createElement("div");
 
 	const desc = App.UI.DOM.appendNewElement("div", el);
 	const result = App.UI.DOM.appendNewElement("div", el);
diff --git a/src/endWeek/reports/penthouseReport.js b/src/endWeek/reports/penthouseReport.js
index 2248460d249..1fe638b39fd 100644
--- a/src/endWeek/reports/penthouseReport.js
+++ b/src/endWeek/reports/penthouseReport.js
@@ -20,7 +20,7 @@ App.EndWeek.penthouseReport = function() {
 	for (const slave of App.SlaveAssignment.reportSlaves(penthouseSlaves)) {
 		const slaveEntry = App.UI.DOM.appendNewElement("div", el, '', "slave-report");
 		if (penthouseArtRenderer) {
-			App.UI.DOM.appendNewElement("div", slaveEntry, penthouseArtRenderer.render(slave), ["imageRef", "medImg"]);
+			App.UI.DOM.drawOneSlaveRight(slaveEntry, slave, penthouseArtRenderer);
 		}
 		App.SlaveAssignment.appendSlaveLinks(slaveEntry, slave);
 		slaveEntry.append(fullReport(slave));
@@ -28,22 +28,21 @@ App.EndWeek.penthouseReport = function() {
 		if (slave.ID === V.HeadGirlID && hgSlave) {
 			/* Output the HG's slave immediately after the hg */
 			const {He2, he2} = getPronouns(hgSlave).appendSuffix("2");
-			const r = [];
+			const hgSlaveEntry = App.UI.DOM.appendNewElement("div", el, '', "slave-report");
 			if (hgSlave.assignment !== Job.HEADGIRLSUITE) {
-				r.push(`<span class="red">${hgSlave.slaveName} had been assigned to live with your Head Girl, but this week ${he2} was assigned to ${hgSlave.assignment}. ${He2} has been released to your penthouse for reassignment.</span>`);
+				App.UI.DOM.appendNewElement("span", hgSlaveEntry, `${hgSlave.slaveName} had been assigned to live with your Head Girl, but this week ${he2} was assigned to ${hgSlave.assignment}. ${He2} has been released to your penthouse for reassignment.`, "warning");
 				removeJob(hgSlave, Job.HEADGIRLSUITE);
 			} else {
-				r.push(App.UI.DOM.makeElement("span", SlaveFullName(hgSlave), "slave-name"));
-				if (hgSlave.choosesOwnAssignment === 2) {
-					r.push(App.SlaveAssignment.choosesOwnJob(hgSlave));
-					r.push(He2);
-				}
 				if (penthouseArtRenderer) {
-					r.push(App.UI.DOM.makeElement("div", penthouseArtRenderer.render(hgSlave), ["imageRef", "medImg"]));
+					App.UI.DOM.drawOneSlaveRight(hgSlaveEntry, hgSlave, penthouseArtRenderer);
+				}
+				App.SlaveAssignment.appendSlaveLinks(hgSlaveEntry, hgSlave);
+				App.UI.DOM.appendNewElement("span", hgSlaveEntry, SlaveFullName(hgSlave), "slave-name");
+				if (hgSlave.choosesOwnAssignment === 2) {
+					hgSlaveEntry.append(App.SlaveAssignment.choosesOwnJob(hgSlave), ` ${He2}`);
 				}
-				r.push(App.SlaveAssignment.liveWithHG(hgSlave));
+				hgSlaveEntry.append(` `, App.SlaveAssignment.liveWithHG(hgSlave));
 			}
-			App.Events.addNode(el, r, "div", "slave-report");
 		}
 	}
 
diff --git a/src/events/scheduled/burst/burst.js b/src/events/scheduled/burst/burst.js
index d5d14df4f79..5bd6efbc5d2 100644
--- a/src/events/scheduled/burst/burst.js
+++ b/src/events/scheduled/burst/burst.js
@@ -10,9 +10,14 @@ App.Events.SEBurst = class SEBurst extends App.Events.BaseEvent {
 	}
 
 	execute(node) {
+		const artRenderer = V.seeImages && V.seeReportImages ? new App.Art.SlaveArtBatch(this.actors, 2, 0) : null;
+		if (artRenderer) {
+			node.append(artRenderer.writePreamble());
+		}
+
 		for (const slave of this.actors.map(id => getSlave(id))) {
 			if (slave.womb.length > 0) {
-				node.append(birth(slave));
+				node.append(birth(slave, {artRenderer}));
 			} else {
 				node.append(pop(slave));
 			}
@@ -32,10 +37,8 @@ App.Events.SEBurst = class SEBurst extends App.Events.BaseEvent {
 				He, His,
 				he, his, him
 			} = getPronouns(slave);
-			if (V.seeImages && V.seeReportImages) {
-				r.push(
-					App.UI.DOM.makeElement("div", App.Art.SlaveArtElement(slave, 2, 0), ["imageRef", "medImg"])
-				);
+			if (artRenderer) {
+				App.UI.DOM.drawOneSlaveRight(el, slave, artRenderer);
 			}
 			r.push(`As ${slave.slaveName} is going about ${his} business with ${his} overfilled`);
 			if (slave.inflation !== 0) {
diff --git a/src/facilities/dairy/freeRangeDairyAssignmentScene.js b/src/facilities/dairy/freeRangeDairyAssignmentScene.js
index c22b6a839e6..21e57308ec4 100644
--- a/src/facilities/dairy/freeRangeDairyAssignmentScene.js
+++ b/src/facilities/dairy/freeRangeDairyAssignmentScene.js
@@ -10,9 +10,7 @@ App.Facilities.Dairy.freeRangeAssignmentScene = function(slave) {
 		he, him, his, himself
 	} = getPronouns(slave);
 
-	if (V.seeImages) {
-		App.UI.DOM.appendNewElement("div", node, App.Art.SlaveArtElement(slave, 2, 0), ["imageRef", "medImg"]);
-	}
+	App.UI.DOM.drawOneSlaveRight(node, slave);
 
 	r.push(`${slave.slaveName} reports to the dairy.`);
 	if (slave.energy > 90 ) {
@@ -65,9 +63,7 @@ App.Facilities.Dairy.freeRangeAssignmentScene = function(slave) {
 				he2, his2, him2,
 			} = getPronouns(cow).appendSuffix("2");
 			aroused = true;
-			if (V.seeImages) {
-				App.UI.DOM.appendNewElement("div", node, App.Art.SlaveArtElement(cow, 2, 0), ["imageRef", "medImg"]);
-			}
+			App.UI.DOM.drawOneSlaveRight(node, cow);
 			r.push(`The hyper-endowed cum-cow ${cow.slaveName} is the pride of ${V.dairyName}. ${He2} is limply hanging on ${his2} milking chair, panting heavily because of the constant suction on ${his2} dick. ${He2} is obviously nearing climax. Soon,`);
 			if (hasAnyNaturalEyes(cow)) {
 				r.push(his2);
@@ -162,17 +158,13 @@ App.Facilities.Dairy.freeRangeAssignmentScene = function(slave) {
 		const {
 			He2
 		} = getPronouns(assayedSlave).appendSuffix("2");
-		if (V.seeImages === 1) {
-			App.UI.DOM.appendNewElement("div", node, App.Art.SlaveArtElement(assayedSlave, 2, 0), ["imageRef", "medImg"]);
-		}
+		App.UI.DOM.drawOneSlaveRight(node, assayedSlave);
 		r.push(`${His} ${assayType} ${assayedSlave.slaveName} is at the dairy, too. ${He2} is in the adjacent stall. The two of them are going to be milked right next to each other.`);
 	}
 	App.Events.addParagraph(node, r);
 	r = [];
 
-	if (S.Milkmaid && V.seeImages === 1) {
-		App.UI.DOM.appendNewElement("div", node, App.Art.SlaveArtElement(S.Milkmaid, 2, 0), ["imageRef", "medImg"]);
-	}
+	App.UI.DOM.drawOneSlaveRight(node, S.Milkmaid);
 	r.push(`The only "furniture" in the stall looks like a dentist's chair. Despite the medical appearance, when ${he}`);
 	if (slave.devotion > 90) {
 		r.push(`eagerly`);
diff --git a/src/js/birth/birth.js b/src/js/birth/birth.js
index dbd4c00a620..fab3eb3645b 100644
--- a/src/js/birth/birth.js
+++ b/src/js/birth/birth.js
@@ -10,8 +10,13 @@ App.Events.SEBirth = class SEBirth extends App.Events.BaseEvent {
 	}
 
 	execute(node) {
+		const artRenderer = V.seeImages && V.seeReportImages ? new App.Art.SlaveArtBatch(this.actors, 2, 0) : null;
+		if (artRenderer) {
+			node.append(artRenderer.writePreamble());
+		}
+
 		for (const slave of this.actors.map(id => getSlave(id))) {
-			node.append(birth(slave));
+			node.append(birth(slave, {artRenderer}));
 			node.append(sectionBreak());
 		}
 		V.birthIDs = [];
@@ -32,8 +37,9 @@ App.Events.SEBirth = class SEBirth extends App.Events.BaseEvent {
  * @param {object} [obj]
  * @param {boolean} [obj.birthStorm]
  * @param {boolean} [obj.cSection]
+ * @param {App.Art.SlaveArtBatch} [obj.artRenderer]
  */
-globalThis.birth = function(slave, {birthStorm = false, cSection = false} = {}) {
+globalThis.birth = function(slave, {birthStorm = false, cSection = false, artRenderer = null} = {}) {
 	const el = document.createElement("p");
 	el.style.overflow = "hidden"; // Keep image from floating into the next slave.
 	let p;
@@ -59,8 +65,8 @@ globalThis.birth = function(slave, {birthStorm = false, cSection = false} = {})
 		he, his, him, himself, wife, girl
 	} = getPronouns(slave);
 	const hands = (hasBothArms(slave)) ? "hands" : "hand";
-	if (V.seeImages && V.seeReportImages) {
-		App.UI.DOM.appendNewElement("div", el, App.Art.SlaveArtElement(slave, 2), ["imageRef", "medImg"]);
+	if (artRenderer) {
+		App.UI.DOM.drawOneSlaveRight(el, slave, artRenderer);
 	}
 	el.append(titleText());
 	suddenBirthCheck();
diff --git a/src/js/utilsDOM.js b/src/js/utilsDOM.js
index c75292dc954..5d0bf432382 100644
--- a/src/js/utilsDOM.js
+++ b/src/js/utilsDOM.js
@@ -544,3 +544,19 @@ App.UI.DOM.makeCheckbox = function(arg) {
 	};
 	return checkbox;
 };
+
+/**
+ * Draw a single medium-sized slave image, floating to the right of a block of text.
+ * If you're rendering simple scene-wide art where the relationship between text and image location isn't important, @see App.Events.drawEventArt instead.
+ * @param {ParentNode} node
+ * @param {App.Entity.SlaveState} slave
+ * @param {App.Art.SlaveArtBatch} [batchRenderer] an initialized batch renderer with the preamble already output; if omitted, the entire art block will be output inline
+ * @returns {HTMLDivElement|DocumentFragment}
+ */
+App.UI.DOM.drawOneSlaveRight = function(node, slave, batchRenderer) {
+	if (!V.seeImages || !slave) {
+		return new DocumentFragment();
+	}
+	const artElement = batchRenderer ? batchRenderer.render(slave) : App.Art.SlaveArtElement(slave, 2, 0);
+	return App.UI.DOM.appendNewElement("div", node, artElement, ["imageRef", "medImg"]);
+};
diff --git a/src/js/utilsSC.js b/src/js/utilsSC.js
index 260b3d96a22..a2de774ae8c 100644
--- a/src/js/utilsSC.js
+++ b/src/js/utilsSC.js
@@ -227,8 +227,8 @@ App.UI.tabBar = function() {
 /** handler function for slaveDescriptionDialog. do not call directly. */
 App.UI._showDescriptionDialog = function(slave, options) {
 	Dialog.setup(SlaveFullName(slave));
-	const image = V.seeImages ? App.UI.DOM.makeElement("div", App.Art.SlaveArtElement(slave, 2, 0), ["imageRef", "medImg"]) : '';
-	Dialog.append(image).append(App.Desc.longSlave(slave, options));
+	App.UI.DOM.drawOneSlaveRight(Dialog.body(), slave);
+	Dialog.append(App.Desc.longSlave(slave, options));
 	Dialog.open();
 };
 
diff --git a/src/npc/interaction/passage/matchmaking.js b/src/npc/interaction/passage/matchmaking.js
index c5987fe2371..d06ae2cd4c3 100644
--- a/src/npc/interaction/passage/matchmaking.js
+++ b/src/npc/interaction/passage/matchmaking.js
@@ -14,13 +14,9 @@ App.Interact.matchmaking = function(slave) {
 
 	const desc = SlaveTitle(slave);
 
-	if (V.seeImages) {
-		node.append(
-			App.UI.DOM.makeElement("div", App.Art.SlaveArtElement(slave, 2, 0), ["imageRef", "medImg"])
-		);
-	}
+	App.UI.DOM.drawOneSlaveRight(node, slave);
 
-	r.push(`You order ${slave.slaveName} to come to your office. The`);
+	r.push(`You order`, App.UI.DOM.slaveDescriptionDialog(slave, slave.slaveName, {noArt: true}), `to come to your office. The`);
 	if (slave.relationship === -2) {
 		r.push(`worshipful`);
 	} else {
@@ -222,6 +218,7 @@ App.Interact.matchmaking = function(slave) {
 			slave.trust -= 10;
 			subSlave.trust -= 10;
 		}
+		App.Events.addParagraph(frag, r);
 
 		return frag;
 	}
-- 
GitLab