From c0e782007491293a851c2ee8498cdb55ee90c249 Mon Sep 17 00:00:00 2001
From: Svornost <11434-svornost@users.noreply.gitgud.io>
Date: Sun, 22 Mar 2020 17:14:03 -0700
Subject: [PATCH] Build some event utilities to avoid getting tons of
 boilerplate in the events, and convert RESS lazy evening to JS as a real
 single-slave event example.

---
 src/events/eventUtils.js      | 143 ++++++++++++++++++++
 src/events/randomEvent.js     |   7 +-
 src/events/ressLazyEvening.js | 241 ++++++++++++++++++++++++++++++++++
 src/js/eventSelectionJS.js    |   6 -
 src/js/utilsSC.js             |  35 +++--
 src/uncategorized/RESS.tw     | 146 --------------------
 6 files changed, 412 insertions(+), 166 deletions(-)
 create mode 100644 src/events/eventUtils.js
 create mode 100644 src/events/ressLazyEvening.js

diff --git a/src/events/eventUtils.js b/src/events/eventUtils.js
new file mode 100644
index 00000000000..a028ee204bf
--- /dev/null
+++ b/src/events/eventUtils.js
@@ -0,0 +1,143 @@
+/** draw event art, with the option to dress the slave in a particular way
+ * @param {HTMLElement} node - DOM node to attach art to
+ * @param {App.Entity.SlaveState|Array<App.Entity.SlaveState>} slaves - one or several slaves to draw art for
+ * @param {[string]} clothesMode - if the slaves' clothing should be overridden, what should they be wearing?
+ */
+App.Events.drawEventArt = function(node, slaves, clothesMode) {
+	// do nothing if the player doesn't want images
+	if (!V.seeImages) {
+		return;
+	}
+
+	// ensure that slaves is an array
+	if (!Array.isArray(slaves)) {
+		slaves = [slaves];
+	}
+
+	// if we were asked to change the slave's clothing, do it now
+	let originalClothes = [];
+	if (clothesMode) {
+		// if there are "themes" of clothes that multiple events want to use ("swimwear", "athletic", "casual", etc), they can be added as special cases here instead of duplicating the logic in every event
+		if (App.Data.misc.niceClothes.map(c => c.value).concat(App.Data.misc.harshClothes.map(c => c.value)).includes(clothesMode)) {
+			// specific clothes have been selected
+			originalClothes = slaves.map((s) => { return {ID: s.ID, clothes: s.clothes}; });
+			slaves.forEach(s => s.clothes = clothesMode);
+		} else {
+			throw "Unrecognized clothes mode for event art";
+		}
+	}
+
+	// actually draw the art - large if single slave, medium column if multiple slaves
+	let artSpan = document.createElement("span");
+	artSpan.id = "artFrame";
+	if (slaves.length === 1) {
+		let refDiv = document.createElement("div");
+		refDiv.classList.add("imageRef", V.imageChoice === 1 ? "lrgVector" : "lrgRender");
+		let maskDiv = document.createElement("div");
+		maskDiv.classList.add("mask");
+		maskDiv.appendChild(document.createTextNode("\u00a0"));
+		refDiv.appendChild(maskDiv);
+		refDiv.appendChild(App.Art.SlaveArtElement(slaves[0], 2, 0));
+		artSpan.appendChild(refDiv);
+	} else {
+		let colDiv = document.createElement("div");
+		colDiv.classList.add("imageColumn");
+		for (const slave of slaves) {
+			let refDiv = document.createElement("div");
+			refDiv.classList.add("imageRef", "medImg");
+			refDiv.appendChild(App.Art.SlaveArtElement(slave, 2, 0));
+			colDiv.appendChild(refDiv);
+		}
+		artSpan.appendChild(colDiv);
+	}
+	node.appendChild(artSpan);
+
+	// change clothing back, if necessary
+	if (originalClothes.length > 0) {
+		originalClothes.forEach((c) => slaves.find(s => s.ID === c.ID).clothes = c.clothes);
+	}
+};
+
+/** intelligently adds spaces to an array of mixed strings and DOM nodes, merging consecutive strings in the process
+ * @param {Array<string|HTMLElement>} sentences
+ * @returns {Array<string|HTMLElement>}
+ */
+App.Events.spaceSentences = function(sentences) {
+	if (sentences.length <= 1) {
+		return sentences;
+	}
+	return sentences.reduce((res, cur) => {
+		if (res.length === 0) {
+			res.push(cur);
+		} else if (typeof (res[res.length-1]) === "string") {
+			if (typeof (cur) === "string") {
+				res[res.length-1] += " " + cur;
+			} else {
+				res[res.length-1] += " ";
+				res.push(cur);
+			}
+		} else {
+			if (typeof (cur) === "string") {
+				res.push(" " + cur);
+			} else {
+				res.push(" ");
+				res.push(cur);
+			}
+		}
+		return res;
+	}, []);
+};
+
+/** assemble a DOM paragraph from an array of DOM nodes, sentences or sentence fragments (which may contain HTML)
+ * @param {HTMLElement} node
+ * @param {Array<string|HTMLElement>} sentences
+ */
+App.Events.addParagraph = function(node, sentences) {
+	let para = document.createElement("p");
+	$(para).append(App.Events.spaceSentences(sentences));
+	node.appendChild(para);
+};
+
+/** a response to an event, and its result */
+App.Events.Result = class {
+	/** @param {[string]} text - the link text for the response
+	 *  @param {[Function]} handler - the function to call to generate the result when the link is clicked
+	 *  @param {[string]} note - a note to provide alongside the link (for example, a cost or virginity loss warning)
+	 *  To mark an option as disabled, construct the result with only the note.
+	 */
+	constructor(text, handler, note) {
+		this.text = text;
+		this.handler = handler;
+		this.note = note;
+	}
+
+	handle() {
+		let frag = document.createDocumentFragment();
+		$(frag).append(App.Events.spaceSentences(this.handler()));
+		App.UI.DOM.replace("#result", frag);
+	}
+
+	/** build the response DOM (for use by addResponses) */
+	makeResponse(node) {
+		if (this.text && this.handler) {
+			node.appendChild(App.UI.DOM.link(this.text, () => this.handle()));
+		}
+		if (this.note) {
+			node.appendChild(App.UI.DOM.makeElement("span", this.note, "detail"));
+		}
+	}
+};
+
+/** add a list of results for an event
+ * @param {HTMLElement} node
+ * @param {Array<App.Events.Result>} results
+ */
+App.Events.addResponses = function(node, results) {
+	let resultSpan = document.createElement("span");
+	resultSpan.id = "result";
+	for (const result of results) {
+		result.makeResponse(resultSpan);
+		resultSpan.appendChild(document.createElement("br"));
+	}
+	node.appendChild(resultSpan);
+};
diff --git a/src/events/randomEvent.js b/src/events/randomEvent.js
index 9dc56283ddc..c463f31dc47 100644
--- a/src/events/randomEvent.js
+++ b/src/events/randomEvent.js
@@ -86,7 +86,7 @@ App.Events.BaseEvent = class BaseEvent {
 	}
 };
 
