code:
App.UI.SlaveInteract.work = function(slave) {
	let el = new DocumentFragment();
	let p;
	let div;
	let span;
	const links = [];
	const {
		His, He,
		his, him
	} = getPronouns(slave);

	function appendLink(text, whoreClass, enabled, disabledText) {
		const link = {text: text};
		if (enabled) {
			link.whoreClass = whoreClass;
		} else {
			link.disabled = disabledText;
		}
		links.push(link);
	}

	p = document.createElement('p');
	if (slave.assignment === Job.AGENT) {
		const arc = V.arcologies.find((a) => a.leaderID === slave.ID);
		p.className = "scene-intro";
		p.textContent = `${He} is serving as your Agent${arc ? ` leading ${arc.name}` : ` but is not currently assigned to an arcology`}. `;
		p.appendChild(App.UI.DOM.link(`Recall and reenslave ${him}`, () => { removeJob(slave, slave.assignment, false); refresh(); }));
	} else if (slave.assignment === Job.AGENTPARTNER) {
		const agent = getSlave(slave.relationshipTarget);
		const arc = agent ? V.arcologies.find((a) => a.leaderID === agent.ID) : null;
		p.className = "scene-intro";
		p.textContent = `${He} is living with your Agent ${SlaveFullName(agent)}${arc ? ` in ${arc.name}` : ``}. `;
		p.appendChild(App.UI.DOM.link(`Recall ${him}`, () => { removeJob(slave, slave.assignment, false); refresh(); }));
	} else {
		div = document.createElement('div');
		div.id = "mini-scene";
		p.appendChild(div);

		span = document.createElement('span');
		span.id = "useSlave";
		p.appendChild(span);
		p.appendChild(useSlaveDisplay());
	}
	el.append(p);

	p = document.createElement('p');
	span = document.createElement('span');
	span.className = "note";
	switch (slave.assignment) {
		case Job.BODYGUARD:
			span.textContent = `${He} is your Bodyguard and is not available for other work`;
			break;
		case Job.MADAM:
			span.textContent = `${He} is the Madam and is not available for other work`;
			break;
		case Job.DJ:
			span.textContent = `${He} is the DJ and is not available for other work`;
			break;
		case Job.MILKMAID:
			span.textContent = `${He} is the Milkmaid and is not available for other work`;
			break;
		case Job.FARMER:
			span.textContent = `${He} is the Farmer and is not available for other work`;
			break;
		case Job.STEWARD:
			span.textContent = `${He} is the Stewardess and is not available for other work`;
			break;
		case Job.HEADGIRL:
			span.textContent = `${He} is your Head Girl and is not available for other work`;
			break;
		case Job.RECRUITER:
			span.textContent = `${He} is recruiting slaves and is not available for other work`;
			break;
		case Job.NURSE:
			span.textContent = `${He} is the Nurse and is not available for other work`;
			break;
		case Job.ATTENDANT:
			span.textContent = `${He} is the Attendant of the spa and is not available for other work`;
			break;
		case Job.MATRON:
			span.textContent = `${He} is the Matron of the nursery and is not available for other work`;
			break;
		case Job.TEACHER:
			span.textContent = `${He} is the Schoolteacher and is not available for other work`;
			break;
		case Job.CONCUBINE:
			span.textContent = `${He} is your Concubine and is not available for other work`;
			break;
		case Job.WARDEN:
			span.textContent = `${He} is the Wardeness and is not available for other work`;
			break;
		default:
			// CAN BE REASSIGNED
			span.classList.remove("note");
			span.id = "assignmentLinks";
			span.appendChild(assignmentBlock());

			if ((V.brothel + V.club + V.dairy + V.farmyard + V.servantsQuarters + V.masterSuite + V.spa + V.nursery + V.clinic + V.schoolroom + V.cellblock + V.arcade + V.HGSuite) > 0) {
				span.append(`Transfer to: `);
				span.appendChild(App.UI.jobLinks.transfersFragment(slave.ID));
			}

			div = document.createElement('div');
			div.id = "fucktoy-pref";
			div.append(fucktoyPref());
			span.appendChild(div);
	}
	el.append(span);

	if (slave.assignment === Job.WHORE || slave.assignment === Job.BROTHEL) {
		div = document.createElement('div');
		div.textContent = `Whoring Target: `;
		span = document.createElement('span');
		span.style.fontWeight = "bold";

		span.id = "whoreClass";
		if (!slave.whoreClass) {
			span.textContent = `auto`;
		} else if (slave.whoreClass === 1) {
			span.textContent = `the lower class`;
		} else if (slave.whoreClass === 2) {
			span.textContent = `the middle class`;
		} else if (slave.whoreClass === 3) {
			span.textContent = `the upper class`;
		} else if (slave.whoreClass === 4) {
			span.textContent = `millionaires`;
		} else {
			span.textContent = `THERE HAS BEEN AN ERROR`;
		}
		div.append(span);
		div.append(`. `);

		div.append(App.UI.DOM.makeElement('span', `This is the highest class they are allowed to service, when eligible `, 'note'));

		appendLink(`Auto`, 0, true);
		appendLink(`Lower Class`, 1, true);
		appendLink(`Middle Class`, 2, true);
		appendLink(`Upper Class`, 3, true);
		appendLink(`Millionaires Class`, 4, true);
		div.appendChild(App.UI.DOM.generateLinksStrip(links.map(generateLink)));

		el.append(div);
	}

	el.append(tutorBlock());
	return el;

	function generateLink(linkDesc) {
		// is it just text?
		if (linkDesc.disabled) { return App.UI.DOM.disabledLink(linkDesc.text, [linkDesc.disabled]); }
		// Are they already on this whoreClass?
		if (linkDesc.whoreClass === slave.whoreClass) { return document.createTextNode(linkDesc.text); }
		// Set up the link
		const link = App.UI.DOM.link(
			linkDesc.text,
			() => {
				slave.whoreClass = linkDesc.whoreClass;
				refresh();
			},
		);

		// add a note node if required
		if (linkDesc.note) {
			App.UI.DOM.appendNewElement("span", link, linkDesc.note, "note");
		}
		return link;
	}

	function assignmentBlock() {
		let el = document.createElement('div');
		let title = document.createElement('div');
		const linkArray = [];
		title.append(`Assignment: `);

		let assign = document.createElement('span');
		assign.style.fontWeight = "bold";
		if (slave.sentence) {
			assign.textContent = `${slave.assignment} (${slave.sentence} weeks). `;
		} else {
			assign.textContent = `${slave.assignment}. `;
		}
		title.appendChild(assign);
		if (V.assignmentRecords[slave.ID] && V.assignmentRecords[slave.ID] !== slave.assignment) {
			title.append(`Previously: `);
			assign = document.createElement('span');
			assign.style.fontWeight = "bold";
			assign.textContent = `${V.assignmentRecords[slave.ID]}. `;
			title.appendChild(assign);
		}
		if (slave.assignment === Job.SUBORDINATE) {
			const target = getSlave(slave.subTarget);
			let linkText = ``;
			if (target) {
				title.appendChild(document.createTextNode(`Serving ${target.slaveName} exclusively. `));
				linkText = `Change`;
			} else if (slave.subTarget === -1) {
				title.appendChild(document.createTextNode(`Serving as a Stud. `));
				linkText = `Change role`;
			} else {
				title.appendChild(document.createTextNode(`Serving all your other slaves. `));
				linkText = `Choose a specific slave to submit to`;
			}
			linkArray.push(App.UI.DOM.passageLink(linkText, "Subordinate Targeting", () => { V.returnTo = "Slave Interact"; }));
		}
		if (slave.assignment !== Job.CHOICE) {
			linkArray.push(
				App.UI.DOM.link(
					`Stay on this assignment for another month`,
					() => {
						slave.sentence += 4;
						refresh();
					},
				)
			);
		}
		title.append(App.UI.DOM.generateLinksStrip(linkArray));
		el.appendChild(title);

		let links = document.createElement('div');
		links.className = "choices";
		links.appendChild(
			App.UI.jobLinks.assignmentsFragment(
				slave.ID, passage(),
				(slave, assignment) => {
					assignJob(slave, assignment);
				}
			)
		);
		el.appendChild(links);
		return el;
	}

	function tutorBlock() {
		let el = App.UI.DOM.makeElement("div");
		let title = App.UI.DOM.appendNewElement("div", el, `Private tutoring: `);
		let tutor = tutorForSlave(slave);

		if (tutor === null) {
			App.UI.DOM.appendNewElement("span", title, `none.`, "bold");
		} else {
			App.UI.DOM.appendNewElement("span", title, tutor + `.`, "bold");
		}

		if (tutor != null) {
			App.UI.DOM.appendNewElement("span", title, ` To progress slave needs to be assigned to: "` + Job.CLASSES + `" or "` + Job.SCHOOL + `".`, "note");
		}

		let list = ["None"];
		for (const keys of Object.keys(V.slaveTutor)) {
			list.push(keys);
		}
		const array = list.map((s) => {
			if (shouldBeEnabled(slave, s)) {
				return App.UI.DOM.link(s, () => setTutorForSlave(slave, s));
			} else {
				let reason = ["Already being taught this skill."];
				return App.UI.DOM.disabledLink(s, reason);
			}
		});
		App.UI.DOM.appendNewElement("div", el, App.UI.DOM.generateLinksStrip(array));

		function shouldBeEnabled(slave, key) {
			let tutor = tutorForSlave(slave);
			if (tutor === null) {
				return "None";
			}
			return (tutor !== key);
		}

		function setTutorForSlave(slave, tutor) {
			const cur = tutorForSlave(slave);

			if (tutor !== cur && cur != null) {
				V.slaveTutor[cur].delete(slave.ID);
			}

			if (cur !== tutor && tutor !== "None") {
				V.slaveTutor[tutor].push(slave.ID);
			}
			refresh();
		}

		return el;
	}

	function useSlaveDisplay() {
		// Goal: Be able to write the entire "use her" block with only dom fragments.
		let el = document.createElement('div');

		/*
			Array of objects. Each object follows the form: {
				text: "Link text",
				scene: "scene to include",
				goto: if another passage is needed
				updateSlave: update slave itself if needed, like {trust: 2},
				update: updates V.,
				note: if a note must appear after the link
			}
			*/
		const sexOptions = [];
		const fillFaceOptions = [];
		const fillAssOptions = [];
		const dairyNameCaps = capFirstChar(V.dairyName);
		// if no scene, it's just text, no link. Italicize it.

		if (slave.fuckdoll === 0) {
			if (slave.vagina > -1) {
				if (canDoVaginal(slave)) {
					sexOptions.push({text: `Fuck ${him}`, scene: `FVagina`});
					if (canDoAnal(slave)) {
						sexOptions.push({text: `Use ${his} holes`, scene: `FButt`});
					}
				} else {
					sexOptions.push({text: `Fuck ${him}`, disabled: `Remove ${his} chastity belt if you wish to fuck ${him}`});
				}
			}
			if (slave.bellyPreg >= 300000) {
				if (canDoVaginal(slave) || canDoAnal(slave)) {
					sexOptions.push({text: `Fuck ${him} on ${his} belly`, scene: `FBellyFuck`});
					if (V.pregInventions >= 1) {
						sexOptions.push({text: `Fuck ${him} in ${his} maternity swing`, scene: `FMaternitySwing`});
						sexOptions.push({text: `Fuck ${him} with the help of ${his} assistants`, scene: `FAssistedSex`});
						sexOptions.push({text: `Fuck ${him} in your goo pool`, scene: `FPoolSex`});
					}
				}
			}

			if (canDoAnal(slave)) {
				sexOptions.push({text: `Fuck ${his} ass`, scene: `FAnus`});
			} else {
				sexOptions.push({text: `Fuck ${his} ass`, disabled: `Remove ${his} chastity belt if you wish to fuck ${his} ass`});
			}
			sexOptions.push({text: `Use ${his} mouth`, scene: `FLips`});
			sexOptions.push({text: `Kiss ${him}`, scene: `FKiss`});
			if (hasAnyLegs(slave)) {
				sexOptions.push({text: `Have ${him} dance for you`, scene: `FDance`});
			}

			sexOptions.push({text: `Play with ${his} tits`, scene: `FBoobs`});

			sexOptions.push({text: `Caress ${him}`, scene: `FCaress`});

			sexOptions.push({text: `Give ${him} a hug`, scene: `FEmbrace`});
			if (V.cheatMode === 1) {
				sexOptions.push({text: `Pat ${his} head`, scene: `FPat`});
			}

			sexOptions.push({text: `Grope ${his} boobs`, scene: `FondleBoobs`});
			if (slave.nipples === "fuckable" && V.PC.dick > 0) {
				sexOptions.push({text: `Fuck ${his} nipples`, scene: `FNippleFuck`});
			}
			if (slave.lactation > 0 && slave.boobs >= 2000 && slave.belly < 60000 && hasAnyArms(slave)) {
				sexOptions.push({text: `Drink ${his} milk`, scene: `FSuckle`});
			}

			if (canDoAnal(slave)) {
				sexOptions.push({text: `Grope ${his} butt`, scene: `FondleButt`});
			}

			if (slave.vagina > -1) {
				if (canDoVaginal(slave)) {
					sexOptions.push({text: `Grope ${his} pussy`, scene: `FondleVagina`});
					sexOptions.push({text: `Eat ${him} out`, scene: `FLickPussy`});
				}
			}

			if (slave.dick > 0) {
				if (!(slave.chastityPenis)) {
					sexOptions.push({text: `Grope ${his} dick`, scene: `FondleDick`});
					if (canPenetrate(slave)) {
						if (V.policies.sexualOpenness === 1 || slave.toyHole === "dick") {
							sexOptions.push({text: `Ride ${his} dick`, scene: `FDick`});
						}
					}
				} else {
					sexOptions.push({text: `Use ${his} dick`, disabled: `Remove ${his} dick chastity belt if you wish to play with ${his} cock`});
				}
			}

			if (hasAnyLegs(slave) && V.PC.dick > 0) {
				sexOptions.push({text: `Get a footjob`, scene: `FFeet`});
			}

			if (canGetPregnant(slave) && (slave.geneticQuirks.superfetation !== 2 || V.geneticMappingUpgrade !== 0) && (slave.fuckdoll === 0) && V.seePreg !== 0) {
				if (canImpreg(slave, V.PC)) {
					sexOptions.push({text: `Impregnate ${him} yourself`, scene: `FPCImpreg`});
				}
				if (canImpreg(slave, slave)) {
					sexOptions.push({text: `Use ${his} own seed to impregnate ${him}`, scene: `FSlaveSelfImpreg`});
				}
				sexOptions.push({text: `Use another slave to impregnate ${him}`, scene: `FSlaveImpreg`});
			}
			if (slave.assignment !== Job.DAIRY && slave.assignment !== Job.ARCADE && slave.assignment !== Job.CELLBLOCK) {
				if (V.dairyPiping === 1) {
					if ((V.milkPipeline > 88 && V.milkPipeline !== 0) || V.arcologies[0].FSPastoralistLaw === 1) {
						if ((slave.inflation < 3 && slave.pregKnown === 0 && slave.bellyImplant < 1500) || slave.inflation < 1) {
							if (slave.inflationType === "milk" || slave.inflationType === "none") {
								fillAssOptions.push({text: `Milk`, scene: `FillUpButt`, updateSlave: {inflationType: "milk", inflationMethod: 2}});
								fillFaceOptions.push({text: `Milk`, scene: `FillUpFace`, updateSlave: {inflationType: "milk", inflationMethod: 1}});
							}
						}
					} else {
						fillAssOptions.push({text: `Milk`, disabled: `${dairyNameCaps} is not producing enough milk to pump through the pipes`});
						fillFaceOptions.push({text: `Milk`, disabled: `${dairyNameCaps} is not producing enough milk to pump through the pipes`});
					}
					if ((V.cumPipeline > 88 && V.cumPipeline !== 0) || V.arcologies[0].FSPastoralistLaw === 1) {
						if ((slave.inflation < 3 && slave.pregKnown === 0 && slave.bellyImplant < 1500) || slave.inflation < 1) {
							if (slave.inflationType === "cum" || slave.inflationType === "none") {
								fillAssOptions.push({text: `Cum`, scene: `FillUpButt`, updateSlave: {inflationType: "cum", inflationMethod: 2}});
								fillFaceOptions.push({text: `Cum`, scene: `FillUpFace`, updateSlave: {inflationType: "cum", inflationMethod: 1}});
							}
						}
					} else {
						fillAssOptions.push({text: `Cum`, disabled: `${dairyNameCaps} is not producing enough cum to pump through the pipes`});
						fillFaceOptions.push({text: `Cum`, disabled: `${dairyNameCaps} is not producing enough cum to pump through the pipes`});
					}
				} /* dairyPiping === 1 */
				if (V.boughtItem.toys.enema === 1) {
					if ((slave.inflation < 3 && slave.pregKnown === 0 && slave.bellyImplant < 1500) || slave.inflation < 1) {
						if (slave.inflationType === "water" || slave.inflationType === "none") {
							fillAssOptions.push({text: `Water`, scene: `FillUpButt`, updateSlave: {inflationType: "water", inflationMethod: 2}});
						}
						if (V.boughtItem.toys.medicalEnema === 1) {
							if (slave.inflationType === "aphrodisiac" || slave.inflationType === "none") {
								fillAssOptions.push({text: `Aphrodisiacs`, scene: `FillUpButt`, updateSlave: {inflationType: "aphrodisiac", inflationMethod: 2}});
							}
							if (slave.inflationType === "curative" || slave.inflationType === "none") {
								fillAssOptions.push({text: `Curatives`, scene: `FillUpButt`, updateSlave: {inflationType: "curative", inflationMethod: 2}});
							}
							if (slave.inflationType === "tightener" || slave.inflationType === "none") {
								fillAssOptions.push({text: `Rectal tighteners`, scene: `FillUpButt`, updateSlave: {inflationType: "tightener", inflationMethod: 2}});
							}
						}
					} /* inflation < 3 */
				} /* enema === 1 */
				if (V.wcPiping === 1) {
					if ((slave.inflation < 3 && slave.pregKnown === 0 && slave.bellyImplant < 1500) || slave.inflation < 1) {
						if (slave.inflationType === "urine" || slave.inflationType === "none") {
							fillAssOptions.push({text: `Urine`, scene: `FillUpButt`, updateSlave: {inflationType: "urine", inflationMethod: 2}});
						}
					}
				} /* wcPiping === 1 */
			} /* assigned to dairy or arcade */
			if (slave.inflation === 0 && slave.bellyImplant < 1500) {
				if (slave.assignment !== Job.DAIRY && slave.assignment !== Job.ARCADE && slave.assignment !== Job.CELLBLOCK) {
					if (V.boughtItem.toys.buckets === 1) {
						fillFaceOptions.push({text: `Two liters of slave food`, scene: `forceFeeding`, updateSlave: {inflation: 1, inflationType: "food", inflationMethod: 1}});
						if (slave.pregKnown === 0) {
							fillFaceOptions.push({text: `A gallon of slave food`, scene: `forceFeeding`, updateSlave: {inflation: 2, inflationType: "food", inflationMethod: 1}});
							fillFaceOptions.push({text: `Two gallons of slave food`, scene: `forceFeeding`, updateSlave: {inflation: 3, inflationType: "food", inflationMethod: 1}});
						}
					}
					fillFaceOptions.push({text: `Get another slave to do it`, goto: `SlaveOnSlaveFeedingWorkAround`});
				}
			}
			if (canDoVaginal(slave)) {
				sexOptions.push({text: `Have another slave fuck ${his} pussy`, scene: `FSlaveSlaveVag`});
			}
			if (canPenetrate(slave)) {
				sexOptions.push({text: `Have another slave ride ${his} cock`, scene: `FSlaveSlaveDick`});
			} else if (slave.clit >= 4) {
				sexOptions.push({text: `Have another slave ride ${his} clit-dick`, scene: `FSlaveSlaveDick`});
			}
			if (V.seeBestiality) {
				if (V.farmyardKennels > 0 && V.activeCanine) {
					sexOptions.push({text: `Have a ${V.activeCanine.species} mount ${him}`, scene: `BeastFucked`, update: {animalType: "canine"}});
				}
				if (V.farmyardStables > 0 && V.activeHooved) {
					sexOptions.push({text: `Let a ${V.activeHooved.species} mount ${him}`, scene: `BeastFucked`, update: {animalType: "hooved"}});
				}
				if (V.farmyardCages > 0 && V.activeFeline) {
					sexOptions.push({text: `Have a ${V.activeFeline.species} mount ${him}`, scene: `BeastFucked`, update: {animalType: "feline"}});
				}
			}
			sexOptions.push({text: `Abuse ${him}`, scene: `FAbuse`});
			if (V.seeIncest === 1) {
				const availRelatives = availableRelatives(slave);
				if (availRelatives.mother) {
					sexOptions.push({text: `Fuck ${him} with ${his} mother`, scene: `FRelation`, update: {partner: "mother"}});
				} else if (availRelatives.motherName !== null) {
					sexOptions.push({text: `${His} mother, ${availRelatives.motherName}, is unavailable`});
				}
				/*
					if (availRelatives.father) {
						sexOptions.push({text: `Fuck ${him} with ${his} father`, scene: `FRelation`, update: {partner: "father"}});
					} else if (availRelatives.fatherName !== null) {
						sexOptions.push({text: `${His} father, ${availRelatives.motherName}, is unavailable`});
					}
					*/
				if (slave.daughters > 0) {
					if (availRelatives.daughters === 0) {
						if (slave.daughters === 1) {
							sexOptions.push({text: `Fuck ${him} with ${his} daughter`, disabled: `${His} ${availRelatives.oneDaughterRel} is unavailable`});
						} else {
							sexOptions.push({text: `Fuck ${him} with one of ${his} daughters`, disabled: `${His} daughters are unavailable`});
						}
					} else {
						if (slave.daughters === 1) {
							sexOptions.push({text: `Fuck ${him} with ${his} ${availRelatives.oneDaughterRel}`, scene: `FRelation`, update: {partner: "daughter"}});
						} else {
							sexOptions.push({text: `Fuck ${him} with one of ${his} daughters`, scene: `FRelation`, update: {partner: "daughter"}});
						}
						/*
							if (availRelatives.daughters > 1) {
								sexOptions.push({text: `Fuck ${him} with ${his} daughters`, scene: `FRelation`, update: {partner: "daughter"}});
							}
							*/
					}
				}
				if (slave.sisters > 0) {
					if (availRelatives.sisters === 0) {
						if (slave.sisters === 1) {
							sexOptions.push({text: `Fuck ${him} with ${his} sister`, disabled: `${His} ${availRelatives.oneSisterRel} is unavailable`});
						} else {
							sexOptions.push({text: `Fuck ${him} with one of ${his} sisters`, disabled: `${His} sisters are unavailable`});
						}
					} else {
						if (slave.sisters === 1) {
							sexOptions.push({text: `Fuck ${him} with ${his} ${availRelatives.oneSisterRel}`, scene: `FRelation`, update: {partner: "sister"}});
						} else {
							sexOptions.push({text: `Fuck ${him} with one of ${his} sisters`, scene: `FRelation`, update: {partner: "sister"}});
						}
						/*
							if (availRelatives.sisters > 1) {
								sexOptions.push({text: `Fuck ${him} with ${his} sisters`, scene: `FRelation`, update: {partner: "sisters}});
							}
							*/
					}
				}
			}
			if (slave.relationship > 0) {
				const lover = getSlave(slave.relationshipTarget);
				if (isSlaveAvailable(lover)) {
					sexOptions.push({text: `Fuck ${him} with ${his} ${relationshipTermShort(slave)} ${SlaveFullName(lover)}`, scene: `FRelation`, update: {partner: "relationship"}});
				} else if (lover.assignment === Job.AGENT) {
					if (slave.broodmother < 2) {
						sexOptions.push({text: `Send ${him} to live with your agent ${SlaveFullName(lover)}`, goto: `Agent Company`, update: {subSlave: lover}});
					} else {
						sexOptions.push({text: `A hyper-broodmother cannot be sent to live with your agent`});
					}
				} else {
					sexOptions.push({text: `${SlaveFullName(lover)} is unavailable`});
				}
			}
			if (slave.rivalryTarget !== 0 && hasAllLimbs(slave)) {
				const rival = getSlave(slave.relationshipTarget);
				if (isSlaveAvailable(rival) && hasAnyLegs(rival)) {
					sexOptions.push({text: `Abuse ${his} rival with ${him}`, scene: `FRival`});
				}
			}
			if (slave.fetish !== "mindbroken" && (canTalk(slave) || hasAnyArms(slave))) {
				sexOptions.push({text: `Ask ${him} about ${his} feelings`, scene: `FFeelings`});
				if (V.PC.dick > 0) {
					sexOptions.push({text: `Make ${him} beg`, scene: `FBeg`});
				}
			}
			if (slave.devotion >= 100 && slave.relationship < 0 && slave.relationship > -3) {
				sexOptions.push({text: `Talk to ${him} about relationships`, goto: `Matchmaking`, update: {subSlave: 0}});
			}
			let ML = V.marrying.length;
			if ((V.policies.mixedMarriage === 1 || V.cheatMode === 1) && slave.relationship !== 5 && slave.relationship !== -3) {
				if (V.marrying.includes(slave.ID)) {
					sexOptions.push({text: `Marry ${him}`, disabled: `You are already marrying ${him} this weekend`});
				} else {
					if (ML < 2) {
						if (V.cheatMode === 1 || ML === 0) {
							sexOptions.push({text: `Marry ${him}`, goto: "FMarry"});
						} else {
							sexOptions.push({text: `Marry ${him}`, disabled: `You already have a wedding planned for this weekend`});
						}
					} else {
						sexOptions.push({text: `Marry ${him}`, disabled: `You can only marry up to two slaves per week`});
					}
				}
			}
			if (V.cheatMode === 1) {
				sexOptions.push({text: `Check ${his} stats`, goto: `Slave Stats`});
			}
		} else {
			/* IS A FUCKDOLL */
			sexOptions.push({text: `Fuck ${his} face hole`, scene: `FFuckdollOral`});
			if (canDoVaginal(slave)) {
				sexOptions.push({text: `Fuck ${his} front hole`, scene: `FFuckdollVaginal`});
			}
			if (canGetPregnant(slave) && (slave.geneticQuirks.superfetation !== 2 || V.geneticMappingUpgrade !== 0) && V.seePreg !== 0) {
				if (canImpreg(slave, V.PC)) {
					sexOptions.push({text: `Put a baby in ${him}`, scene: `FFuckdollImpreg`});
				}
			}
			if (canDoAnal(slave)) {
				sexOptions.push({text: `Fuck ${his} rear hole`, scene: `FFuckdollAnal`});
			}
		}
		let activeSlaveRepSacrifice = repGainSacrifice(slave, V.arcologies[0]);
		if (activeSlaveRepSacrifice > 0 && V.arcologies[0].FSPaternalist === "unset" && (slave.breedingMark === 0 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset")) {
			sexOptions.push({
				text: `Sacrifice ${him} on the altar`,
				goto: `Aztec Slave Sacrifice`,
				note: `This will kill ${him} and gain you ${activeSlaveRepSacrifice} reputation`,
				update: {sacrificeType: "life"}
			});
		}
		el.append(`Use ${him}: `);
		el.appendChild(generateRows(sexOptions));
		if (!jQuery.isEmptyObject(fillFaceOptions)) {
			let fill = document.createElement('div');
			fill.appendChild(document.createTextNode(` Fill ${his} mouth with: `));
			fill.appendChild(generateRows(fillFaceOptions));
			el.appendChild(fill);
		}
		if (!jQuery.isEmptyObject(fillAssOptions)) {
			let fill = document.createElement('div');
			fill.appendChild(document.createTextNode(` Fill ${his} ass with: `));
			fill.appendChild(generateRows(fillAssOptions));
			el.appendChild(fill);
		}

		function generateRows(sexArray) {
			const linkArray = [];
			for (let i = 0; i < sexArray.length; i++) {
				let link;

				// is it just text?
				if (sexArray[i].disabled) {
					link = App.UI.DOM.disabledLink(sexArray[i].text, [sexArray[i].disabled]);
				} else {
					let passage = "";
					if (sexArray[i].goto) {
						passage = sexArray[i].goto;
					}

					// Set up the link
					link = App.UI.DOM.link(
						sexArray[i].text,
						() => { click(sexArray[i]); },
						[],
						passage
					);

					// add a note node if required
					if (sexArray[i].note) {
						link.appendChild(App.UI.DOM.makeElement('span', sexArray[i].note, 'note'));
					}
				}
				linkArray.push(link);
			}

			return App.UI.DOM.generateLinksStrip(linkArray);

			function click(sexOption) {
				if (sexOption.updateSlave) {
					Object.assign(slave, sexOption.updateSlave);
				}
				if (sexOption.update) {
					Object.assign(V, sexOption.update);
				}

				if (sexOption.goto) {
					// just play the passage, no need to refresh anything here
					Engine.play(sexOption.goto);
				} else if (sexOption.scene) {
					// Run scene and store render results temporarily
					let frag = App.UI.DOM.renderPassage(sexOption.scene);

					// Refresh (clears scene display)
					refresh();

					// Display scene render results
					$("#mini-scene").append(frag);
				} else {
					// just refresh
					refresh();
				}
			}
		}
		return el;
	}

	function fucktoyPref() {
		const el = document.createElement('div');
		let links = [];

		function appendLink(text, toyHole, enabled, disabledText) {
			const link = {text: text};
			if (enabled) {
				link.toyHole = toyHole;
			} else {
				link.disabled = disabledText;
			}
			links.push(link);
		}

		if ((slave.assignment === App.Data.Facilities.penthouse.jobs.fucktoy.assignment) || (slave.assignment === App.Data.Facilities.masterSuite.jobs.fucktoy.assignment) || (slave.assignment === App.Data.Facilities.masterSuite.manager.assignment)) {
			App.UI.DOM.appendNewElement("span", el, "Fucktoy use preference:", "story-label");
			el.append(` `);

			const hole = App.UI.DOM.appendNewElement('span', el, `${slave.toyHole}. `);
			hole.style.fontWeight = "bold";

			appendLink('Mouth', 'mouth', true);
			appendLink('Tits', 'boobs', true);
			if (slave.vagina >= 0) {
				appendLink('Pussy', 'pussy', slave.vagina > 0 && canDoVaginal(slave), `Take ${his} virginity before giving ${his} pussy special attention`);
			}
			appendLink('Ass', 'ass', (slave.anus > 0) && canDoAnal(slave), `Take ${his} anal virginity before giving ${his} ass special attention`);
			if (slave.dick > 0 && canPenetrate(slave)) {
				appendLink('Dick', 'dick', true);
			}
			appendLink('No Preference', "all her holes", true);
		}

		function generateLink(linkDesc) {
			// is it just text?
			if (linkDesc.disabled) { return App.UI.DOM.disabledLink(linkDesc.text, [linkDesc.disabled]); }
			// Are they already on this toyHole?
			if (linkDesc.toyHole === slave.toyHole) { return document.createTextNode(linkDesc.text); }
			// Set up the link
			const link = App.UI.DOM.link(
				linkDesc.text,
				() => {
					slave.toyHole = linkDesc.toyHole;
					jQuery('#fucktoy-pref').empty().append(fucktoyPref());
				},
			);

			// add a note node if required
			if (linkDesc.note) {
				App.UI.DOM.appendNewElement("span", link, linkDesc.note, "note");
			}
			return link;
		}

		el.appendChild(App.UI.DOM.generateLinksStrip(links.map(generateLink)));

		return el;
	}

	function refresh() {
		jQuery("#content-work").empty().append(App.UI.SlaveInteract.work(slave));
	}
};


code:
App.UI.SlaveInteract.mainPage = function(slave) {
	const el = new DocumentFragment();
	App.UI.tabBar.handlePreSelectedTab(V.tabChoice.SlaveInteract);

	if (!assignmentVisible(slave)) {
		switch (slave.assignment) {
			case "work in the brothel":
			case "be the Madam":
				V.nextLink = "Brothel";
				break;
			case "be confined in the arcade":
				V.nextLink = "Arcade";
				break;
			case "serve in the club":
			case "be the DJ":
				V.nextLink = "Club";
				break;
			case "work in the dairy":
			case "be the Milkmaid":
				V.nextLink = "Dairy";
				break;
			case "work as a farmhand":
			case "be the Farmer":
				V.nextLink = "Farmyard";
				break;
			case "rest in the spa":
			case "be the Attendant":
				V.nextLink = "Spa";
				break;
			case "work as a nanny":
			case "be the Matron":
				V.nextLink = "Nursery";
				break;
			case "learn in the schoolroom":
			case "be the Schoolteacher":
				V.nextLink = "Schoolroom";
				break;
			case "work as a servant":
			case "be the Stewardess":
				V.nextLink = "Servants' Quarters";
				break;
			case "serve in the master suite":
			case "be your Concubine":
				V.nextLink = "Master Suite";
				break;
			case "be confined in the cellblock":
			case "be the Wardeness":
				V.nextLink = "Cellblock";
				break;
			case "get treatment in the clinic":
			case "be the Nurse":
				V.nextLink = "Clinic";
				break;
			case "live with your Head Girl":
				V.nextLink = "Head Girl Suite";
				break;
			case "be your agent":
			case "live with your agent":
				V.nextLink = "Neighbor Interact";
		}
	}

	V.encyclopedia = either("Costs Summary", "Disease in the Free Cities", "Drugs and Their Effects", "From Rebellious to Devoted", "Gender", "Independent Slaves", "Modern Anal", "Nymphomania", "Slave Couture");
	if (slave.dick > 0) {
		V.encyclopedia = "Gender";
	}
	App.Utils.scheduleSidebarRefresh();

	/**
	 * @typedef {Object} siCategory
	 * @property {string} title
	 * @property {string} id
	 * @property {Node} node
	 * @property {Function} [onClick]
	 */

	 /** @type {Array<siCategory>} */
	const buttons = [
		{
			title: "Description",
			id: "description",
			get node() { return App.UI.SlaveInteract.description(slave); }
		},
		{
			title: "Modify",
			id: "modify",
			get node() { return App.UI.SlaveInteract.modify(slave); }
		},
		{
			title: "Work",
			id: "work",
			get node() { return App.UI.SlaveInteract.work(slave); }
		},
		{
			title: "Appearance",
			id: "appearance",
			get node() { return App.UI.SlaveInteract.wardrobe(slave); }
		},
		{
			title: "Physical Regimen",
			id: "physical-regimen",
			get node() { return App.UI.SlaveInteract.physicalRegimen(slave); }
		},
		{
			title: "Rules",
			id: "rules",
			get node() { return App.UI.SlaveInteract.rules(slave); }
		},
		{
			title: "Financial",
			id: "financial",
			get node() { return App.UI.SlaveInteract.financial(slave); }
		},
		{
			title: "Customize",
			id: "customize",
			get node() { return App.UI.SlaveInteract.custom(slave); }
		},
		{
			title: "Family",
			id: "family-tab",
			onClick: () => {
				renderFamilyTree(V.slaves, slave.ID);
			},
			get node() { return App.UI.SlaveInteract.family(); }
		}
	];

	el.append(App.UI.SlaveInteract.navigation(slave));

	if (V.slaveInteractLongForm) {
		el.append(displayWithoutTabs());
	} else {
		el.append(displayWithTabs());
	}

	return el;

	function displayWithTabs() {
		const el = new DocumentFragment();

		const tabBar = document.createElement("div");
		tabBar.classList.add("tab-bar");
		const tabContents = new DocumentFragment();

		for (const item of buttons) {
			tabBar.append(makeTabButton(item));
			tabContents.append(makeTabContents(item));
		}

		el.append(tabBar);

		App.Events.drawEventArt(el, slave);

		el.append(tabContents);

		return el;

		function makeTabButton(item) {
			const btn = document.createElement("button");
			btn.className = "tab-links";
			btn.id = `tab ${item.id}`;
			btn.innerHTML = item.title;
			btn.onclick = (event) => {
				App.UI.tabBar.openTab(event, item.id);
				jQuery(`#content-${item.id}`).empty().append(item.node);
				if (item.hasOwnProperty("onClick")){
					item.onClick();
				}
			};

			return btn;
		}
	}

	function displayWithoutTabs() {
		const el = new DocumentFragment();
		App.Events.drawEventArt(el, slave);
		for (const item of buttons) {
			App.UI.DOM.appendNewElement("h2", el, item.title);
			el.append(item.node);
			if (item.hasOwnProperty("onClick")) {
				item.onClick();
			}
		}
		return el;
	}
	function makeTabContents(item) {
		const wrapperEl = document.createElement("div");
		wrapperEl.id = item.id;
		wrapperEl.classList.add("tab-content");

		const classEl = document.createElement("div");
		classEl.classList.add("content");

		const uniqueContent = document.createElement("span");
		uniqueContent.id = `content-${item.id}`;

		uniqueContent.append(item.node);
		classEl.append(uniqueContent);
		wrapperEl.append(classEl);

		return wrapperEl;
	}
};

repo_name: Welptard/fc

license: mit


code:
/**
 * Contains the parameters for the player's custom slave order.
 * Some members are ignored by huskSlave (particularly mental properties).
 * NOTE: $customSlave/$huskSlave IS NOT A SLAVE. Do not treat them like you would treat an instance of SlaveState.
 */
App.Entity.CustomSlaveOrder = class CustomSlaveOrder {
	constructor() {
		/** desired age, approximately
		 * Only certain values are valid; check seCustomSlaveDelivery.tw.
		 * @type {number}
		 */
		this.age = 19;

		/** desired health
		 * 0 = healthy, 1 = extremely healthy
		 * @type {number}
		 */
		this.health = 1;

		/** desired musculature
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.muscles = 0;

		/** desired lips
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.lips = 15;

		/** desired voice
		 * -1 = unimportant; other values as in SlaveState.
		 * @type {number}
		 */
		this.voice = -1;

		/** desired height category
		 * "greatly below average", "below average", "normal", "above average", or "greatly above average"
		 * @type {string}
		 */
		this.heightMod = "normal";

		/** desired weight
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.weight = 0;

		/** desired facial attractiveness
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.face = 0;

		/** desired race
		 * "ethnicity is unimportant" or other values as in SlaveState
		 * @type {string}
		 */
		this.race = "white";

		/** desired skin color
		 * "left natural" or other values as in SlaveState
		 * @type {string}
		 */
		this.skin = "left natural";

		/** desired boob size
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.boobs = 500;

		/** desired butt size
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.butt = 3;

		/** desired genetic sex and genitalia
		 * 1 = female (XX), 2 = male (XY), 3 = futanari (XX)
		 * @type {number}
		 */
		this.sex = 1;

		/** virginity required
		 * 0 = yes, 1 = no (and yes, you are reading that correctly)
		 * @type {number}
		 */
		this.virgin = 0;

		/** desired dick and foreskin size, male and futa only
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.dick = 2;

		/** desired balls and scrotum size, male and futa only
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.balls = 2;

		/** desired clit size, female and futa only
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.clit = 0;

		/** desired labia size, female and futa only
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.labia = 0;

		/** desired vaginal lubrication, female and futa only
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.vaginaLube = 1;

		/** anal virginity required
		 * 0 = yes, 1 = no (and yes, you are reading that correctly)
		 * @type {number}
		 */
		this.analVirgin = 0;

		/** desired sex skill rating (anal, oral, and vaginal)
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.skills = 15;

		/** desired skills rating (.whore controls both whoring and entertainment, .combat controls combat)
		 * Values as in SlaveState.
		 * @type {Object.<string, number>}
		 */
		this.skill = {whore: 15, combat: 0};

		/** desired intelligence level
		 * Range -3 to +3 (NOT the same range as SlaveState!)
		 * @type {number}
		 */
		this.intelligence = 0;

		/** desired education
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.intelligenceImplant = 0;

		/** desired nationality
		 * "Nationality is unimportant" or other values as in SlaveState.
		 * @type {string}
		 */
		this.nationality = "Stateless";

		/** desired left and right leg state
		 * @type {{left: App.Entity.LimbState, right: App.Entity.LimbState}}
		 */
		this.leg = {left: new App.Entity.LimbState(), right: new App.Entity.LimbState()};

		/** desired left and right arm state
		 * @type {{left: App.Entity.LimbState, right: App.Entity.LimbState}}
		 */
		this.arm = {left: new App.Entity.LimbState(), right: new App.Entity.LimbState()};

		/** desired eye state
		 * Only the "vision" property is used, all others are ignored
		 * @type {App.Entity.EyeState}
		 */
		this.eye = new App.Entity.EyeState();

		/** hearing
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.hears = 0;

		/** sense of smell
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.smells = 0;

		/** sense of taste
		 * Values as in SlaveState.
		 * @type {number}
		 */
		this.tastes = 0;
	}
};

/**
 * Checks to see if the custom slave has accidentally been turned into an actual slave object.
 * IF THIS FUNCTION RETURNS TRUE, YOU HAVE PROBABLY FUCKED UP A BACKCOMPAT. CHANGE YOUR BACKCOMPAT, NOT THIS FUNCTION!
 * @param {App.Entity.CustomSlaveOrder} slaveOrder
 * @returns {boolean}
 */
App.Utils.IsCustomSlaveMutated = function(slaveOrder) {
	/* ensure the absence of a few properties that slave orders definitely should not have, but every real slave has */
	if (slaveOrder.hasOwnProperty("rules") || slaveOrder.hasOwnProperty("pronoun")) {
		return true;
	/* ensure the presence of a few properties that slave orders definitely should have, but no real slave should */
	} else if (!slaveOrder.hasOwnProperty("age") || !slaveOrder.hasOwnProperty("sex")) {
		return true;
	}
	return false;
};