-/* This is a trivial event for use as an example. */
+/** This is a trivial event for use as an example. */
 App.Events.TestEvent = class TestEvent extends App.Events.BaseEvent {
 	constructor(actors, params) {
 		super(actors, params);
@@ -104,6 +104,7 @@ App.Events.TestEvent = class TestEvent extends App.Events.BaseEvent {
 
 	execute(node) {
 		let [eventSlave] = this.actors.map(a => getSlave(a)); // mapped deconstruction of actors into local slave variables
+		App.Events.drawEventArt(node, eventSlave);
 		node.appendChild(document.createTextNode(`This test event for ${eventSlave.slaveName} was successful.`));
 	}
 };
@@ -120,7 +121,8 @@ App.Events.TestEvent = class TestEvent extends App.Events.BaseEvent {
 App.Events.getIndividualEvents = function(slave) {
 	return [
 		// instantiate all possible random individual events here
-		// new App.Events.TestEvent(),
+		// example: new App.Events.TestEvent(),
+		new App.Events.RESSLazyEvening(),
 	]
 	.filter(e => (e.eventPrerequisites().every(p => p()) && e.castActors(slave)))
 	.reduce((res, cur) => res.concat(Array(cur.weight).fill(cur)), []);
@@ -132,6 +134,7 @@ App.Events.getIndividualEvents = function(slave) {
 App.Events.getNonindividualEvents = function() {
 	return [
 		// instantiate all possible random nonindividual events here
+		// example: new App.Events.TestEvent(),
 	]
 	.filter(e => (e.eventPrerequisites().every(p => p()) && e.castActors(null)))
 	.reduce((res, cur) => res.concat(Array(cur.weight).fill(cur)), []);
diff --git a/src/events/ressLazyEvening.js b/src/events/ressLazyEvening.js
new file mode 100644
index 00000000000..68c3c8cf0c3
--- /dev/null
+++ b/src/events/ressLazyEvening.js
@@ -0,0 +1,241 @@
+App.Events.RESSLazyEvening = class RESSLazyEvening extends App.Events.BaseEvent {
+	constructor(actors, params) {
+		super(actors, params);
+	}
+
+	eventPrerequisites() {
+		return [];
+	}
+
+	actorPrerequisites() {
+		return [
+			[ // single event slave
+				s => s.fetish !== "mindbroken",
+				hasAnyArms,
+				hasAnyLegs,
+				s => s.assignment === "please you",
+				s => s.devotion > 20,
+			]
+		];
+	}
+
+	execute(node) {
+		let [eventSlave] = this.actors.map(a => getSlave(a));
+		const {
+			He, he, His, his, hers, him, himself, girl, woman
+		} = getPronouns(eventSlave);
+		Enunciate(eventSlave);
+
+		function getSceneClothes(slave) {
+			if (getLimbCount(slave, 102) > 2) {
+				return "an oversized t-shirt";
+			} else if (slave.boobs > 4000) {
+				return "an oversized t-shirt"; /* loose pajama top */
+			} else if (slave.intelligence+slave.intelligenceImplant > 50) {
+				return "a halter top dress";
+			} else if (slave.muscles > 30) {
+				if (isItemAccessible.entry("sport shorts", "clothing")) {
+					if (slave.boobs >= 650) {
+						return "sport shorts and a sports bra";
+					} else {
+						return "sport shorts";
+					}
+				} else {
+					return "spats and a tank top";
+				}
+			} else if (slave.energy > 95) {
+				return null; // no change of clothes
+			} else {
+				return "conservative clothing";
+			}
+		}
+
+		App.Events.drawEventArt(node, eventSlave, getSceneClothes(eventSlave));
+
+		let t = [];
+		t.push(`Although your life as an arcology owner comes with many associated privileges, extended idleness to bask in your luxury is not often among them. Thankfully, ${V.assistant.name} knows better than to let you run yourself ragged from the weight of your assorted responsibilities and often allots time in the evenings of your active schedule to simply relax.`);
+		App.Events.addParagraph(node, t);
+
+		t = [];
+		t.push(`Of course, no self respecting arcology owner could be expected to enjoy a lazy night of idle relaxation on their own. As you resolve the last of your most pressing responsibilities for the evening, ${V.assistant.name} directs one of your attentive slaves to gently guide you away from the unending burdens of running your arcology. Leaning against the doorway and wearing a facsimile of what an old world ${woman} might wear on a casual night in,`);
+		t.push(App.UI.DOM.slaveDescriptionDialog(eventSlave));
+		if (!canTalk(eventSlave)) {
+			t.push(`asks with a gesture that carries just the right mixture of submission and exaggerated casualness if you'd like to 'hang out.'`);
+		} else if (SlaveStatsChecker.checkForLisp(eventSlave)) {
+			t.push(`lisps with exaggerated casualness, "Let'${V.sEnunciate} hang out, ${V.titleEnunciate}?"`);
+		} else {
+			t.push(`asks with exaggerated casualness, "Want to hang out, ${V.titleEnunciate}?"`);
+		}
+		App.Events.addParagraph(node, t);
+
+		t = [];
+		t.push(`${He} saunters over and`);
+		if (eventSlave.belly >= 100000) {
+			t.push(`struggles to lower ${his} ${bellyAdjective(eventSlave)} form to an obedient kneel`);
+		} else if (eventSlave.belly >= 10000) {
+			t.push(`gingerly lowers ${his} heavily ${eventSlave.bellyPreg > 3000 ? "gravid" : "swollen"} form to an obedient kneel`);
+		} else if (eventSlave.belly >= 5000) {
+			t.push(`gently lowers ${his} ${eventSlave.bellyPreg > 3000 ? "gravid" : "swollen"} form to an obedient kneel`);
+		} else {
+			t.push(`kneels obediently`);
+		}
+		t.push(`in front of you, awaiting further direction.`);
+
+		/* If you are updating below, please consider updating the vector art swaps at the top to match. */
+		if (getLimbCount(eventSlave, 102) > 2) {
+			t.push(`Clad in an antique T-Shirt referencing some defunct old world website, ${his} P-Limbs stand in stark contrast — gyros and servomotors against simple thread and cloth. With such tangible examples of the technological prowess of the Free Cities serving as ${his} limbs, ${his} ${eventSlave.belly >= 5000 ? "taut " : ""} shirt is an amusing testimonial to how far behind the old world stands in contrast to the new.`);
+		} else if (eventSlave.boobs > 4000) {
+			t.push(`${His} breasts are so massive that the front of ${his} loose pajama top must be unbuttoned. Even so, the protrusion of ${his} immense breasts${eventSlave.belly >= 5000 ? ` and ${bellyAdjective(eventSlave)} rounded belly` : ""} from ${his} chest strains the soft pajama top to it's breaking point.`);
+		} else if (eventSlave.intelligence+eventSlave.intelligenceImplant > 50) {
+			t.push(`As a clever ${girl}, ${his} simple${eventSlave.belly >= 5000 ? `, yet tight around the middle,` : ""} summer dress evokes memories of bygone warm weather days at elite old world colleges — and the sexual conquest of their youthful residents.`);
+		} else if (eventSlave.muscles > 30) {
+			t.push(`${His} simple sports bra and compression shorts ensemble does little to conceal ${his} incredible musculature,`);
+			if (eventSlave.belly >= 1500) {
+				t.push(`straining to hold up against ${his} swelling middle and`);
+			}
+			t.push(`glistening with sweat from a recent workout. Despite ${his} recent exertions, ${he}'s able to maintain utter stillness in the perfect posture of an obedient slave.`);
+		} else if (eventSlave.energy > 95) {
+			t.push(`${He}'s controlling ${his} absurd sex drive for the moment in deference to the notion of your relaxation time, but ${he} clearly wouldn't mind some sex as part of the evening.`);
+			if (eventSlave.dick > 0) {
+				if (canAchieveErection(eventSlave)) {
+					t.push(`${His} cock is painfully erect`);
+					if (eventSlave.belly >= 10000) {
+						t.push(`and pressed against the underside of ${his} belly,`);
+					}
+				} else {
+					t.push(`${His} soft dick is dribbling precum,`);
+				}
+			} else {
+				t.push(`${His} pussy is visibly soaked,`);
+			}
+			t.push(`showing unmistakably how badly ${he} needs release.`);
+		} else {
+			t.push(`${He} keeps ${his}`);
+			if (canSee(eventSlave)) {
+				t.push(App.Desc.eyesColor(eventSlave));
+			} else {
+				t.push("face");
+			}
+			t.push(`slightly downcast, ${his} hands lightly smoothing the folds from ${his} tight skirt while ${his} breasts visibly rise and fall under ${his} even tighter blouse.`);
+			if (eventSlave.belly >= 5000) {
+				t.push(`Between the two, there is little ${he} can do to cover ${his} exposed ${eventSlave.bellyPreg >= 3000 ? "pregnancy" : "middle"}.`);
+			 }
+			 t.push(`${He}'s the perfect picture of an attentive little old world ${girl}friend ${eventSlave.height > 185 ? ` (though, of course, ${he}'s anything but physically small)` : ""}.`);
+		}
+		App.Events.addParagraph(node, t);
+
+		App.Events.addResponses(node, [
+			new App.Events.Result("Enjoy some oral with an evening of wallscreen television", flixAndChill),
+			new App.Events.Result("Spend the night idly cuddling", cuddle),
+			new App.Events.Result(`Unwind by tormenting ${him}`, torture)
+		]);
+
+		function flixAndChill() {
+			let t = [];
+			t.push(`There are some things that never change, even after ascension to the high position of an arcology owner. One of these fixtures of life is the ability to enjoy a relaxing evening of wallscreen television and`);
+			if (V.PC.dick !== 0) {
+				t.push("a blowjob");
+				if (V.PC.vagina !== -1) {
+					t.push("and");
+				}
+			}
+			if (V.PC.vagina !== -1) {
+				t.push("some cunnilingus");
+			}
+			t.push(t.pop() + ".");
+			t.push(`With ${eventSlave.slaveName} sequestered between your legs, you tune into your favorite Free Cities serial drama and widen your legs slightly as you sink back into the chair with a sigh of contentment. ${He}`);
+			if (eventSlave.belly >= 300000) {
+				t.push(`gently leans onto ${his} ${bellyAdjective(eventSlave)} belly`);
+				if (hasAnyArms(eventSlave)) {
+					t.push(`with ${his} arm${hasBothArms(eventSlave) ? "s" : ""} steadying the mass`);
+				}
+			} else if (eventSlave.belly >= 5000) {
+				if (hasAnyLegs(eventSlave)) {
+					t.push(`kneels carefully`);
+				} else {
+					t.push(`lowers ${himself}`);
+				}
+				if (hasAnyArms(eventSlave)) {
+					t.push(`with ${hasBothArms(eventSlave) ? `an` : `${his}`} arm`);
+					if (eventSlave.bellyPreg >= 3000) {
+						t.push(`wrapped protectively around ${his} bump`);
+					} else {
+						t.push(`cradling ${his} ${bellyAdjective(eventSlave)} belly`);
+					}
+				}
+			} else {
+				t.push(`sinks to`);
+				if (hasAnyLegs(eventSlave)) {
+					t.push(`${his} knee${hasBothLegs(eventSlave) ? "s" : ""} obediently`);
+					if (hasAnyArms(eventSlave)) {
+						t.push(`with ${his} hand${hasBothArms(eventSlave) ? "s" : ""} placed placidly on ${his} thigh${hasBothLegs(eventSlave) ? "s" : ""}`);
+					}
+				} else {
+					t.push("the ground");
+				}
+			}
+			t.push(`before putting ${his} mouth to work,`);
+			if (eventSlave.skill.oral >= 100) {
+				t.push(`${his} mastery at giving oral providing a wealth of pleasure.`);
+			} else if (eventSlave.skill.oral > 60) {
+				t.push(`${his} skills in oral providing ample pleasure.`);
+			} else {
+				t.push(`${his} mediocre oral skills providing some relief.`);
+			}
+			if (eventSlave.teeth === "pointy") {
+				t.push(`Although most of your attention is focused on the intriguing drama unfolding on your wallscreen, you still feel the extreme care ${he} has to take to keep ${his} shark-like teeth clear of you.`);
+			} else if (eventSlave.lips > 40) {
+				t.push(`${His} huge lips are soft and pillowy against you.`);
+			} else if (eventSlave.teeth === "gapped") {
+				t.push(`Although most of your attention is focused on the intriguing drama unfolding on your wallscreen, you can feel the slight hesitations as ${he} takes care to not pinch you between ${his} front teeth.`);
+			} else if ((eventSlave.teeth === "straightening braces") || (eventSlave.teeth === "cosmetic braces")) {
+				t.push(`Although most of your attention is focused on the intriguing drama unfolding on your wallscreen, you can feel the slight hesitations as ${he} takes care to keep ${his} braces off you.`);
+			}
+			t.push(`You have an enjoyable evening glued to your wallscreen, punctuated by the playful ruffling ${eventSlave.hLength > 1 ? `of ${eventSlave.slaveName}'s ${eventSlave.hColor} hair` : `across ${eventSlave.slaveName}'s scalp`} and the occasional orgasm into ${his} waiting mouth.`);
+			if (eventSlave.sexualFlaw !== "hates oral") {
+				t.push(`Though your experience was more stimulating than ${hers}, ${eventSlave.slaveName} <span class="devotion inc">enjoyed being used while you enjoyed yourself.</span>`);
+				eventSlave.devotion += 4;
+			} else {
+				t.push(`Although you enjoyed ${his} ministrations, ${eventSlave.slaveName} had a bad time because of ${his} <span class="devotion dec">hate of oral.</span>`);
+				eventSlave.devotion -= 2;
+			}
+			seX(eventSlave, "oral", V.PC, "penetrative");
+			return t;
+		}
+
+		function cuddle() {
+			let t = [];
+			t.push(`Though your evening could hardly be called eventful, there is something eminently comforting about having a warm`);
+			if (eventSlave.physicalAge > 30) {
+				t.push(woman);
+			} else if (eventSlave.physicalAge > 18) {
+				t.push("young lady");
+			} else if (eventSlave.physicalAge > 12) {
+				t.push("teen");
+			} else {
+				t.push(`little ${girl}`);
+			}
+			t.push(`cuddled up beside you to idly while away the hours`);
+			if (eventSlave.bellyPreg >= 1500 && eventSlave.pregSource === -1){
+				t.push(t.pop() + ",");
+				t.push(`especially when ${he} is ${eventSlave.belly >= 300000 ? `so massively swollen with your children` : `heavy with your child${eventSlave.pregType > 1 ? "ren" : ""}`}`);
+			}
+			t.push(t.pop() + ".");
+			t.push(`${He} <span class="trust inc">trusts you more</span> for these few intimate hours amidst ${his} life of sexual servitude.`);
+			eventSlave.trust += 4;
+			return t;
+		}
+
+		function torture() {
+			let t = [];
+			t.push(`Though there is no shortage of torments you inflict during the course of your day to day life as an arcology owner, there is something refreshing about torturing a slave out of idle boredom rather than corrective disciple or sexual domination. Your night is filled with ${eventSlave.voice === 0 ? "the horrible rasping that a mute throat substitutes for cries of agony" : "echoing shrieks of anguish"}, though every vocal outburst is idly punished with electro shock or strike of the whip. Come the morning, ${eventSlave.slaveName}`);
+			if (eventSlave.fetish === "masochist") {
+				t.push(`is mortified by the intensity of ${his} orgasms that night,<span class="devotion inc"> and more convinced than ever that ${he}'s a pain slut,</span> and yet`);
+				eventSlave.devotion += 4;
+			}
+			t.push(`<span class="trust dec">scuttles away to tend to the bruises and marks that litter ${his} battered body.</span>`);
+			eventSlave.trust -= 5;
+			return t;
+		}
+	}
+};
diff --git a/src/js/eventSelectionJS.js b/src/js/eventSelectionJS.js
index 9de37f261ef..da0d4d3a96d 100644
--- a/src/js/eventSelectionJS.js
+++ b/src/js/eventSelectionJS.js
@@ -1305,12 +1305,6 @@ window.generateRandomEventPoolStandard = function(eventSlave) {
 				}
 			}
 
-			if (eventSlave.assignment === "please you") {
-				if (eventSlave.devotion > 20) {
-					State.variables.RESSevent.push("lazy evening");
-				}
-			}
-
 			if (eventSlave.height < (Height.mean(eventSlave) * 0.95)) {
 				if (eventSlave.physicalAge > 12) {
 					if (canDoAnal(eventSlave)) {
diff --git a/src/js/utilsSC.js b/src/js/utilsSC.js
index c25ceb4cf6a..e115b003fcf 100644
--- a/src/js/utilsSC.js
+++ b/src/js/utilsSC.js
@@ -271,6 +271,19 @@ App.UI.disabledLink = function(link, reasons) {
 	return `<span class="textWithTooltip">${link}${tooltips}</span>`;
 };
 
+/** handler function for slaveDescriptionDialog.  do not call directly. */
+App.UI._showDescriptionDialog = function(slave) {
+	const oldEventDescription = V.eventDescription;
+	V.eventDescription = 1; // should be easy enough to use this for non-event cases too, but you'll need to fix the art display
+	const oldActiveSlave = V.activeSlave;
+	V.activeSlave = slave;
+	Dialog.setup(SlaveFullName(slave));
+	Dialog.wiki('<div class="imageRef medImg"><<= SlaveArt($activeSlave, 2, 0)>></div><<include "Long Slave Description">>');
+	Dialog.open();
+	V.activeSlave = oldActiveSlave;
+	V.eventDescription = oldEventDescription;
+};
+
 /**
  * Generates a link which shows a slave description dialog for a specified slave.
  * Do not call from within another dialog.
@@ -278,17 +291,15 @@ App.UI.disabledLink = function(link, reasons) {
  * @returns {string} link (in SC markup)
  */
 App.UI.slaveDescriptionDialog = function(slave) {
-	function showDialog() {
-		const oldEventDescription = V.eventDescription;
-		V.eventDescription = 1; // should be easy enough to use this for non-event cases too, but you'll need to fix the art display
-		const oldActiveSlave = V.activeSlave;
-		V.activeSlave = slave;
-		Dialog.setup(SlaveFullName(slave));
-		Dialog.wiki('<div class="imageRef medImg"><<= SlaveArt($activeSlave, 2, 0)>></div><<include "Long Slave Description">>');
-		Dialog.open();
-		V.activeSlave = oldActiveSlave;
-		V.eventDescription = oldEventDescription;
-	}
+	return App.UI.link(SlaveFullName(slave), App.UI._showDescriptionDialog, [slave]);
+};
 
-	return App.UI.link(SlaveFullName(slave), showDialog, []);
+/**
+ * Generates a link which shows a slave description dialog for a specified slave.
+ * Do not call from within another dialog.
+ * @param {App.Entity.SlaveState} slave
+ * @returns {HTMLElement} link
+ */
+App.UI.DOM.slaveDescriptionDialog = function(slave) {
+	return App.UI.DOM.link(SlaveFullName(slave), App.UI._showDescriptionDialog, [slave]);
 };
diff --git a/src/uncategorized/RESS.tw b/src/uncategorized/RESS.tw
index 08701f15e8c..8e5d034b3a5 100644
--- a/src/uncategorized/RESS.tw
+++ b/src/uncategorized/RESS.tw
@@ -60,30 +60,6 @@
 	<<else>>
 		<<set $activeSlave.clothes = "spats and a tank top">>
 	<</if>>
-<<case "lazy evening">>
-	<<if getLimbCount($activeSlave, 102) > 2>>
-		<<set $activeSlave.clothes = "an oversized t-shirt">>
-	<<elseif $activeSlave.boobs > 4000>>
-		<<set $activeSlave.clothes = "an oversized t-shirt">> /* loose pajama top */
-	<<elseif $activeSlave.intelligence+$activeSlave.intelligenceImplant > 50>>
-		<<set $activeSlave.clothes = "a halter top dress">>
-	<<elseif $activeSlave.muscles > 30>>
-		<<if isItemAccessible.entry("sport shorts", "clothing")>>
-			<<if $activeSlave.boobs >= 650>>
-				<<set $activeSlave.clothes = "sport shorts and a sports bra">>
-			<<else>>
-				<<set $activeSlave.clothes = "sport shorts">>
-			<</if>>
-		<<else>>
-			<<set $activeSlave.clothes = "spats and a tank top">>
-		<</if>>
-	<<elseif $activeSlave.energy > 95>>
-		/* No custom outfit defined */
-	<<else>>
-		<<set $activeSlave.clothes = "conservative clothing">>
-	<</if>>
-<<case "mean girls">>
-
 <</switch>>
 
 <<if $RESSevent == "mean girls">>
@@ -4234,55 +4210,6 @@ and flirting with passersby. Or $he would be, if $he weren't surrounded by a gro
 <</if>>
 says a third, obviously smitten. "I'd give anything to have a night with $him."
 
-<<case "lazy evening">>
-
-Although your life as an arcology owner comes with many associated privileges, extended idleness to bask in your luxury is not often among them. Thankfully, $assistant.name knows better than to let you run yourself ragged from the weight of your assorted responsibilities and often allots time in the evenings of your active schedule to simply relax.
-<br><br>
-Of course, no self respecting arcology owner could be expected to enjoy a lazy night of idle relaxation on their own. As you resolve the last of your most pressing responsibilities for the evening, $assistant.name directs one of your attentive slaves to gently guide you away from the unending burdens of running your arcology. Leaning against the doorway and wearing a facsimile of what an old world $woman might wear on a casual night in, <<= App.UI.slaveDescriptionDialog($activeSlave)>>
-<<if !canTalk($activeSlave)>>
-	asks with a gesture that carries just the right mixture of submission and exaggerated casualness if you'd like to 'hang out.'
-<<elseif SlaveStatsChecker.checkForLisp($activeSlave)>>
-	lisps with exaggerated casualness, "Let'<<s>> hang out, <<Master>>?"
-<<else>>
-	asks with exaggerated casualness, "Want to hang out, <<Master>>?"
-<</if>>
-<br><br>
-$He saunters over and
-<<if $activeSlave.belly >= 100000>>
-	struggles to lower $his _belly form to an obedient kneel
-<<elseif $activeSlave.belly >= 10000>>
-	gingerly lowers $his heavily <<if $activeSlave.bellyPreg > 3000>>gravid<<else>>swollen<</if>> form to an obedient kneel
-<<elseif $activeSlave.belly >= 5000>>
-	gently lowers $his <<if $activeSlave.bellyPreg > 3000>>gravid<<else>>swollen<</if>> form to an obedient kneel
-<<else>>
-	kneels obediently
-<</if>>
-in front of you, awaiting further direction.
-/* If you are updating below, please consider updating the vector art swaps at the top to match. */
-<<if getLimbCount($activeSlave, 102) > 2>>
-	Clad in an antique T-Shirt referencing some defunct old world website, $his P-Limbs stand in stark contrast — gyros and servomotors against simple thread and cloth. With such tangible examples of the technological prowess of the Free Cities serving as $his limbs, $his <<if $activeSlave.belly >= 5000>>taut <</if>>shirt is an amusing testimonial to how far behind the old world stands in contrast to the new.
-<<elseif $activeSlave.boobs > 4000>>
-	$His breasts are so massive that the front of $his loose pajama top must be unbuttoned. Even so, the protrusion of $his immense breasts<<if $activeSlave.belly >= 5000>> and _belly rounded belly<</if>> from $his chest strains the soft pajama top to it's breaking point.
-<<elseif $activeSlave.intelligence+$activeSlave.intelligenceImplant > 50>>
-	As a clever $girl, $his simple<<if $activeSlave.belly >= 5000>>, yet tight around the middle,<</if>> summer dress evokes memories of bygone warm weather days at elite old world colleges — and the sexual conquest of their youthful residents.
-<<elseif $activeSlave.muscles > 30>>
-	$His simple sports bra and compression shorts ensemble does little to conceal $his incredible musculature, <<if $activeSlave.belly >= 1500 && $activeSlave.belly < 5000>>straining to hold up against $his swelling middle and<<elseif $activeSlave.belly >= 5000>>straining to hold up against $his swelling middle and <</if>>glistening with sweat from a recent workout. Despite $his recent exertions, $he's able to maintain utter stillness in the perfect posture of an obedient slave.
-<<elseif $activeSlave.energy > 95>>
-	$He's controlling $his absurd sex drive for the moment in deference to the notion of your relaxation time, but $he clearly wouldn't mind some sex as part of the evening.
-	<<if $activeSlave.dick > 0>>
-		<<if canAchieveErection($activeSlave)>>
-			$His cock is painfully erect<<if $activeSlave.belly >= 10000>> and pressed against the underside of $his belly<</if>>,
-		<<else>>
-			$His soft dick is dribbling precum,
-		<</if>>
-	<<else>>
-		$His pussy is visibly soaked,
-	<</if>>
-	showing unmistakably how badly $he needs release.
-<<else>>
-	$He keeps $his <<if canSee($activeSlave)>><<= App.Desc.eyesColor($activeSlave)>><<else>>face<</if>> slightly downcast, $his hands lightly smoothing the folds from $his tight skirt while $his breasts visibly rise and fall under $his even tighter blouse<<if $activeSlave.belly >= 5000>>. Between the two, there is little $he can do to cover $his exposed <<if $activeSlave.bellyPreg >= 3000>>pregnancy<<else>>middle<</if>><</if>>. $He's the perfect picture of an attentive little old world <<= $girl>>friend<<if $activeSlave.height > 185>> (though, of course, $he's anything but physically small)<</if>>.
-<</if>>
-
 <<case "devoted shortstack">>
 
 <<= App.UI.slaveDescriptionDialog($activeSlave)>> comes before you for a routine inspection. The
@@ -19886,79 +19813,6 @@ brought in to you. This time <<= App.UI.slaveDescriptionDialog($activeSlave)>> h
 	<</link>>
 <</if>>
 
-<<case "lazy evening">>
-
-<<link "Enjoy some oral with an evening of wallscreen television">>
-	<<replace "#result">>
-		There are some things that never change, even after ascension to the high position of an arcology owner. One of these fixtures of life is the ability to enjoy a relaxing evening of wallscreen television and <<if $PC.dick != 0>>a blowjob<<if $PC.vagina != -1>> and <</if>><</if>><<if $PC.vagina != -1>>some cunnilingus<</if>>. With $activeSlave.slaveName sequestered between your legs, you tune into your favorite Free Cities serial drama and <<if !canSee($activeSlave)>>audibly <</if>>widen your legs slightly as you sink back into the chair with a sigh of contentment. $He
-		<<if $activeSlave.belly >= 300000>>
-			gently leans onto $his _belly belly<<if hasAnyArms($activeSlave)>> with $his arm<<if hasBothArms($activeSlave)>>s<</if>> steadying the mass<</if>>
-		<<elseif $activeSlave.belly >= 5000>>
-			<<if hasAnyLegs($activeSlave)>>kneels carefully<<else>>lowers $himself<</if>>
-			<<if hasAnyArms($activeSlave)>>
-				with <<if hasBothArms($activeSlave)>>an<<else>>$his<</if>> arm
-				<<if $activeSlave.bellyPreg >= 3000>>
-					wrapped protectively around $his bump
-				<<else>>
-					cradling $his _belly belly
-				<</if>>
-			<</if>>
-		<<else>>
-			sinks to <<if hasAnyLegs($activeSlave)>>$his knee<<if !hasAnyLegs($activeSlave)>>s<</if>> obediently<<if hasAnyArms($activeSlave)>> with $his hand<<if hasBothArms($activeSlave)>>s<</if>> placed placidly on $his thigh<<if !hasAnyLegs($activeSlave)>>s<</if>><</if>><<else>>the ground<</if>>
-		<</if>>
-		before putting $his mouth to work,
-		<<if $activeSlave.skill.oral >= 100>>
-			$his mastery at giving oral providing a wealth of pleasure.
-		<<elseif $activeSlave.skill.oral > 60>>
-			$his skills in oral providing ample pleasure.
-		<<else>>
-			$his mediocre oral skills providing some relief.
-		<</if>>
-		<<if $activeSlave.teeth == "pointy">>
-			Although most of your attention is focused on the intriguing drama unfolding on your wallscreen, you still feel the extreme care $he has to take to keep $his shark-like teeth clear of you.
-		<<elseif $activeSlave.lips > 40>>
-			$His huge lips are soft and pillowy against you.
-		<<elseif $activeSlave.teeth == "gapped">>
-			Although most of your attention is focused on the intriguing drama unfolding on your wallscreen, you can feel the slight hesitations as $he takes care to not pinch you between $his front teeth.
-		<<elseif ($activeSlave.teeth == "straightening braces") || ($activeSlave.teeth == "cosmetic braces")>>
-			Although most of your attention is focused on the intriguing drama unfolding on your wallscreen, you can feel the slight hesitations as $he takes care to keep $his braces off you.
-		<</if>>
-		You have an enjoyable evening glued to your wallscreen, punctuated by the playful ruffling <<if $activeSlave.hLength > 1>>of $activeSlave.slaveName's $activeSlave.hColor hair<<else>>across $activeSlave.slaveName's scalp<</if>> and the occasional orgasm into $his waiting mouth.
-			<<if $activeSlave.sexualFlaw !== "hates oral">>
-				Though your experience was more stimulating than $hers, $activeSlave.slaveName enjoyed @@.hotpink;being used while you enjoyed yourself.@@
-				<<set $activeSlave.devotion += 4>>
-			<<else>>
-				Although you enjoyed $his ministrations, $activeSlave.slaveName had a bad time because of $his @@.gold;hate of oral.@@
-				<<set $activeSlave.devotion -= 2>>
-			<</if>>
-		<<run seX($activeSlave, "oral", $PC, "penetrative")>>
-	<</replace>>
-<</link>>
-<br><<link "Spend the night idly cuddling">>
-	<<replace "#result">>
-		Though your evening could hardly be called eventful, there is something eminently comforting about having a warm
-		<<if $activeSlave.physicalAge > 30>>
-			$woman
-		<<elseif $activeSlave.physicalAge > 18>>
-			young lady
-		<<elseif $activeSlave.physicalAge > 12>>
-			teen
-		<<else>>
-			little $girl
-		<</if>>
-		cuddled up beside you to idly while away the hours<<if $activeSlave.bellyPreg >= 1500 && $activeSlave.pregSource == -1>>, especially when $he is <<if $activeSlave.belly >= 300000>>so massively swollen with your children<<else>>heavy with your child<<if $activeSlave.pregType > 1>>ren<</if>><</if>><</if>>.
-		$He @@.mediumaquamarine;trusts you more@@ for these few intimate hours amidst $his life of sexual servitude.
-		<<set $activeSlave.trust += 4>>
-	<</replace>>
-<</link>>
-<br><<link "Unwind by tormenting $him">>
-	<<replace "#result">>
-		Though there is no shortage of torments you inflict during the course of your day to day life as an arcology owner, there is something refreshing about torturing a slave out of idle boredom rather than corrective disciple or sexual domination. Your night is filled with <<if $activeSlave.voice == 0>>the horrible rasping that a mute throat substitutes for cries of agony<<else>>echoing shrieks of anguish<</if>>, though every vocal outburst is idly punished with electro shock or strike of the whip. Come the morning, $activeSlave.slaveName <<if $activeSlave.fetish == "masochist">> is mortified by the intensity of $his orgasms that night,@@.hotpink; and more convinced than ever that $he's a pain slut,@@ and yet<</if>> @@.gold;scuttles away to tend to the bruises and marks that litter $his battered body.@@
-		<<set $activeSlave.trust -= 5>>
-		<<if $activeSlave.fetish == "masochist">><<set $activeSlave.devotion += 4>><</if>>
-	<</replace>>
-<</link>>
-
 <<case "devoted shortstack">>
 
 <<link "Show $him why you like having short <<= $girl>>s around">>
-- 
GitLab