code:
/**
 * STANDARD EVENTS
 * @param {App.Entity.SlaveState} eventSlave
 */
globalThis.generateRandomEventPoolStandard = function(eventSlave) {
	if (eventSlave.fetish !== "mindbroken") {
		if (hasAnyArms(eventSlave) && hasAnyLegs(eventSlave)) {
			if (canTalk(eventSlave)) {
				if (V.RECockmilkInterceptionIDs.length > 1 || (V.RECockmilkInterceptionIDs.length === 1 && eventSlave.ID !== V.RECockmilkInterceptionIDs[0])) {
					if (eventSlave.devotion > 20) {
						if (eventSlave.dietCum > 0 || (eventSlave.fetishKnown && eventSlave.fetish === "cumslut")) {
							V.RETSevent.push("cockmilk interception");
						}
					}
				}

				if (V.REInterslaveBeggingIDs.length > 1 || (V.REInterslaveBeggingIDs.length === 1 && eventSlave.ID !== V.REInterslaveBeggingIDs[0])) {
					if (V.universalRulesConsent === 1) {
						if (eventSlave.devotion > 20) {
							if (eventSlave.energy > 40) {
								if (canPenetrate(eventSlave)) {
									if (canSee(eventSlave)) {
										if (eventSlave.rules.release.slaves === 1) {
											V.RETSevent.push("interslave begging");
										}
									}
								}
							}
						}
					}
				}

				if (V.seeIncest === 1) {
					if (eventSlave.devotion > 20) {
						if (eventSlave.sexualQuirk === "perverted" || eventSlave.energy > 80) {
							if (eventSlave.belly < 100000) {
								if (eventSlave.lactation > 0) {
									if (eventSlave.nipples !== "fuckable") {
										if (eventSlave.rules.release.family === 1) {
											if (eventSlave.daughters > 0) {
												V.RETSevent.push("incestuous nursing");
											}
										}
									}
								}
							}
						}
					}
				}

				if (eventSlave.skill.entertainment >= 60 || ["a ballerina", "a camgirl", "a camwhore", "a cheerleader", "a classical dancer", "a dancer", "a house DJ", "a party girl", "an aspiring pop star", "an exotic dancer", "an idol"].includes(eventSlave.career)) {
					if (canHear(eventSlave)) {
						if (eventSlave.health.condition > 40) {
							if (eventSlave.devotion > 50) {
								if (eventSlave.trust > 50) {
									V.RESSevent.push("happy dance");
								}
							}
						}
					}
				}

				if (V.RERepressedAnalVirginSubIDs.length > 0) {
					if (eventSlave.anus === 0) {
						if (eventSlave.devotion >= -50) {
							if (eventSlave.trust >= -50) {
								if (eventSlave.sexualFlaw === "repressed") {
									V.RETSevent.push("repressed anal virgin");
								}
							}
						}
					}
				}

				if (V.assistant > 0) {
					if (V.assistant.appearance !== "normal") {
						if (eventSlave.devotion >= -20) {
							if (canSee(eventSlave)) {
								if (eventSlave.devotion <= 50) {
									if (eventSlave.assignment === Job.HOUSE) {
										if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
											V.RESSevent.push("PA servant");
										}
									}
								} else if (eventSlave.trust > 75) {
									if (eventSlave.energy > 60) {
										if ((canDoAnal(eventSlave) && eventSlave.anus > 0) || (canDoVaginal(eventSlave) && eventSlave.vagina > 0)) {
											V.RESSevent.push("PA flirting");
										}
									}
								}
							}
						}
					}
				}

				if (eventSlave.clothes === "a succubus outfit") {
					if (eventSlave.devotion > 20) {
						if (eventSlave.trust > 20) {
							V.RESSevent.push("sexy succubus");
						}
					}
				}

				if (V.HeadGirlID !== 0) {
					if (eventSlave.devotion <= 50) {
						if (eventSlave.anus !== 0 && canDoAnal(eventSlave)) {
							if (V.HGSeverity >= 0) {
								V.events.push("RE anal punishment");
							}
							V.events.push("RE shower punishment");
						}
					}
					if (eventSlave.ID === V.HeadGirlID) {
						if (eventSlave.trust > 50) {
							V.RESSevent.push("trusting HG");
						}
					}
					if (eventSlave.ID !== V.HeadGirlID) {
						if (canSee(eventSlave) && canWalk(eventSlave)) {
							if (eventSlave.rules.speech !== "restrictive") {
								if (eventSlave.trust > 75) {
									if (eventSlave.devotion > 50) {
										if (eventSlave.skill.oral > 30) {
											if (eventSlave.intelligence + eventSlave.intelligenceImplant >= S.HeadGirl.intelligence + S.HeadGirl.intelligenceImplant) {
												if (eventSlave.skill.oral > S.HeadGirl.skill.oral) {
													V.events.push("RE HG replacement");
												}
											}
										}
									}
								}
							}
						}
					}
				}

				if (eventSlave.rules.living === "spare") {
					if (eventSlave.devotion <= 20) {
						if (eventSlave.devotion > -10) {
							V.RESSevent.push("sleeping ambivalent");
						}
						if (eventSlave.trust < -20) {
							V.RESSevent.push("bad dream");
						}
					}
				}

				if (eventSlave.devotion <= 50) {
					if (eventSlave.devotion >= -20) {
						if (eventSlave.weekAcquired > 0) {
							if (V.week - eventSlave.weekAcquired < 10) {
								if (eventSlave.energy > 20) {
									if (eventSlave.anus !== 0) {
										if (eventSlave.vagina !== 0) {
											V.RESSevent.push("ignorant horny");
										}
									}
								}
							}
						}
					}
				}

				if (V.RETasteTestSubIDs.length > 1) {
					if (eventSlave.rules.living === "luxurious") {
						if (eventSlave.devotion > 20) {
							if (eventSlave.energy > 80) {
								if (canTaste(eventSlave)) {
									V.RETSevent.push("taste test");
								}
							}
						}
					}
				}

				if (V.modRequestsAllowed > 0) {
					if (eventSlave.trust > 50) {
						if (eventSlave.devotion > 20) {
							if (eventSlave.dick !== 0 || eventSlave.vagina !== -1) {
								if (eventSlave.nipples !== "fuckable") {
									if (eventSlave.rules.speech === "permissive") {
										if (eventSlave.corsetPiercing === 0) {
											if (eventSlave.tonguePiercing === 0) {
												if (eventSlave.anusPiercing === 0) {
													if (eventSlave.nipplesPiercing === 0) {
														if (eventSlave.dickPiercing === 0) {
															if (eventSlave.vaginaPiercing === 0) {
																if (eventSlave.lipsTat === 0 || eventSlave.lipsTat === "none") {
																	if (eventSlave.vaginaTat === 0 || eventSlave.vaginaTat === "none") {
																		V.RESSevent.push("mods please");
																	}
																}
															}
														}
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}

				if (eventSlave.muscles > 5) {
					if (eventSlave.devotion > 20) {
						if (eventSlave.diet !== "slimming") {
							V.RESSevent.push("sore shoulders");
						}
					}
				}

				if (eventSlave.relationship > 3) {
					let relationshipSlave = V.slaves.find(s => s.ID === eventSlave.relationshipTarget);
					if (relationshipSlave.devotion > 20) {
						if (canWalk(relationshipSlave)) {
							if (canTalk(relationshipSlave)) {
								if (eventSlave.devotion > 50) {
									if (eventSlave.trust > 50) {
										if (eventSlave.rules.speech !== "restrictive") {
											V.RETSevent.push("date please");
										}
										if (relationshipSlave.anus !== 0) {
											if (relationshipSlave.dick !== 1) {
												if (relationshipSlave.dick === 0 || canAchieveErection(relationshipSlave)) {
													if (relationshipSlave.fetish === "dom" || relationshipSlave.fetish === "sadist") {
														V.RETSevent.push("top exhaustion");
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}

				if (V.REAnalCowgirlSubIDs.length > 1 || (V.REAnalCowgirlSubIDs.length === 1 && eventSlave.ID !== V.REAnalCowgirlSubIDs[0])) {
					if (eventSlave.devotion > 20) {
						if (eventSlave.rules.release.slaves === 1) {
							if (eventSlave.fetish === "buttslut" || eventSlave.fetish === "sadist" || eventSlave.fetish === "dom") {
								if ((eventSlave.chastityPenis !== 1) || (eventSlave.dick === 0)) {
									V.RETSevent.push("anal cowgirl");
								}
							}
						}
					}
				}

				if (V.REBoobCollisionSubIDs.length > 1) {
					if (eventSlave.devotion > 20) {
						if (eventSlave.trust > 20) {
							if (eventSlave.boobs > 3000) {
								if (eventSlave.attrXX >= 50 || (eventSlave.fetish === "boobs" && eventSlave.fetishStrength > 95)) {
									V.RETSevent.push("boob collision");
								}
							}
						}
					}
				}

				if (V.weatherToday.severity <= 1) {
					if (eventSlave.trust > 50) {
						if (eventSlave.devotion > 20) {
							if (eventSlave.anus !== 0 && canDoAnal(eventSlave)) {
								if (eventSlave.vagina !== 0) {
									V.RESSevent.push("confident tanning");
								}
							}
						}
					}
				}

				if (eventSlave.devotion > 50) {
					if (eventSlave.trust > 50) {
						let giver = 0;
						if (V.HeadGirlID === 0) {
							giver = 1;
						} else if (V.HeadGirlID !== eventSlave.ID) {
							giver = 1;
						}
						if (giver === 1) {
							V.RESSevent.push("a gift");
						}
					}
				}

				if (eventSlave.relationship >= 2) {
					if (eventSlave.relationship < 5) {
						if (eventSlave.devotion > 20) {
							if (eventSlave.trust >= -20) {
								V.events.push("RE relationship advice");
							}
						}
					}
				}

				if (eventSlave.devotion > 50) {
					if (eventSlave.anus > 0) {
						if (eventSlave.vagina !== 0) {
							if (eventSlave.trust > 20) {
								V.RESSevent.push("devoted exhibition");
								V.RESSevent.push("devoted lotion");
							}
							if (eventSlave.energy > 95 && canDoAnal(eventSlave)) {
								V.RESSevent.push("devoted nympho");
							}
							if (eventSlave.rules.release.masturbation === 1) {
								if (hasBothArms(eventSlave)) {
									if ((eventSlave.chastityPenis !== 1) || (eventSlave.dick === 0)) {
										V.RESSevent.push("permitted masturbation");
									}
								}
							}
						}
					}
				}

				if (eventSlave.trust < -50) {
					if (eventSlave.devotion <= 20) {
						V.RESSevent.push("terrified inspection");
					}
				}

				/*
				if(eventSlave.drugs === "breast injections") {
					if(eventSlave.anus > 0 || eventSlave.vagina > 0) {
						if(eventSlave.devotion <= 50) {
							if(eventSlave.devotion >= -20) {
								if(eventSlave.trust >= -50) {
									if(V.REReductionCheckinIDs.includes(eventSlave.ID)) {
										V.RECIevent.push("reduction");
									}
								}
							}
						}
					}
				}
				*/

				if (eventSlave.assignment === Job.HOUSE) {
					if (eventSlave.devotion <= 95) {
						if (eventSlave.intelligence + eventSlave.intelligenceImplant < -50) {
							V.RESSevent.push("cooler lockin");
						}
					}
				}

				if (V.universalRulesConsent === 0) {
					if (eventSlave.devotion > 50) {
						if (eventSlave.fetish === "sadist" || eventSlave.fetish === "dom" || eventSlave.energy > 95) {
							if (V.REShowerForceSubIDs.length > 0) {
								if (eventSlave.trust <= 75) {
									V.RETSevent.push("shower force");
								}
							}
							if (eventSlave.anus > 0) {
								if (V.RESadisticDescriptionSubIDs.length > 0) {
									V.RETSevent.push("sadistic description");
								}
							}
						}
						if (V.REIfYouEnjoyItSubIDs.length > 0) {
							V.RETSevent.push("if you enjoy it");
						}
					}
				}

				if (eventSlave.devotion < -50) {
					if (eventSlave.trust >= -20) {
						if (eventSlave.rules.speech === "restrictive") {
							V.RESSevent.push("vocal disobedience");
						}
					}
				}

				if (V.boobAccessibility !== 1 && V.ballsAccessibility !== 1 && V.pregAccessibility !== 1) {
					if (eventSlave.boobs > 10000) {
						if (eventSlave.devotion >= -20) {
							if (V.slaves.length > 2) {
								if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
									V.RESSevent.push("huge tits");
								}
							}
						}
					}
				}

				if (eventSlave.physicalAge > 30) {
					if (eventSlave.ageImplant > 0) {
						if (eventSlave.devotion > 20) {
							V.RESSevent.push("age implant");
						}
					}
				}

				if (V.seeAge !== 0) {
					if (eventSlave.devotion > 50) {
						if (eventSlave.rules.speech !== "restrictive") {
							if (eventSlave.birthWeek >= 51) {
								/* let's give this a much higher chance of appearing */
								V.RESSevent.push("birthday");
								V.RESSevent.push("birthday");
								V.RESSevent.push("birthday");
								V.RESSevent.push("birthday");
								V.RESSevent.push("birthday");
								V.RESSevent.push("birthday");
								V.RESSevent.push("birthday");
								V.RESSevent.push("birthday");
							}
						}
					}
				}

				if (eventSlave.clothes === "a penitent nuns habit") {
					if (eventSlave.anus > 0) {
						if (eventSlave.devotion >= -20) {
							if (eventSlave.devotion <= 50) {
								V.RESSevent.push("penitent");
							}
						}
					}
				}

				if (eventSlave.slaveName !== eventSlave.birthName && eventSlave.birthName !== "") {
					if (eventSlave.devotion <= 20) {
						if (eventSlave.trust >= -20) {
							if (eventSlave.anus > 0 && canDoAnal(eventSlave)) {
								V.RESSevent.push("not my name");
							}
						}
					}
				}

				if (V.slaves.length > 2) {
					if (eventSlave.devotion >= -20) {
						if (eventSlave.heels === 1) {
							if (eventSlave.shoes === "heels" || eventSlave.shoes === "boots" || eventSlave.shoes === "extreme heels") {
								V.RESSevent.push("tendon fall");
							}
						}
					}
				}

				if (eventSlave.devotion > 50) {
					if (eventSlave.trust > 20) {
						if (eventSlave.physicalAge > 37) {
							if (eventSlave.anus > 0) {
								if (eventSlave.vagina > 0) {
									V.RESSevent.push("devoted old");
								}
							}
						}
					}
				}

				if (eventSlave.devotion > 20) {
					if (eventSlave.trust > 20) {
						if (eventSlave.actualAge > 35) {
							if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
								if (V.PC.actualAge < 25) {
									V.RESSevent.push("young PC age difference");
								}
							}
						}
					}
				}

				if (eventSlave.devotion > 20) {
					if (eventSlave.trust > 20) {
						if (eventSlave.actualAge < 22) {
							if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
								if (V.PC.actualAge >= 50) {
									V.RESSevent.push("old PC age difference");
								}
							}
						}
					}
				}

				if (eventSlave.fetish === "humiliation" || eventSlave.energy > 95) {
					if (eventSlave.devotion <= 50) {
						if (eventSlave.devotion >= -20) {
							if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
								if (eventSlave.vagina !== 0) {
									if (eventSlave.anus !== 0) {
										if (eventSlave.fetishKnown === 1) {
											V.RESSevent.push("fearful humiliation");
										}
									}
								}
							}
						}
					}
				}

				if (eventSlave.rules.living === "luxurious") {
					if (eventSlave.devotion <= 75) {
						if (eventSlave.devotion > 50) {
							V.RESSevent.push("newly devoted sunrise");
						}
					}
				}

				if (V.spa > 0) {
					if (eventSlave.boobs > 2000) {
						if (eventSlave.devotion > 20) {
							if (eventSlave.trust > 20) {
								if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
									V.RESSevent.push("spa boobs");
								}
							}
						}
					}
				}

				if (eventSlave.lactation > 0) {
					if (eventSlave.boobs > 800) {
						if (eventSlave.nipples !== "fuckable") {
							if (eventSlave.devotion > 20) {
								if (eventSlave.trust > 20) {
									V.RESSevent.push("cow milking");
								}
							}
						}
					}
				}

				if (V.arcologies[0].FSSubjugationist !== "unset") {
					if (eventSlave.race !== V.arcologies[0].FSSubjugationistRace) {
						if (V.week - eventSlave.weekAcquired > 1) {
							if (V.week - eventSlave.weekAcquired < 10) {
								if (eventSlave.devotion < -20) {
									if (eventSlave.trust >= -20) {
										if (eventSlave.health.condition > -20) {
											if (eventSlave.race === eventSlave.origRace) {
												if (eventSlave.indentureRestrictions < 1) {
													V.RESSevent.push("subjugation blues");
												}
											}
										}
									}
								}
							}
						}
					}
				}

				if (eventSlave.drugs === "intensive breast injections" || eventSlave.drugs === "hyper breast injections") {
					if (eventSlave.boobs <= 8000) {
						if (eventSlave.boobs > 2000) {
							if (eventSlave.devotion <= 50) {
								if (eventSlave.devotion >= -50) {
									if (eventSlave.trust >= -50) {
										if (eventSlave.fetish !== "boobs") {
											if (eventSlave.intelligence + eventSlave.intelligenceImplant >= -50) {
												V.RESSevent.push("breast expansion blues");
											}
										}
									}
								}
							}
						}
					}
				}

				if (eventSlave.physicalAge > 35) {
					if (eventSlave.rules.speech !== "restrictive") {
						if ([Job.PUBLIC, Job.WHORE].includes(eventSlave.assignment)) {
							if (eventSlave.devotion >= -20) {
								if (eventSlave.devotion <= 95) {
									V.RESSevent.push("ara ara");
								}
							}
						}
					}
				}

				if (eventSlave.anus > 3) {
					if (eventSlave.devotion > 50) {
						if (eventSlave.trust > 50) {
							V.RESSevent.push("gaped asshole");
						}
					}
				}

				if (eventSlave.fetish === "sadist") {
					if (eventSlave.fetishStrength > 20) {
						if (App.Entity.facilities.arcade.established) {
							if (eventSlave.trust >= -20) {
								if (eventSlave.devotion > 50) {
									if (eventSlave.belly < 300000) {
										V.RESSevent.push("arcade sadist");
									}
								}
							}
						}
					}
				}

				if (eventSlave.dick === 0) {
					if (eventSlave.vagina === -1) {
						if (canDoAnal(eventSlave)) {
							if (eventSlave.energy > 20) {
								if (eventSlave.devotion >= -50) {
									if (eventSlave.fetish !== "buttslut" || eventSlave.fetishStrength <= 20) {
										if (eventSlave.nipples !== "fuckable") {
											V.RESSevent.push("desperate null");
										}
									}
								}
							}
						}
					}
				}

				if (eventSlave.devotion > 20) {
					if (eventSlave.boobs > 2000) {
						V.RESSevent.push("back stretch");
					}
				}

				if (eventSlave.devotion > 20) {
					if (eventSlave.trust > 20) {
						if (eventSlave.rules.speech !== "restrictive") {
							if (eventSlave.choosesOwnClothes !== 1) {
								if (setup.modestClothes.includes(eventSlave.clothes)) {
									V.RESSevent.push("modest clothes");
								}
							}
						}
					}
				}

				if (V.PC.vagina > -1) {
					if (eventSlave.devotion <= 20) {
						if ((eventSlave.trust >= -20 && eventSlave.behavioralFlaw === "arrogant") || (eventSlave.trust > 20)) {
							if (eventSlave.muscles > 30) {
								if (eventSlave.health.condition > 20) {
									if (eventSlave.energy > 50) {
										if (canPenetrate(eventSlave)) {
											if (V.BodyguardID === 0) {
												V.RESSevent.push("mutinery attempt");
											}
										}
									}
								}
							}
						}
					}
				}
			} /* closes mute exempt */

			if (eventSlave.devotion > 20) {
				if (eventSlave.mouthAccessory !== "none") {
					V.RESSevent.push("gagged slave");
				}
			}

			if (eventSlave.devotion <= 20) {
				if (eventSlave.trust <= -20) {
					if (eventSlave.genes === "XY") {
						if (eventSlave.dick > 0) {
							if (eventSlave.chastityPenis !== 1) {
								if (eventSlave.vagina === -1) {
									if (eventSlave.attrXY < 50) {
										V.RESSevent.push("transition anxiety");
									}
								}
							}
						}
					}
				}
			}

			if (V.seeAge === 1) {
				if (eventSlave.actualAge < 18) {
					if (eventSlave.devotion > 20) {
						if (eventSlave.ovaries === 1) {
							if (eventSlave.pubertyXX === 0) {
								if (eventSlave.preg === 0) {
									if (eventSlave.physicalAge + eventSlave.birthWeek / 52 >= eventSlave.pubertyAgeXX - 0.5) {
										V.RESSevent.push(...Array.from({length: 10}, () => "first period"));
									}
								}
							}
						}
						if (canPenetrate(eventSlave)) {
							if (eventSlave.balls > 0) {
								if (eventSlave.pubertyXY === 0) {
									if (eventSlave.physicalAge + eventSlave.birthWeek / 52 >= eventSlave.pubertyAgeXY - 0.5) {
										V.RESSevent.push(...Array.from({length: 10}, () => "wet dreams"));
									}
								}
							}
						}
					}
				}
			} /* closes aging exemption */

			if (eventSlave.devotion >= -150) {
				if (eventSlave.devotion <= 50) {
					V.events.push("RE standard punishment");
				}
			}

			if (eventSlave.boobs > 600) {
				if (eventSlave.boobShape === "torpedo-shaped") {
					if (eventSlave.devotion >= -50) {
						V.RESSevent.push("torpedo squeeze");
					}
				}
			}

			if (eventSlave.assignment === Job.PUBLIC) {
				if (eventSlave.fetishKnown === 1) {
					if (V.arcologies[0].FSDegradationist === "unset") {
						if (V.arcologyUpgrade.drones === 1) {
							V.RESSevent.push("mean girls");
						}
					}
				}
			}

			if (eventSlave.balls > 0) {
				if (eventSlave.hormoneBalance >= 100) {
					if (V.hormoneUpgradeMood === 0) {
						if (eventSlave.trust >= -50) {
							if (eventSlave.devotion >= -20) {
								if (eventSlave.devotion <= 50) {
									V.RESSevent.push("orchiectomy please");
								}
							}
						}
					}
				}
			}

			if (eventSlave.trust > 20) {
				if (eventSlave.devotion > 50) {
					if (V.PC.belly < 5000) {
						V.RESSevent.push("shower slip");
					}
					if (getLimbCount(eventSlave, 102) === 4) {
						V.RESSevent.push("plimb help");
					}
				}
			}

			if (eventSlave.relationship <= -2) {
				if (eventSlave.trust > 50) {
					if (eventSlave.devotion > 50) {
						V.RESSevent.push("bonded love");
					}
				}
			}

			if (eventSlave.trust > 20) {
				if (eventSlave.devotion > 50) {
					V.RESSevent.push("objectifying visit");
				}
			}

			if (canWalk(eventSlave)) {
				if (eventSlave.devotion > 20) {
					if (eventSlave.trust > -10) {
						if (eventSlave.ID !== V.HeadGirlID) {
							if (eventSlave.ID !== V.ConcubineID) {
								V.RESSevent.push("refreshment delivery");
							}
						}
					}
				}
			}

			if (eventSlave.dick > 8) {
				if (eventSlave.balls > 0) {
					if (eventSlave.energy > 60) {
						if (eventSlave.devotion > 50) {
							if (eventSlave.trust > 50) {
								if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
									if (eventSlave.belly < 100000) {
										V.RESSevent.push("dick wringing");
									}
								}
							}
						}
					}
				}
			}

			if (eventSlave.balls > 0) {
				if (eventSlave.scrotum > 0) {
					if (eventSlave.chastityPenis === 1) {
						if (eventSlave.devotion <= 95) {
							if (eventSlave.energy > 50) {
								if (eventSlave.energy < 95) {
									if (eventSlave.devotion >= -20 || eventSlave.trust < -20) {
										V.RESSevent.push("cage relief");
									}
								}
							}
						}
					}
				}
			}

			if (V.PC.dick > 0) {
				if (V.PC.boobs >= 300) {
					if (canSee(eventSlave)) {
						if (eventSlave.devotion <= 50) {
							if (eventSlave.devotion >= -20) {
								if (((eventSlave.attrXY <= 35) && (eventSlave.attrXX > 65)) || ((eventSlave.attrXX <= 35) && (eventSlave.attrXY > 65))) {
									V.RESSevent.push("dickgirl PC");
								}
							}
						}
					}
				}
			}

			if (eventSlave.voice > 0) {
				if (eventSlave.lips <= 95) {
					if (eventSlave.mouthAccessory === "none") {
						if (eventSlave.accent === 3) {
							if (eventSlave.devotion <= 50) {
								if (eventSlave.trust > -10) {
									if (eventSlave.devotion >= -20 || eventSlave.trust < -20) {
										V.RESSevent.push("language lesson");
									}
								}
							}
						}
					}
				}
			}

			if (eventSlave.trust <= 20) {
				if (eventSlave.trust >= -75) {
					if (eventSlave.devotion <= 30) {
						if (eventSlave.devotion >= -20) {
							V.RESSevent.push("like me");
						}
					}
				}
			}

			if (eventSlave.boobs >= 2000) {
				if (eventSlave.boobsImplant === 0) {
					if (eventSlave.nipples !== "tiny" && eventSlave.nipples !== "fuckable") {
						if (eventSlave.devotion > 20) {
							V.RESSevent.push("huge naturals");
						}
					}
				}
			}

			if (eventSlave.boobs > 800) {
				if (Math.floor(eventSlave.boobsImplant / eventSlave.boobs) >= 0.60) {
					if (eventSlave.devotion > 20) {
						V.RESSevent.push("implant inspection");
					}
				}
			}

			if (eventSlave.devotion < -20) {
				if (eventSlave.trust >= -20) {
					if (eventSlave.clothes === "uncomfortable straps") {
						V.RESSevent.push("bondage gear");
					}
				}
			}

			if (eventSlave.labia > 1 && eventSlave.vagina >= 0) {
				if (eventSlave.muscles > 5 || eventSlave.diet === "muscle building") {
					if (eventSlave.belly < 10000) {
						if (eventSlave.devotion > 20) {
							if (eventSlave.trust > 20) {
								V.RESSevent.push("inconvenient labia");
							}
						}
					}
				}
			}

			if (eventSlave.devotion <= 50) {
				if (eventSlave.trust >= -50) {
					if (eventSlave.behavioralFlaw === "gluttonous") {
						if (eventSlave.diet === "restricted") {
							V.RESSevent.push("diet");
						}
					}
				}
			}

			if (eventSlave.devotion <= 20) {
				if (eventSlave.devotion >= -50) {
					V.RESSevent.push("resistant shower");
				}
			}

			if (eventSlave.rules.speech !== "restrictive") {
				if (eventSlave.devotion >= -20) {
					if (eventSlave.trust >= -20) {
						if (eventSlave.energy > 75) {
							if (eventSlave.fetish !== "buttslut") {
								if (eventSlave.vagina !== 0 || eventSlave.anus !== 0) {
									V.RESSevent.push("night visit");
								}
							}
						}
					}
				}
			}

			if (eventSlave.rules.release.masturbation === 0 && !App.Utils.hasNonassignmentSex(eventSlave)) {
				if (eventSlave.need) {
					if (eventSlave.devotion <= 95) {
						if (eventSlave.trust >= -20) {
							if ((eventSlave.chastityPenis !== 1) || (eventSlave.dick === 0)) {
								V.RESSevent.push("forbidden masturbation");
							}
						}
					}
					if (eventSlave.devotion >= -20) {
						if (eventSlave.trust >= -50) {
							V.RESSevent.push("desperately horny");
						}
					}
				}
			}

			if (eventSlave.devotion >= -20) {
				if (eventSlave.lactation > 1) {
					if (canDoVaginal(eventSlave) || canDoAnal(eventSlave)) {
						V.RESSevent.push("milkgasm");
					}
				}
			}

			if (eventSlave.skill.entertainment >= 100) {
				if (eventSlave.trust > 50) {
					if (eventSlave.assignment === Job.PUBLIC) {
						V.RESSevent.push("masterful entertainer");
					}
				}
			}

			if (eventSlave.skill.whoring >= 100) {
				if (eventSlave.trust > 50) {
					if (eventSlave.assignment === Job.WHORE) {
						V.RESSevent.push("masterful whore");
					}
				}
			}

			if (eventSlave.dick > 0) {
				if (eventSlave.balls === 0) {
					if (eventSlave.genes === "XY") {
						if (eventSlave.devotion <= 50) {
							if (eventSlave.trust < -50) {
								if (eventSlave.anus > 0) {
									if (canDoAnal(eventSlave)) {
										V.RESSevent.push("resistant gelding");
									}
								}
							}
						}
					}
				}
			}

			if (V.seePreg !== 0) {
				if (isFertile(eventSlave)) {
					if (eventSlave.devotion > 50) {
						if (V.PC.dick !== 0) {
							if (eventSlave.fetish === "pregnancy" || eventSlave.energy > 95) {
								if (eventSlave.eggType === "human") {
									if (eventSlave.fetishKnown === 1) {
										if (eventSlave.vagina !== 0) {
											if (eventSlave.anus > 0) {
												if (eventSlave.geneticQuirks.superfetation === 2 && (eventSlave.intelligence + eventSlave.intelligenceImplant > 15) && eventSlave.womb.length > 0) {
													if (eventSlave.belly < (eventSlave.pregAdaptation * 1750)) {
														V.RESSevent.push("impregnation please");
													}
												} else {
													V.RESSevent.push("impregnation please");
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}

			if (["huge plug", "large plug", "long, huge plug", "long, large plug"].includes(eventSlave.buttplug)) {
				if (eventSlave.assignment !== Job.CONFINEMENT && isSlaveAvailable(eventSlave)) {
					if (eventSlave.devotion <= 20) {
						if (eventSlave.trust >= -50) {
							if (eventSlave.anus < 3) {
								V.RESSevent.push("plug disobedience");
							}
						}
					}
				}
			}

			if ([Job.PUBLIC, Job.WHORE].includes(eventSlave.assignment)) {
				if (eventSlave.vagina !== 0) {
					if (eventSlave.anus !== 0) {
						if (canDoAnal(eventSlave)) {
							if (eventSlave.devotion <= 50) {
								V.RESSevent.push("used whore");
							}
						}
					}
				}
			}

			if (eventSlave.assignment === Job.PUBLIC) {
				if (eventSlave.devotion >= -20) {
					if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
						V.RESSevent.push("nice guys");
					}
				}
			}
		} /* closes amp/crawling exempt */

		if (canTalk(eventSlave)) {
			if (eventSlave.rules.speech !== "restrictive") {
				if (eventSlave.boobsImplant > 400) {
					if (eventSlave.lipsImplant >= 30) {
						if (eventSlave.buttImplant > 3) {
							if (eventSlave.boobs < 9000 || eventSlave.butt < 8) {
								V.RESSevent.push("surgery addict");
							}
						}
					}
				}
			}

			if ([Job.CONCUBINE, Job.FUCKTOY, Job.MASTERSUITE].includes(eventSlave.assignment)) {
				if (eventSlave.devotion > 20) {
					if (eventSlave.trust >= -20) {
						if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
							if ((eventSlave.chastityPenis !== 1) || (eventSlave.dick === 0)) {
								if (V.corp.Incorporated !== 0) {
									V.RESSevent.push("shift sleep");
								}
								if (canWalk(eventSlave)) {
									if (eventSlave.rules.release.masturbation === 1) {
										V.RESSevent.push("shift masturbation");
									}
								}
								if (eventSlave.skill.entertainment >= 60) {
									V.RESSevent.push("shift doorframe");
								}
							}
							if (canDoVaginal(eventSlave)) {
								if (V.PC.vagina > -1) {
									if (eventSlave.dick === 0) {
										if (V.fuckSlaves > 2) {
											V.RESSevent.push("fucktoy tribbing");
										}
									}
								}
							}
						}
					}
				}
			}

			if (eventSlave.rules.speech !== "restrictive") {
				if (eventSlave.counter.oral + eventSlave.counter.vaginal + eventSlave.counter.anal > 900) {
					if (eventSlave.counter.oral + eventSlave.counter.vaginal + eventSlave.counter.anal < 1100) {
						if (eventSlave.vagina !== 0) {
							if (eventSlave.anus > 0) {
								if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
									V.RESSevent.push("millenary");
								}
							}
						}
					}
				}
			}

			if (eventSlave.assignment === Job.CONFINEMENT) {
				if (eventSlave.devotion <= 50) {
					V.RESSevent.push("solitary desperation");
				}
			}

			if (eventSlave.rules.speech === "restrictive") {
				if (eventSlave.devotion > 60) {
					if (eventSlave.trust >= -20) {
						V.RESSevent.push("restricted profession");
					}
				}
			}

			if (eventSlave.rules.speech === "restrictive") {
				if (eventSlave.intelligence > 15) {
					if (eventSlave.trust >= -20) {
						if (eventSlave.devotion <= 20) {
							V.RESSevent.push("restricted smart");
						}
					}
				}
			}

			if (V.expansionRequestsAllowed > 0) {
				if (eventSlave.drugs === "no drugs") {
					if (eventSlave.rules.speech === "permissive") {
						if (eventSlave.health.condition > 20) {
							if (eventSlave.devotion >= -20) {
								if (eventSlave.trust > 20) {
									if (V.arcologies[0].FSSlimnessEnthusiast === "unset") {
										V.RESSevent.push("injections please");
									}
								}
							}
						}
					}
				}
			}
		} /* closes mute exempt */

		if (eventSlave.boobs > 25000) {
			if (eventSlave.belly < 100000) {
				V.RESSevent.push("tittymonster inspection");
			}
		}

		if (V.assistant > 1) {
			if (eventSlave.fetishKnown === 1) {
				if (eventSlave.energy > 95) {
					if (eventSlave.devotion > 20) {
						if (eventSlave.rules.release.masturbation === 1) {
							V.RESSevent.push("nympho with assistant");
						}
					}
				}
			}
		}

		if (eventSlave.devotion > 50) {
			if (eventSlave.trust > 50) {
				if ([Job.CONCUBINE, Job.FUCKTOY, Job.MASTERSUITE].includes(eventSlave.assignment)) {
					V.RESSevent.push("bed snuggle");
				}
			}
		}

		if (eventSlave.minorInjury === "sore ass") {
			if (eventSlave.devotion <= 50) {
				V.RESSevent.push("sore ass");
			}
		}

		if (eventSlave.sexualFlaw === "hates oral") {
			if (V.PC.dick !== 0) {
				if (eventSlave.devotion <= 50) {
					V.RESSevent.push("hates oral");
				}
			}
		}

		if (V.seeExtreme === 1) {
			if (eventSlave.balls > 1) {
				if (eventSlave.scrotum > 0) {
					if (eventSlave.devotion <= 20) {
						if (eventSlave.trust <= 20) {
							if (eventSlave.rules.speech !== "restrictive") {
								V.RESSevent.push("fearful balls");
							}
						}
					}
				}
			}
		}

		if (eventSlave.devotion <= 50) {
			if (eventSlave.assignment === Job.QUARTER || eventSlave.assignment === Job.HOUSE) {
				V.RESSevent.push("scrubbing");
			}
		}

		if (eventSlave.devotion <= 20) {
			if (eventSlave.assignment === Job.QUARTER || eventSlave.assignment === Job.HOUSE) {
				if (eventSlave.clothes === "a slutty maid outfit" || eventSlave.clothes === "a nice maid outfit") {
					V.RESSevent.push("servant maid");
				}
			}
		}

		if (eventSlave.rules.speech !== "restrictive") {
			if (canDoVaginal(eventSlave)) {
				if (eventSlave.fetish !== "buttslut") {
					if (eventSlave.vagina === 0) {
						if (eventSlave.anus !== 0) {
							if (eventSlave.counter.oral + eventSlave.counter.vaginal + eventSlave.counter.anal > 100) {
								V.RESSevent.push("unhappy virgin");
							}
						}
					}
				}
			}
		}

		if (eventSlave.aphrodisiacs > 1 || eventSlave.inflationType === "aphrodisiac") {
			if (eventSlave.rules.speech === "restrictive" && eventSlave.rules.release.master === 1 && App.Utils.releaseRestricted(eventSlave)) {
				V.RESSevent.push("extreme aphrodisiacs");
			}
		}

		if (eventSlave.rules.release.masturbation === 1) {
			if (eventSlave.dick > 4) {
				if (hasAnyArms(eventSlave)) {
					if (canAchieveErection(eventSlave)) {
						if (eventSlave.belly < 10000) {
							if (eventSlave.chastityPenis !== 1) {
								V.RESSevent.push("slave dick huge");
							}
						}
					}
				}
			}
		}

		if (eventSlave.heels === 1) {
			if (eventSlave.shoes === "heels" || eventSlave.shoes === "extreme heels") {
				if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
					V.RESSevent.push("heels");
				}
			}
		}

		if (eventSlave.rules.release.masturbation === 1) {
			if (eventSlave.belly < 300000) {
				if (eventSlave.anus > 2) {
					if (eventSlave.fetish === "buttslut" || eventSlave.energy > 95) {
						if (eventSlave.fetish !== "none") {
							if (canHold(eventSlave)) {
								if (canDoAnal(eventSlave)) {
									V.RESSevent.push("loose buttslut");
								}
							}
						}
					}
				}
			}
		}

		if (eventSlave.assignment === Job.WHORE) {
			if (canDoAnal(eventSlave) && (eventSlave.vagina < 0 || canDoVaginal(eventSlave))) {
				if (eventSlave.devotion < -20 && eventSlave.trust >= -20) {
					V.RESSevent.push("whore rebellious");
				}
			}
		}

		if (isAmputee(eventSlave)) {
			if (eventSlave.devotion > 20) {
				if (eventSlave.anus > 0 && canDoAnal(eventSlave)) {
					V.RESSevent.push("devoted amp");
				}
			}
			if (eventSlave.assignment === Job.REST) {
				V.RESSevent.push("resting amp");
			}
		}

		if (eventSlave.boobs > 1200) {
			if (eventSlave.areolaeShape !== "circle") {
				if (eventSlave.devotion > 50) {
					V.RESSevent.push("shaped areolae");
				}
			}
		}

		if (eventSlave.behavioralFlaw === "arrogant") {
			if (eventSlave.devotion < -50) {
				if (eventSlave.trust >= -50) {
					V.RESSevent.push("rebellious arrogant");
				}
			}
		}

		if (V.seePreg !== 0) {
			if (eventSlave.bellyPreg >= 10000) {
				V.RESSevent.push("hugely pregnant");
			}
		}

		if (eventSlave.hormoneBalance >= 50) {
			if (eventSlave.vagina === -1) {
				if (eventSlave.balls >= 0) {
					if (eventSlave.devotion > 20 || eventSlave.trust < -20) {
						if (eventSlave.devotion <= 50) {
							if (eventSlave.fetish !== "buttslut") {
								if (eventSlave.rules.speech === "permissive") {
									V.RESSevent.push("hormone dysfunction");
								}
							}
						}
					}
				}
			}
		}

		if (eventSlave.vaginaPiercing > 1) {
			if (eventSlave.nipplesPiercing > 1) {
				if (eventSlave.clitPiercing > 1) {
					if (eventSlave.devotion > 20 || eventSlave.trust < -20) {
						if (eventSlave.devotion <= 50) {
							if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
								V.RESSevent.push("heavy piercing");
							}
						}
					}
				}
			}
		}

		if (eventSlave.fetishKnown === 1) {
			if (eventSlave.fetish === "cumslut" || eventSlave.energy > 95) {
				if ([Job.PUBLIC, Job.WHORE, Job.GLORYHOLE].includes(eventSlave.assignment)) {
					if (eventSlave.devotion > 20) {
						if (V.PC.dick !== 0) {
							V.RESSevent.push("cumslut whore");
						}
					}
				}
			}
		}

		if (eventSlave.anus === 0) {
			if (eventSlave.devotion < -20) {
				if (eventSlave.trust >= -20) {
					V.RESSevent.push("resistant anal virgin");
				}
			}
		}

		if (V.PC.dick !== 0) {
			if (eventSlave.bellyPreg >= 300000) {
				V.RESSevent.push("hyperpreg stuck");
			}
		}

		if (eventSlave.devotion >= 50) {
			if (eventSlave.trust <= 20) {
				if (canWalk(eventSlave)) {
					V.RESSevent.push("devoted fearful slave");
				}
			}
		}

		if (eventSlave.prestige === 0) {
			if (eventSlave.devotion > 50) {
				if (eventSlave.trust > 50) {
					if (eventSlave.skill.entertainment >= 100) {
						if (eventSlave.assignment === Job.PUBLIC) {
							V.events.push("RE legendary entertainer");
						}
					}

					if (eventSlave.skill.whoring >= 100) {
						if (eventSlave.assignment === Job.WHORE) {
							V.events.push("RE legendary whore");
						}
					}

					if (eventSlave.lactation > 0) {
						if ((eventSlave.boobs - eventSlave.boobsImplant) > 6000) {
							if (eventSlave.assignment === Job.MILKED) {
								V.events.push("RE legendary cow");
							}
						}
					}

					if (eventSlave.bellyPreg >= 14000) {
						if (eventSlave.broodmother === 0) {
							if (eventSlave.counter.births > 10) {
								if (eventSlave.assignment === Job.WHORE || eventSlave.assignment === Job.PUBLIC) {
									if (!isAmputee(eventSlave)) {
										V.events.push("RE legendary womb");
									}
								}
							}
						}
					}

					if (eventSlave.balls > 6) {
						if (eventSlave.dick > 6) {
							if (eventSlave.assignment === Job.MILKED) {
								V.events.push("RE legendary balls");
							}
						}
					}

					if (eventSlave.origin === "You sentenced $him to enslavement as a punishment for attempted theft of a slave." || eventSlave.origin === "$He is an enslaved Daughter of Liberty." || eventSlave.origin === "You got $him at the Slave Shelter. $He is an enslaved Daughter of Liberty, caught some weeks after the failed coup. $His previous owner used $him as a punching bag and dart board, then when he was bored of $him tattooed obscenities all over $his body and threw $him away." || eventSlave.origin === "$He is an enslaved member of an anti-slavery extremist group." || eventSlave.career === "an antislavery activist") {
						if (eventSlave.devotion > 95 && eventSlave.assignment === Job.FUCKTOY) {
							V.events.push("RE former abolitionist");
						}
					}
				}
			}
		}

		if (eventSlave.relationship === 4) {
			if (eventSlave.devotion > 20) {
				if (eventSlave.trust > 20) {
					V.events.push("RE slave marriage");
				}
			}
		}

		if (V.eventResults.snatch === 0 && eventSlave.origin === "$He is your share of a raid on an illegal laboratory by your mercenaries." && eventSlave.newGamePlus === 0) {
			if (V.geneticMappingUpgrade === 2 && eventSlave.health.condition > 25 && eventSlave.devotion > 50 && V.cash > V.surgeryCost*4) {
				/* only one slave ever qualifies, so give it a bit more weight */
				V.events.push("RE snatch and grab followup");
				V.events.push("RE snatch and grab followup");
				V.events.push("RE snatch and grab followup");
			}
		}

		/* NICKNAME EVENTS */

		if (V.nicknamesAllowed === 1) {
			let toSearch = eventSlave.slaveName.toLowerCase();
			if (!toSearch.includes("'")) {
				if (V.week - eventSlave.weekAcquired >= 4) {
					V.events.push("RE nickname");
					V.seed = 0;
				}
			} /* closes nickname check */
		} /* closes no nicknames option */
	} /* closes mindbreak exempt */

	if (eventSlave.fetish === "mindbroken") {
		if (canWalk(eventSlave)) {
			V.RESSevent.push("mindbroken morning");
		}
		if (eventSlave.kindness !== undefined && eventSlave.kindness >= 100) {
			if ((isSlaveAvailable(eventSlave) && canWalk(eventSlave)) || ([Job.CONCUBINE, Job.FUCKTOY, Job.MASTERSUITE].includes(eventSlave.assignment))) {
				if (eventSlave.relationship === -3) {
					if (jsRandom(1, 200) < eventSlave.kindness || V.cheatMode) {
						V.RESSevent.push("surprising wakeup");
					}
				}
			}
		}
		if (eventSlave.career === "a breeding bull" && canImpreg(V.PC, eventSlave) && eventSlave.assignment === Job.MASTERSUITE && canPenetrate(eventSlave) && eventSlave.dick >= 5 && hasAllLimbs(eventSlave) && V.PC.vagina > -1 && V.PC.preg === 0 && (eventSlave.muscles > 30 || eventSlave.weight > 30)) {
			V.RESSevent.push("breeding bull");
			if (V.PC.fertDrugs > 0) {
				V.RESSevent.push("breeding bull");
				V.RESSevent.push("breeding bull");
			}
			if (V.PC.forcedFertDrugs > 0) {
				V.RESSevent.push("breeding bull");
				V.RESSevent.push("breeding bull");
			}
		}
	}
};

/**
 * servants spend a lot of time in the penthouse, so should be eligible for a number (but not all) random events
 * @param {App.Entity.SlaveState} eventSlave
 */
globalThis.generateRandomEventPoolServant = function(eventSlave) {
	/* STANDARD EVENTS */

	if (eventSlave.fetish !== "mindbroken") {
		if (hasAnyArms(eventSlave) && hasAnyLegs(eventSlave)) {
			if (canTalk(eventSlave)) {
				if (V.RECockmilkInterceptionIDs.length > 1 || (V.RECockmilkInterceptionIDs.length === 1 && eventSlave.ID !== V.RECockmilkInterceptionIDs[0])) {
					if (eventSlave.devotion > 20) {
						if (eventSlave.dietCum > 0 || (eventSlave.fetishKnown && eventSlave.fetish === "cumslut")) {
							V.RETSevent.push("cockmilk interception");
						}
					}
				}

				if (V.REInterslaveBeggingIDs.length > 1 || (V.REInterslaveBeggingIDs.length === 1 && eventSlave.ID !== V.REInterslaveBeggingIDs[0])) {
					if (V.universalRulesConsent === 1) {
						if (eventSlave.devotion > 20) {
							if (eventSlave.energy > 40) {
								if (canPenetrate(eventSlave)) {
									if (canSee(eventSlave)) {
										if (eventSlave.rules.release.slaves === 1) {
											V.RETSevent.push("interslave begging");
										}
									}
								}
							}
						}
					}
				}

				if (V.seeIncest === 1) {
					if (eventSlave.devotion > 20) {
						if (eventSlave.sexualQuirk === "perverted" || eventSlave.energy > 80) {
							if (eventSlave.belly < 100000) {
								if (eventSlave.lactation > 0) {
									if (eventSlave.nipples !== "fuckable") {
										if (eventSlave.rules.release.family === 1) {
											if (eventSlave.daughters > 0) {
												V.RETSevent.push("incestuous nursing");
											}
										}
									}
								}
							}
						}
					}
				}

				if (V.assistant > 0) {
					if (V.assistant.appearance !== "normal") {
						if (eventSlave.devotion >= -20) {
							if (canSee(eventSlave)) {
								if (eventSlave.devotion <= 50) {
									if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
										V.RESSevent.push("PA servant");
									}
								}
							}
						}
					}
				}

				if (V.HeadGirlID !== 0) {
					if (eventSlave.devotion <= 50) {
						if (eventSlave.anus !== 0 && canDoAnal(eventSlave)) {
							if (V.HGSeverity >= 0) {
								V.events.push("RE anal punishment");
							}
							V.events.push("RE shower punishment");
						}
					}
				}

				if (V.RETasteTestSubIDs.length > 1) {
					if (eventSlave.rules.living === "luxurious") {
						if (eventSlave.devotion > 20) {
							if (eventSlave.energy > 80) {
								if (canTaste(eventSlave)) {
									V.RETSevent.push("taste test");
								}
							}
						}
					}
				}

				if (eventSlave.relationship > 3) {
					let relationshipSlave = V.slaves.find(s => s.ID === eventSlave.relationshipTarget);
					if (relationshipSlave.devotion > 20) {
						if (canWalk(relationshipSlave)) {
							if (canTalk(relationshipSlave)) {
								if (eventSlave.devotion > 50) {
									if (eventSlave.trust > 50) {
										if (relationshipSlave.anus !== 0) {
											if (relationshipSlave.dick !== 1) {
												if (relationshipSlave.dick === 0 || canAchieveErection(relationshipSlave)) {
													if (relationshipSlave.fetish === "dom" || relationshipSlave.fetish === "sadist") {
														V.RETSevent.push("top exhaustion");
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}

				if (V.weatherToday.severity <= 1) {
					if (eventSlave.trust > 50) {
						if (eventSlave.devotion > 20) {
							if (eventSlave.anus !== 0 && canDoAnal(eventSlave)) {
								if (eventSlave.vagina !== 0) {
									V.RESSevent.push("confident tanning");
								}
							}
						}
					}
				}

				if (eventSlave.devotion > 50) {
					if (eventSlave.trust > 50) {
						let giver = 0;
						if (V.HeadGirlID === 0) {
							giver = 1;
						} else if (V.HeadGirlID !== eventSlave.ID) {
							giver = 1;
						}
						if (giver === 1) {
							V.RESSevent.push("a gift");
						}
					}
				}

				if (eventSlave.devotion > 50) {
					if (eventSlave.anus > 0) {
						if (eventSlave.vagina !== 0) {
							if (eventSlave.trust > 20) {
								V.RESSevent.push("devoted lotion");
							}
						}
					}
				}

				if (eventSlave.devotion <= 95) {
					if (eventSlave.intelligence + eventSlave.intelligenceImplant < -50) {
						V.RESSevent.push("cooler lockin");
					}
				}

				if (V.universalRulesConsent === 0) {
					if (eventSlave.devotion > 50) {
						if (eventSlave.fetish === "sadist" || eventSlave.fetish === "dom" || eventSlave.energy > 95) {
							if (V.REShowerForceSubIDs.length > 0) {
								if (eventSlave.trust <= 75) {
									V.RETSevent.push("shower force");
								}
							}
							if (eventSlave.anus > 0) {
								if (V.RESadisticDescriptionSubIDs.length > 0) {
									V.RETSevent.push("sadistic description");
								}
							}
						}
						if (V.REIfYouEnjoyItSubIDs.length > 0) {
							V.RETSevent.push("if you enjoy it");
						}
					}
				}

				if (V.boobAccessibility !== 1 && V.ballsAccessibility !== 1 && V.pregAccessibility !== 1) {
					if (eventSlave.boobs > 10000) {
						if (eventSlave.devotion >= -20) {
							if (V.slaves.length > 2) {
								if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
									V.RESSevent.push("huge tits");
								}
							}
						}
					}
				}

				if (V.seeAge !== 0) {
					if (eventSlave.devotion > 50) {
						if (eventSlave.rules.speech !== "restrictive") {
							if (eventSlave.birthWeek >= 51) {
								/* let's give this a much higher chance of appearing */
								V.RESSevent.push("birthday");
								V.RESSevent.push("birthday");
								V.RESSevent.push("birthday");
								V.RESSevent.push("birthday");
								V.RESSevent.push("birthday");
								V.RESSevent.push("birthday");
								V.RESSevent.push("birthday");
								V.RESSevent.push("birthday");
							}
						}
					}
				}

				if (eventSlave.slaveName !== eventSlave.birthName && eventSlave.birthName !== "") {
					if (eventSlave.devotion <= 20) {
						if (eventSlave.trust >= -20) {
							if (eventSlave.anus > 0 && canDoAnal(eventSlave)) {
								V.RESSevent.push("not my name");
							}
						}
					}
				}

				if (V.slaves.length > 2) {
					if (eventSlave.devotion >= -20) {
						if (eventSlave.heels === 1) {
							if (eventSlave.shoes === "heels" || eventSlave.shoes === "boots" || eventSlave.shoes === "extreme heels") {
								V.RESSevent.push("tendon fall");
							}
						}
					}
				}

				if (eventSlave.devotion > 50) {
					if (eventSlave.trust > 20) {
						if (eventSlave.physicalAge > 37) {
							if (eventSlave.anus > 0) {
								if (eventSlave.vagina > 0) {
									V.RESSevent.push("devoted old");
								}
							}
						}
					}
				}

				if (eventSlave.devotion > 20) {
					if (eventSlave.trust > 20) {
						if (eventSlave.actualAge > 35) {
							if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
								if (V.PC.actualAge < 25) {
									V.RESSevent.push("young PC age difference");
								}
							}
						}
					}
				}

				if (eventSlave.devotion > 20) {
					if (eventSlave.trust > 20) {
						if (eventSlave.actualAge < 22) {
							if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
								if (V.PC.actualAge >= 50) {
									V.RESSevent.push("old PC age difference");
								}
							}
						}
					}
				}

				if (eventSlave.fetish === "humiliation" || eventSlave.energy > 95) {
					if (eventSlave.devotion <= 50) {
						if (eventSlave.devotion >= -20) {
							if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
								if (eventSlave.vagina !== 0) {
									if (eventSlave.anus !== 0) {
										if (eventSlave.fetishKnown === 1) {
											V.RESSevent.push("fearful humiliation");
										}
									}
								}
							}
						}
					}
				}

				if (eventSlave.rules.living === "luxurious") {
					if (eventSlave.devotion <= 75) {
						if (eventSlave.devotion > 50) {
							V.RESSevent.push("newly devoted sunrise");
						}
					}
				}

				if (V.spa > 0) {
					if (eventSlave.boobs > 2000) {
						if (eventSlave.devotion > 20) {
							if (eventSlave.trust > 20) {
								if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
									V.RESSevent.push("spa boobs");
								}
							}
						}
					}
				}

				if (eventSlave.lactation > 0) {
					if (eventSlave.boobs > 800) {
						if (eventSlave.nipples !== "fuckable") {
							if (eventSlave.devotion > 20) {
								if (eventSlave.trust > 20) {
									V.RESSevent.push("cow milking");
								}
							}
						}
					}
				}

				if (V.arcologies[0].FSSubjugationist !== "unset") {
					if (eventSlave.race !== V.arcologies[0].FSSubjugationistRace) {
						if (V.week - eventSlave.weekAcquired > 1) {
							if (V.week - eventSlave.weekAcquired < 10) {
								if (eventSlave.devotion < -20) {
									if (eventSlave.trust >= -20) {
										if (eventSlave.health.condition > -20) {
											if (eventSlave.race === eventSlave.origRace) {
												if (eventSlave.indentureRestrictions < 1) {
													V.RESSevent.push("subjugation blues");
												}
											}
										}
									}
								}
							}
						}
					}
				}

				if (eventSlave.fetish === "sadist") {
					if (eventSlave.fetishStrength > 20) {
						if (App.Entity.facilities.arcade.established) {
							if (eventSlave.trust >= -20) {
								if (eventSlave.devotion > 50) {
									if (eventSlave.belly < 300000) {
										V.RESSevent.push("arcade sadist");
									}
								}
							}
						}
					}
				}

				if (V.PC.vagina > -1) {
					if (eventSlave.devotion <= 20) {
						if ((eventSlave.trust >= -20 && eventSlave.behavioralFlaw === "arrogant") || (eventSlave.trust > 20)) {
							if (eventSlave.muscles > 30) {
								if (eventSlave.health.condition > 20) {
									if (eventSlave.energy > 50) {
										if (canPenetrate(eventSlave)) {
											if (V.BodyguardID === 0) {
												V.RESSevent.push("mutinery attempt");
											}
										}
									}
								}
							}
						}
					}
				}
			} /* closes mute exempt */

			if (eventSlave.devotion > 20) {
				if (eventSlave.mouthAccessory !== "none") {
					V.RESSevent.push("gagged slave");
				}
			}

			if (V.seeAge === 1) {
				if (eventSlave.actualAge < 18) {
					if (eventSlave.devotion > 20) {
						if (eventSlave.ovaries === 1) {
							if (eventSlave.pubertyXX === 0) {
								if (eventSlave.preg === 0) {
									if (eventSlave.physicalAge + eventSlave.birthWeek / 52 >= eventSlave.pubertyAgeXX - 0.5) {
										V.RESSevent.push(...Array.from({length: 10}, () => "first period"));
									}
								}
							}
						}
						if (canPenetrate(eventSlave)) {
							if (eventSlave.balls > 0) {
								if (eventSlave.pubertyXY === 0) {
									if (eventSlave.physicalAge + eventSlave.birthWeek / 52 >= eventSlave.pubertyAgeXY - 0.5) {
										V.RESSevent.push(...Array.from({length: 10}, () => "wet dreams"));
									}
								}
							}
						}
					}
				}
			} /* closes aging exemption */

			if (eventSlave.devotion >= -150) {
				if (eventSlave.devotion <= 50) {
					V.events.push("RE standard punishment");
				}
			}

			if (eventSlave.trust > 20) {
				if (eventSlave.devotion > 50) {
					if (V.PC.belly < 5000) {
						V.RESSevent.push("shower slip");
					}
					if (getLimbCount(eventSlave, 102) === 4) {
						V.RESSevent.push("plimb help");
					}
				}
			}

			if (eventSlave.relationship <= -2) {
				if (eventSlave.trust > 50) {
					if (eventSlave.devotion > 50) {
						V.RESSevent.push("bonded love");
					}
				}
			}

			if (eventSlave.trust > 20) {
				if (eventSlave.devotion > 50) {
					V.RESSevent.push("objectifying visit");
				}
			}

			if (canWalk(eventSlave)) {
				if (eventSlave.devotion > 20) {
					if (eventSlave.trust > -10) {
						if (eventSlave.ID !== V.HeadGirlID) {
							if (eventSlave.ID !== V.ConcubineID) {
								V.RESSevent.push("refreshment delivery");
							}
						}
					}
				}
			}

			if (eventSlave.dick > 8) {
				if (eventSlave.balls > 0) {
					if (eventSlave.energy > 60) {
						if (eventSlave.devotion > 50) {
							if (eventSlave.trust > 50) {
								if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
									if (eventSlave.belly < 100000) {
										V.RESSevent.push("dick wringing");
									}
								}
							}
						}
					}
				}
			}

			if (eventSlave.balls > 0) {
				if (eventSlave.scrotum > 0) {
					if (eventSlave.chastityPenis === 1) {
						if (eventSlave.devotion <= 95) {
							if (eventSlave.energy > 50) {
								if (eventSlave.energy < 95) {
									if (eventSlave.devotion >= -20 || eventSlave.trust < -20) {
										V.RESSevent.push("cage relief");
									}
								}
							}
						}
					}
				}
			}

			if (V.PC.dick > 0) {
				if (V.PC.boobs >= 300) {
					if (canSee(eventSlave)) {
						if (eventSlave.devotion <= 50) {
							if (eventSlave.devotion >= -20) {
								if (((eventSlave.attrXY <= 35) && (eventSlave.attrXX > 65)) || ((eventSlave.attrXX <= 35) && (eventSlave.attrXY > 65))) {
									V.RESSevent.push("dickgirl PC");
								}
							}
						}
					}
				}
			}

			if (eventSlave.trust <= 20) {
				if (eventSlave.trust >= -75) {
					if (eventSlave.devotion <= 30) {
						if (eventSlave.devotion >= -20) {
							V.RESSevent.push("like me");
						}
					}
				}
			}

			if (eventSlave.devotion < -20) {
				if (eventSlave.trust >= -20) {
					if (eventSlave.clothes === "uncomfortable straps") {
						V.RESSevent.push("bondage gear");
					}
				}
			}

			if (eventSlave.labia > 1 && eventSlave.vagina >= 0) {
				if (eventSlave.muscles > 5 || eventSlave.diet === "muscle building") {
					if (eventSlave.belly < 10000) {
						if (eventSlave.devotion > 20) {
							if (eventSlave.trust > 20) {
								V.RESSevent.push("inconvenient labia");
							}
						}
					}
				}
			}

			if (eventSlave.devotion <= 50) {
				if (eventSlave.trust >= -50) {
					if (eventSlave.behavioralFlaw === "gluttonous") {
						if (eventSlave.diet === "restricted") {
							V.RESSevent.push("diet");
						}
					}
				}
			}

			if (eventSlave.devotion <= 20) {
				if (eventSlave.devotion >= -50) {
					V.RESSevent.push("resistant shower");
				}
			}

			if (eventSlave.rules.speech !== "restrictive") {
				if (eventSlave.devotion >= -20) {
					if (eventSlave.trust >= -20) {
						if (eventSlave.energy > 75) {
							if (eventSlave.fetish !== "buttslut") {
								if (eventSlave.vagina !== 0 || eventSlave.anus !== 0) {
									V.RESSevent.push("night visit");
								}
							}
						}
					}
				}
			}

			if (eventSlave.rules.release.masturbation === 0) {
				if (eventSlave.need) {
					if (eventSlave.devotion <= 95) {
						if (eventSlave.trust >= -20) {
							if ((eventSlave.chastityPenis !== 1) || (eventSlave.dick === 0)) {
								V.RESSevent.push("forbidden masturbation");
							}
						}
					}
					if (eventSlave.devotion >= -20) {
						if (eventSlave.trust >= -50) {
							V.RESSevent.push("desperately horny");
						}
					}
				}
			}

			if (eventSlave.devotion >= -20) {
				if (eventSlave.lactation > 1) {
					if (canDoVaginal(eventSlave) || canDoAnal(eventSlave)) {
						V.RESSevent.push("milkgasm");
					}
				}
			}

			if (eventSlave.dick > 0) {
				if (eventSlave.balls === 0) {
					if (eventSlave.genes === "XY") {
						if (eventSlave.devotion <= 50) {
							if (eventSlave.trust < -50) {
								if (eventSlave.anus > 0) {
									if (canDoAnal(eventSlave)) {
										V.RESSevent.push("resistant gelding");
									}
								}
							}
						}
					}
				}
			}

			if (V.seePreg !== 0) {
				if (isFertile(eventSlave)) {
					if (eventSlave.devotion > 50) {
						if (V.PC.dick !== 0) {
							if (eventSlave.fetish === "pregnancy" || eventSlave.energy > 95) {
								if (eventSlave.eggType === "human") {
									if (eventSlave.fetishKnown === 1) {
										if (eventSlave.vagina !== 0) {
											if (eventSlave.anus > 0) {
												if (eventSlave.geneticQuirks.superfetation === 2 && (eventSlave.intelligence + eventSlave.intelligenceImplant > 15) && eventSlave.womb.length > 0) {
													if (eventSlave.belly < (eventSlave.pregAdaptation * 1750)) {
														V.RESSevent.push("impregnation please");
													}
												} else {
													V.RESSevent.push("impregnation please");
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}

			if (["huge plug", "large plug", "long, huge plug", "long, large plug"].includes(eventSlave.buttplug)) {
				if (isSlaveAvailable(eventSlave)) {
					if (eventSlave.devotion <= 20) {
						if (eventSlave.trust >= -50) {
							if (eventSlave.anus < 3) {
								V.RESSevent.push("plug disobedience");
							}
						}
					}
				}
			}
		} /* closes amp/crawling exempt */

		if (canTalk(eventSlave)) {
			if (eventSlave.rules.speech !== "restrictive") {
				if (eventSlave.boobsImplant > 400) {
					if (eventSlave.lipsImplant >= 30) {
						if (eventSlave.buttImplant > 3) {
							if (eventSlave.boobs < 9000 || eventSlave.butt < 8) {
								V.RESSevent.push("surgery addict");
							}
						}
					}
				}
			}

			if (eventSlave.rules.speech !== "restrictive") {
				if (eventSlave.counter.oral + eventSlave.counter.vaginal + eventSlave.counter.anal > 900) {
					if (eventSlave.counter.oral + eventSlave.counter.vaginal + eventSlave.counter.anal < 1100) {
						if (eventSlave.vagina !== 0) {
							if (eventSlave.anus > 0) {
								if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
									V.RESSevent.push("millenary");
								}
							}
						}
					}
				}
			}

			if (V.expansionRequestsAllowed > 0) {
				if (eventSlave.drugs === "no drugs") {
					if (eventSlave.rules.speech === "permissive") {
						if (eventSlave.health.condition > 20) {
							if (eventSlave.devotion >= -20) {
								if (eventSlave.trust > 20) {
									if (V.arcologies[0].FSSlimnessEnthusiast === "unset") {
										V.RESSevent.push("injections please");
									}
								}
							}
						}
					}
				}
			}
		} /* closes mute exempt */

		if (V.assistant > 1) {
			if (eventSlave.fetishKnown === 1) {
				if (eventSlave.energy > 95) {
					if (eventSlave.devotion > 20) {
						if (eventSlave.rules.release.masturbation === 1) {
							V.RESSevent.push("nympho with assistant");
						}
					}
				}
			}
		}

		if (eventSlave.minorInjury === "sore ass") {
			if (eventSlave.devotion <= 50) {
				V.RESSevent.push("sore ass");
			}
		}

		if (eventSlave.sexualFlaw === "hates oral") {
			if (V.PC.dick !== 0) {
				if (eventSlave.devotion <= 50) {
					V.RESSevent.push("hates oral");
				}
			}
		}

		if (eventSlave.devotion <= 50) {
			V.RESSevent.push("scrubbing");
		}

		if (eventSlave.devotion <= 20) {
			if (eventSlave.clothes === "a slutty maid outfit" || eventSlave.clothes === "a nice maid outfit") {
				V.RESSevent.push("servant maid");
			}
		}

		if (eventSlave.aphrodisiacs > 1 || eventSlave.inflationType === "aphrodisiac") {
			if (eventSlave.rules.speech === "restrictive" && eventSlave.rules.release.master === 1 && App.Utils.releaseRestricted(eventSlave)) {
				V.RESSevent.push("extreme aphrodisiacs");
			}
		}

		if (eventSlave.rules.release.masturbation === 1) {
			if (eventSlave.dick > 4) {
				if (hasAnyArms(eventSlave)) {
					if (canAchieveErection(eventSlave)) {
						if (eventSlave.belly < 10000) {
							if (eventSlave.chastityPenis !== 1) {
								V.RESSevent.push("slave dick huge");
							}
						}
					}
				}
			}
		}

		if (eventSlave.heels === 1) {
			if (eventSlave.shoes === "heels" || eventSlave.shoes === "extreme heels") {
				if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
					V.RESSevent.push("heels");
				}
			}
		}

		if (eventSlave.rules.release.masturbation === 1) {
			if (eventSlave.belly < 300000) {
				if (eventSlave.anus > 2) {
					if (eventSlave.fetish === "buttslut" || eventSlave.energy > 95) {
						if (eventSlave.fetish !== "none") {
							if (canHold(eventSlave)) {
								if (canDoAnal(eventSlave)) {
									V.RESSevent.push("loose buttslut");
								}
							}
						}
					}
				}
			}
		}

		if (eventSlave.boobs > 1200) {
			if (eventSlave.areolaeShape !== "circle") {
				if (eventSlave.devotion > 50) {
					V.RESSevent.push("shaped areolae");
				}
			}
		}

		if (V.seePreg !== 0) {
			if (eventSlave.bellyPreg >= 10000) {
				V.RESSevent.push("hugely pregnant");
			}
		}

		if (eventSlave.hormoneBalance >= 50) {
			if (eventSlave.vagina === -1) {
				if (eventSlave.balls >= 0) {
					if (eventSlave.devotion > 20 || eventSlave.trust < -20) {
						if (eventSlave.devotion <= 50) {
							if (eventSlave.fetish !== "buttslut") {
								if (eventSlave.rules.speech === "permissive") {
									V.RESSevent.push("hormone dysfunction");
								}
							}
						}
					}
				}
			}
		}

		if (eventSlave.vaginaPiercing > 1) {
			if (eventSlave.nipplesPiercing > 1) {
				if (eventSlave.clitPiercing > 1) {
					if (eventSlave.devotion > 20 || eventSlave.trust < -20) {
						if (eventSlave.devotion <= 50) {
							if (canDoAnal(eventSlave) || canDoVaginal(eventSlave)) {
								V.RESSevent.push("heavy piercing");
							}
						}
					}
				}
			}
		}

		if (eventSlave.anus === 0) {
			if (eventSlave.devotion < -20) {
				if (eventSlave.trust >= -20) {
					V.RESSevent.push("resistant anal virgin");
				}
			}
		}

		if (V.PC.dick !== 0) {
			if (eventSlave.bellyPreg >= 300000) {
				V.RESSevent.push("hyperpreg stuck");
			}
		}

		if (eventSlave.devotion >= 50) {
			if (eventSlave.trust <= 20) {
				if (canWalk(eventSlave)) {
					V.RESSevent.push("devoted fearful slave");
				}
			}
		}

		if (eventSlave.relationship === 4) {
			if (eventSlave.devotion > 20) {
				if (eventSlave.trust > 20) {
					V.events.push("RE slave marriage");
				}
			}
		}

		if (V.eventResults.snatch === 0 && eventSlave.origin === "$He is your share of a raid on an illegal laboratory by your mercenaries." && eventSlave.newGamePlus === 0) {
			if (V.geneticMappingUpgrade === 2 && eventSlave.health.condition > 25 && eventSlave.devotion > 50 && V.cash > V.surgeryCost*4) {
				/* only one slave ever qualifies, so give it a bit more weight */
				V.events.push("RE snatch and grab followup");
				V.events.push("RE snatch and grab followup");
				V.events.push("RE snatch and grab followup");
			}
		}

		/* NICKNAME EVENTS */

		if (V.nicknamesAllowed === 1) {
			let toSearch = eventSlave.slaveName.toLowerCase();
			if (!toSearch.includes("'")) {
				if (V.week - eventSlave.weekAcquired >= 4) {
					V.events.push("RE nickname");
					V.seed = 0;
				}
			} /* closes nickname check */
		} /* closes no nicknames option */
	} /* closes mindbreak exempt */

	if (eventSlave.fetish === "mindbroken") {
		if (canWalk(eventSlave)) {
			V.RESSevent.push("mindbroken morning");
		}
	}
};

globalThis.populateEventArray = function(RESS = V.RESSevent.length, RESSTR = V.RESSTRevent.length, RETS = V.RETSevent.length, RECI = V.RECIevent.length) {
	/* EVENT RANDOMIZATION */
	let events = V.events;
	let i = 0;

	for (i = 0; i < RESS; i++) {
		events.push("RESS");
	}
	for (i = 0; i < RESSTR; i++) {
		events.push("RESSTR");
	}
	for (i = 0; i < RETS; i++) {
		events.push("RETS");
	}
	for (i = 0; i < RECI; i++) {
		events.push("RECI");
	}
	if (events.length === 0) {
		events.push("RE no event");
	}

	return events;
};


code:
/* see documentation for details /devNotes/Extended Family Mode Explained.txt */

/** @typedef Relative
 * An object that represents a entity in a family tree. Represents a group of common properties shared by SlaveState, InfantState, and PlayerState, as well as genepool objects.
 * @type {object}
 * @property {number} ID
 * @property {number} mother
 * @property {number} father
 * @property {number} actualAge
 * @property {number} birthWeek
 * @property {string} genes
 */

/** Returns true if mother is the mother of daughter
 * @param {Relative} daughter
 * @param {Relative} mother
 * @returns {boolean}
 */
globalThis.isMotherP = function(daughter, mother) {
	return daughter.mother === mother.ID;
};

/** Returns true if father is the father of daughter
 * @param {Relative} daughter
 * @param {Relative} father
 * @returns {boolean}
 */
globalThis.isFatherP = function(daughter, father) {
	return daughter.father === father.ID;
};

/** Returns true if parent is either the father or mother of daughter
 * @param {Relative} daughter
 * @param {Relative} parent
 * @returns {boolean}
 */
globalThis.isParentP = function(daughter, parent) {
	return isMotherP(daughter, parent) || isFatherP(daughter, parent);
};

/** Returns true if grandmother is the grandmother of granddaughter
 * @param {Relative} granddaughter
 * @param {Relative} grandmother
 * @returns {boolean}
 */
globalThis.isGrandmotherP = function(granddaughter, grandmother) {
	let father;
	let mother;
	if (((mother = getSlave(granddaughter.mother)) && (mother.mother === grandmother.ID)) || ((father = getSlave(granddaughter.father)) && (father.mother === grandmother.ID))) {
		return true;
	}
	return false;
};

/** Returns true if grandfather is the grandfather of granddaughter
 * @param {Relative} granddaughter
 * @param {Relative} grandfather
 * @returns {boolean}
 */
globalThis.isGrandfatherP = function(granddaughter, grandfather) {
	let father;
	let mother;
	if (((mother = getSlave(granddaughter.mother)) && (mother.father === grandfather.ID)) || ((father = getSlave(granddaughter.father)) && (father.father === grandfather.ID))) {
		return true;
	}
	return false;
};

/** Returns true if grandparent is the either the grandmother or grandfather of granddaughter
 * @param {Relative} granddaughter
 * @param {Relative} grandparent
 * @returns {boolean}
 */
globalThis.isGrandparentP = function(granddaughter, grandparent) {
	return isGrandmotherP(granddaughter, grandparent) || isGrandfatherP(granddaughter, grandparent);
};

/** Returns true if slave1 and slave2 share the same father
 * @param {Relative} slave1
 * @param {Relative} slave2
 * @returns {boolean}
 */
globalThis.sameDad = function(slave1, slave2) {
	if ((slave1.father === slave2.father) && (specificDad(slave1))) {
		return true;
	}
	return false;
};

/** Returns true if slave1 and slave2 share the same mother
 * @param {Relative} slave1
 * @param {Relative} slave2
 * @returns {boolean}
 */
globalThis.sameMom = function(slave1, slave2) {
	if ((slave1.mother === slave2.mother) && (specificMom(slave1))) {
		return true;
	}
	return false;
};

/** Returns true if slave1 and slave2 have at least one common parent
 * @param {Relative} slave1
 * @param {Relative} slave2
 * @returns {boolean}
 */
globalThis.sameParent = function(slave1, slave2) {
	return sameMom(slave1, slave2) || sameDad(slave1, slave2);
};

/** Returns true if ID represents a unique, specific, and traceable character; false if it represents a nonspecific or group ID.
 * @param {number} ID
 * @returns {boolean}
 */
globalThis.specificCharacterID = function(ID) {
	return (ID > 0		// active slave
		 || ID < -20	// missing slave
		 || ID === -1	// player
		 || ID === -3);	// player's old master
};

/** Returns true if the father of slave is a specific, unique character (current or former slave, or the PC)
 * @param {Relative} slave
 * @returns {boolean}
 */
globalThis.specificDad = function(slave) {
	return specificCharacterID(slave.father);
};

/** Returns true if the mother of slave is a specific, unique character (current or former slave, or the PC)
 * @param {Relative} slave
 * @returns {boolean}
 */
globalThis.specificMom = function(slave) {
	return specificCharacterID(slave.mother);
};

/** Returns true if aunt is the aunt of niece
 * @param {Relative} niece
 * @param {Relative} aunt
 * @returns {boolean}
 */
globalThis.isAunt = function(niece, aunt) {
	if (!V.showDistantRelatives) {
		return false;
	}

	if (!niece || !aunt || (niece.ID === aunt.ID)) {
		return false;
	}
	let father;
	let mother;
	if ((mother = getSlave(niece.mother)) && (mother.ID !== aunt.ID) && !sameTParent(mother, aunt) && sameMom(mother, aunt) && sameDad(mother, aunt)) {
		return true;
	} else if ((father = getSlave(niece.father)) && (father.ID !== aunt.ID) && !sameTParent(father, aunt) && sameMom(father, aunt) && sameDad(father, aunt)) {
		return true;
	}

	return false;
};

// catches the case if a mother is a father or a father a mother - thank you familyAnon, for this code
globalThis.sameTParent = function(slave1, slave2) {
	if (slave1.mother === -1 && slave1.father === -1 && slave2.mother === -1 && slave2.father === -1) {
		return 1;
	} else if (slave1.mother === slave2.father && slave1.father === slave2.mother && specificMom(slave1) && specificDad(slave1) && slave1.mother !== slave1.father) {
		return 2;
	} else if ((slave1.mother === slave2.father || slave1.father === slave2.mother) && specificMom(slave1) && specificMom(slave2) && slave1.mother !== slave1.father) {
		return 3;
	}
	return 0;
};

/** Returns the degree of siblinghood shared by two entities
 * @param {Relative} slave1
 * @param {Relative} slave2
 * @returns {number} - 0: not siblings; 1: twins; 2: full siblings; 3: half-siblings
 */
globalThis.areSisters = function(slave1, slave2) {
	if (slave1.ID === slave2.ID) {
		return 0; // you are not your own sister
	} else if (!specificDad(slave1) && !specificMom(slave1)) {
		return 0; // not related
	} else {
		if (!sameDad(slave1, slave2) && sameMom(slave1, slave2)) {
			return 3; // half sisters
		} else if (sameDad(slave1, slave2) && !sameMom(slave1, slave2)) {
			return 3; // half sisters
		} else if (sameTParent(slave1, slave2) === 3) {
			return 3; // half sisters
		} else if (sameTParent(slave1, slave2) === 2) {
			return 2; // sisters
		} else if (sameDad(slave1, slave2) && sameMom(slave1, slave2)) {
			if (slave1.actualAge === slave2.actualAge && slave1.birthWeek === slave2.birthWeek) {
				return 1; // twins
			} else {
				return 2; // sisters
			}
		} else {
			return 0; // not related
		}
	}
};

/** Returns true if two entities are simple first cousins (i.e. at least one parent of one slave is a full sibling of at least one parent of the other slave)
 * @param {Relative} slave1
 * @param {Relative} slave2
 * @returns {boolean}
 */
globalThis.areCousins = function(slave1, slave2) {
	if (!V.showDistantRelatives) {
		return false;
	}

	if (!slave1 || !slave2 || (slave1.ID === slave2.ID) || areSisters(slave1, slave2)) {
		return false;
	}

	let slave1Mom;
	let slave1Dad;
	let slave2Mom;
	let slave2Dad;

	if ((slave1Mom = getSlave(slave1.mother)) && (slave2Mom = getSlave(slave2.mother)) && !sameTParent(slave1Mom, slave2Mom) && sameMom(slave1Mom, slave2Mom) && sameDad(slave1Mom, slave2Mom)) {
		return true;
	} else if ((slave1Mom = getSlave(slave1.mother)) && (slave2Dad = getSlave(slave2.father)) && !sameTParent(slave1Mom, slave2Dad) && sameMom(slave1Mom, slave2Dad) && sameDad(slave1Mom, slave2Dad)) {
		return true;
	} else if ((slave1Dad = getSlave(slave1.father)) && (slave2Mom = getSlave(slave2.mother)) && !sameTParent(slave1Dad, slave2Mom) && sameMom(slave1Dad, slave2Mom) && sameDad(slave1Dad, slave2Mom)) {
		return true;
	} else if ((slave1Dad = getSlave(slave1.father)) && (slave2Dad = getSlave(slave2.father)) && !sameTParent(slave1Dad, slave2Dad) && sameMom(slave1Dad, slave2Dad) && sameDad(slave1Dad, slave2Dad)) {
		return true;
	}

	return false;
};

/** Returns whether two entities are *closely* related (i.e. siblings or parent/child).
 * Distant relatives are not checked by this function.
 * @param {Relative} slave1
 * @param {Relative} slave2
 * @returns {boolean}
 */
globalThis.areRelated = function(slave1, slave2) {
	return (slave1.father === slave2.ID || slave1.mother === slave2.ID || slave2.father === slave1.ID || slave2.mother === slave1.ID || areSisters(slave1, slave2) > 0);
};

/** Returns the total number of relatives that a slave has
 * @param {App.Entity.SlaveState} slave
 * @returns {number}
 */
globalThis.totalRelatives = function(slave) {
	let relatives = 0;
	if (slave.mother > 0) {
		relatives += 1;
	}
	if (slave.father > 0) {
		relatives += 1;
	}
	if (slave.daughters > 0) {
		relatives += slave.daughters;
	}
	if (slave.sisters > 0) {
		relatives += slave.sisters;
	}
	return relatives;
};

/** Returns the slaves which are mutual children of two entities
 * @param {Relative} slave1
 * @param {Relative} slave2
 * @param {App.Entity.SlaveState[]} slaves
 * @returns {number}
 */
globalThis.mutualChildren = function(slave1, slave2, slaves) {
	return slaves.filter(function(s) {
		return s.ID !== slave1.ID && s.ID !== slave2.ID && s.mother > 0 && s.father > 0 && ((s.mother === slave1.ID && s.father === slave2.ID) || (s.mother === slave2.ID && s.father === slave1.ID));
	}).length;
};

/** Returns a random slave related to a given entity
 * @param {Relative} slave
 * @param {function(App.Entity.SlaveState): boolean} [filterFunction]
 * @returns {App.Entity.SlaveState}
 */
globalThis.randomRelatedSlave = function(slave, filterFunction) {
	if (!slave) {
		return undefined;
	}
	if (typeof filterFunction !== 'function') {
		filterFunction = () => true;
	}
	const arr = V.slaves.filter((s) => areRelated(slave, s) && filterFunction(s));
	return arr.random();
};

/**
 * @param {Relative} slave
 * @returns {App.Entity.SlaveState}
 */
globalThis.randomRelatedAvailableSlave = function(slave) {
	return randomRelatedSlave(slave, (s) => isSlaveAvailable(s));
};

/**
 * @param {Relative} slave
 * @returns {App.Entity.SlaveState}
 */
globalThis.randomSister = function(slave) {
	return randomRelatedSlave(slave, (s) => areSisters(slave, s) > 0);
};

/**
 * @param {Relative} slave
 * @returns {App.Entity.SlaveState}
 */
globalThis.randomTwinSister = function(slave) {
	return randomRelatedSlave(slave, (s) => areSisters(slave, s) === 1);
};

/**
 * @param {Relative} slave
 * @returns {App.Entity.SlaveState}
 */
globalThis.randomAvailableSister = function(slave) {
	return randomRelatedSlave(slave, (s) => isSlaveAvailable(s) && areSisters(slave, s) > 0);
};

/**
 * @param {Relative} slave
 * @returns {App.Entity.SlaveState}
 */
globalThis.randomDaughter = function(slave) {
	return randomRelatedSlave(slave, (s) => isParentP(s, slave));
};

/**
 * @param {Relative} slave
 * @returns {App.Entity.SlaveState}
 */
globalThis.randomAvailableDaughter = function(slave) {
	return randomRelatedSlave(slave, (s) => isSlaveAvailable(s) && isParentP(s, slave));
};

/**
 * @param {Relative} slave
 * @returns {App.Entity.SlaveState}
 */
globalThis.randomParent = function(slave) {
	return randomRelatedSlave(slave, (s) => isParentP(slave, s));
};

/**
 * @param {Relative} slave
 * @returns {App.Entity.SlaveState}
 */
globalThis.randomAvailableParent = function(slave) {
	return randomRelatedSlave(slave, (s) => isSlaveAvailable(s) && isParentP(slave, s));
};

/**
 * @param {Relative} slave
 * @returns {object}
 */
globalThis.availableRelatives = function(slave) {
	let avail = {
		mother: false, motherName: null, father: false, fatherName: null, sisters: 0, daughters: 0, oneSisterRel: null, oneDaughterRel: null
	};

	V.slaves.forEach((other) => {
		if (slave.mother === other.ID) {
			avail.motherName = other.slaveName;
		}
		if (slave.father === other.ID) {
			avail.fatherName = other.slaveName;
		}
		if (isSlaveAvailable(other)) {
			if (slave.mother === other.ID) {
				avail.mother = true;
			}
			if (slave.father === other.ID) {
				avail.father = true;
			}
			if (slave.ID === other.mother || slave.ID === other.father) {
				avail.daughters++;
				if (avail.daughters === 1) {
					avail.oneDaughterRel = relativeTerm(slave, other);
				}
			}
			if (areSisters(slave, other) > 0) {
				avail.sisters++;
				if (avail.sisters === 1) {
					avail.oneSisterRel = relativeTerm(slave, other);
				}
			}
		}
	});

	return avail;
};

globalThis.totalPlayerRelatives = function(pc) {
	let relatives = 0;
	if (pc.mother > 0) {
		relatives += 1;
	}
	if (pc.father > 0) {
		relatives += 1;
	}
	if (pc.daughters > 0) {
		relatives += pc.daughters;
	}
	if (pc.sisters > 0) {
		relatives += pc.sisters;
	}
	return relatives;
};

/**
 * Returns the term for slave2's relationship to slave1 (i.e. "daughter" if slave2 is slave1's daughter).
 * Performs distant relative checking if enabled.
 * @param {Relative} slave1
 * @param {Relative} slave2
 * @returns {string|null} - returns null if the slaves are not related, even distantly.
 */
globalThis.relativeTerm = function(slave1, slave2) {
	const useMaleTerms = (V.diversePronouns || slave2.ID === -1);
	if (slave2.mother === slave1.ID || slave2.father === slave1.ID) {
		if (slave2.genes === "XY" && useMaleTerms) {
			return "son";
		} else {
			return "daughter";
		}
	} else if (slave1.mother === slave2.ID && slave1.father === slave2.ID) {
		return "sole parent";
	} else if (slave1.mother === slave2.ID) {
		return "mother";
	} else if (slave1.father === slave2.ID) {
		return "father";
	} else if (areSisters(slave2, slave1) === 1) {
		if (slave2.genes === "XY" && useMaleTerms) {
			return "twin brother";
		} else {
			return "twin sister";
		}
	} else if (areSisters(slave2, slave1) === 2) {
		if (slave2.genes === "XY" && useMaleTerms) {
			return "brother";
		} else {
			return "sister";
		}
	} else if (areSisters(slave2, slave1) === 3) {
		if (slave2.genes === "XY" && useMaleTerms) {
			return "half-brother";
		} else {
			return "half-sister";
		}
	} else if (isAunt(slave1, slave2)) {
		if (slave2.genes === "XY" && useMaleTerms) {
			return "uncle";
		} else {
			return "aunt";
		}
	} else if (isAunt(slave2, slave1)) {
		if (slave2.genes === "XY" && useMaleTerms) {
			return "nephew";
		} else {
			return "niece";
		}
	} else if (areCousins(slave2, slave1)) {
		return "cousin";
	} else if (isGrandfatherP(slave1, slave2)) {
		return "grandfather";
	} else if (isGrandmotherP(slave1, slave2)) {
		return "grandmother";
	} else if (isGrandparentP(slave2, slave1)) {
		if (slave2.genes === "XY" && useMaleTerms) {
			return "grandson";
		} else {
			return "granddaughter";
		}
	}

	return null;
};

/** completely reset all the family counters in the game state (for both PC and slaves) */
globalThis.resetFamilyCounters = function() {
	for (let slave of V.slaves) {
		slave.daughters = 0;
		slave.sisters = 0;
	}
	V.PC.daughters = 0;
	V.PC.sisters = 0;

	for (let slave of V.slaves) {
		if (slave.mother === -1 || slave.father === -1) {
			V.PC.daughters++;
		}
		if (areSisters(slave, V.PC)) {
			V.PC.sisters++;
		}
		for (let otherSlave of V.slaves) {
			if (isParentP(otherSlave, slave)) {
				slave.daughters++;
			}
			if (areSisters(otherSlave, slave)) {
				slave.sisters++;
			}
		}
	}
};

/** Set any missing parents to a known ID for the given entity (usually so that a sibling can be safely generated)
 * @param {Relative} slave
 */
globalThis.setMissingParents = function(slave) {
	const gp = V.genePool.find(s => s.ID === slave.ID);
	if (!specificMom(slave)) {
		slave.mother = V.missingParentID;
		if (gp) {
			gp.mother = slave.mother;
		}
		V.missingParentID--;
	}
	if (!specificDad(slave)) {
		slave.father = V.missingParentID;
		if (gp) {
			gp.father = slave.father;
		}
		V.missingParentID--;
	}
};

repo_name: NovX/fc

license: mit


code:
/* eslint-disable camelcase */
/* eslint-disable no-console */
let lastActiveSlave, lastSlaves, lastPC;

/*
	To use, add something like:

		<<link "Pull up the file on $his family tree.">>
		<<replace #family-tree-link>>
			<<run renderFamilyTree($slaves, $activeSlave.ID)>>
		<</replace>>
	<</link>>

*/

globalThis.renderFamilyTree = function(slaves, filterID) {
	'use strict';

	let ftreeWidth, ftreeHeight;
	let chartWidth, chartHeight;
	let margin;
	d3.select('#ftree-canvas').remove();
	let svg = d3.select('#family-tree')
		.append('svg')
		.attr('id', 'ftree-canvas');
	let chartLayer = svg.append('g').classed('chartLayer', true);

	let data = buildFamilyTree(slaves, filterID);

	initFtreeSVG(data);
	runFtreeSim(data);

	function initFtreeSVG(data) {
		ftreeWidth = data.nodes.length * 45;
		ftreeHeight = data.nodes.length * 35;
		if (ftreeWidth < 600) {
			ftreeWidth = 600;
		} else if (ftreeWidth > 1920) {
			ftreeWidth = 1920;
		}
		if (ftreeHeight < 480) {
			ftreeHeight = 480;
		} else if (ftreeHeight > 1200) {
			ftreeHeight = 1200;
		}

		margin = {
			top: 0,
			left: 0,
			bottom: 0,
			right: 0
		};

		chartWidth = ftreeWidth - (margin.left + margin.right);
		chartHeight = ftreeHeight - (margin.top + margin.bottom);

		svg.attr('width', ftreeWidth).attr('height', ftreeHeight);

		svg.append('defs');

		svg.append('defs').append('marker')
			.attr('id', 'arrowhead')
			.attr('viewBox', '-0 -5 10 10')
			.attr('refX', 13)
			.attr('refY', 0)
			.attr('orient', 'auto')
			.attr('markerWidth', 13)
			.attr('markerHeight', 13)
			.attr('xoverflow', 'visible')
			.append('svg:path')
			.attr('d', 'M 0,-1 L 5,0 L 0,1')
			.attr('fill', '#a1a1a1')
			.style('stroke', 'none');

		chartLayer
			.attr('width', chartWidth)
			.attr('height', chartHeight)
			.attr('transform', `translate(${[margin.left, margin.top]})`);
	}

	function runFtreeSim(data) {
		let simulation = d3.forceSimulation()
			.force('link', d3.forceLink().id(function(d) {
				return d.index;
			}))
			// eslint-disable-next-line no-unused-vars
			.force('collide', d3.forceCollide(function(d) {
				return 60;
			}).iterations(4))
			.force('charge', d3.forceManyBody().strength(-200).distanceMin(100).distanceMax(1000))
			.force('center', d3.forceCenter(chartWidth / 2, chartHeight / 2))
			.force('y', d3.forceY(100))
			.force('x', d3.forceX(200));

		let link = svg.append('g')
			.attr('class', 'link')
			.selectAll('link')
			.data(data.links)
			.enter()
			.append('line')
			.attr('marker-end', 'url(#arrowhead)')
			.attr('stroke', function(d) {
				if (d.type === 'homologous') {
					return '#862d59';
				} else if (d.type === 'paternal') {
					return '#24478f';
				} else {
					return '#aa909b';
				}
			})
			.attr('stroke-width', 2)
			.attr('fill', 'none');

		let node = svg.selectAll('.node')
			.data(data.nodes)
			.enter().append('g')
			.attr('class', 'node')
			.call(d3.drag()
				.on('start', dragstarted)
				.on('drag', dragged)
				.on('end', dragended));

		node.append('circle')
			.attr('r', function(d) { return d.r; })
			.attr('stroke', function(d) {
				if (d.ID === filterID) {
					return '#ffff20';
				} else {
					return '#5a5a5a';
				}
			})
			.attr('class', 'node-circle')
			.attr('r', 20);

		node.append('text')
			.text(function(d) {
				let ssym;
				if (d.dick > 0 && d.vagina > -1) {
					ssym = '☿';
				} else if (d.dick > 0) {
					ssym = '♂';
				} else if (d.vagina > -1) {
					ssym = '♀';
				} else {
					ssym = '?';
				}
				return `${d.name}(${ssym})`;
			})
			.attr('dy', 4)
			.attr('dx', function(d) {
				return -(8 * d.name.length) / 2;
			})
			.attr('class', 'node-text')
			.style('fill', function(d) {
				if (d.is_mother && d.is_father) {
					return '#b84dff';
				} else if (d.is_father) {
					return '#00ffff';
				} else if (d.is_mother) {
					return '#ff3399';
				} else if (d.unborn) {
					return '#a3a3c2';
				} else {
					return '#66cc66';
				}
			});

		svg.selectAll('.node-circle');
		svg.selectAll('.node-text');

		let ticked = function() {
			link
				.attr('x1', function(d) { return d.source.x; })
				.attr('y1', function(d) { return d.source.y; })
				.attr('x2', function(d) { return d.target.x; })
				.attr('y2', function(d) { return d.target.y; });

			node
				.attr("transform", function(d) { return `translate(${d.x}, ${d.y})`; });
		};

		simulation.nodes(data.nodes)
			.on('tick', ticked);

		simulation.force('link')
			.links(data.links);

		function dragstarted(d) {
			if (!d3.event.active) { simulation.alphaTarget(0.3).restart(); }
			d.fx = d.x;
			d.fy = d.y;
		}

		function dragged(d) {
			d.fx = d3.event.x;
			d.fy = d3.event.y;
		}

		function dragended(d) {
			if (!d3.event.active) { simulation.alphaTarget(0); }
			d.fx = null;
			d.fy = null;
		}
	}
};

globalThis.buildFamilyTree = function(slaves, filterID) {
	let family_graph = {nodes: [], links: []};
	let node_lookup = {};
	let preset_lookup = {
		'-2': 'A citizen',
		'-3': 'Former Master',
		'-4': 'An arcology owner',
		'-5': 'Client',
		'-6': 'Social Elite',
		'-7': 'Gene lab'
	};
	let outdads = {};
	let outmoms = {};
	let kids = {};

	let fake_pc = {
		slaveName: `${V.PC.slaveName}(You)`,
		mother: V.PC.mother,
		father: V.PC.father,
		dick: V.PC.dick,
		vagina: V.PC.vagina,
		ID: V.PC.ID
	};
	let charList = [fake_pc];
	charList.push.apply(charList, slaves);
	charList.push.apply(charList, V.tanks);

	let unborn = {};
	for (let i = 0; i < V.tanks.length; i++) {
		unborn[V.tanks[i].ID] = true;
	}
	for (let i = 0; i < V.cribs.length; i++) {
		unborn[V.cribs[i].ID] = true;
	}

	for (let i = 0; i < charList.length; i++) {
		let mom = charList[i].mother;
		let dad = charList[i].father;

		if (mom) {
			if (!kids[mom]) {
				kids[mom] = {};
			}
			kids[mom].mother = true;
		}
		if (dad) {
			if (!kids[dad]) {
				kids[dad] = {};
			}
			kids[dad].father = true;
		}
	}

	for (let i = 0; i < charList.length; i++) {
		let character = charList[i];
		if (character.mother === 0 && character.father === 0 && !kids[character.ID]) {
			continue;
		}
		let mom = character.mother;
		if (mom < -6) {
			if (mom in V.missingTable && V.showMissingSlaves) {
				if (typeof node_lookup[mom] === 'undefined') {
					node_lookup[mom] = family_graph.nodes.length;
					let missing = V.missingTable[mom];
					charList.push({
						ID: mom,
						mother: missing.mother,
						father: missing.father,
						is_mother: true,
						dick: missing.dick,
						vagina: missing.vagina,
						slaveName: missing.slaveName
					});
				}
			} else {
				if (typeof outmoms[mom] === 'undefined') {
					outmoms[mom] = [];
				}
				outmoms[mom].push(character.slaveName);
			}
		} else if (mom < 0 && typeof node_lookup[mom] === 'undefined' && typeof preset_lookup[mom] !== 'undefined') {
			node_lookup[mom] = family_graph.nodes.length;
			charList.push({
				ID: mom,
				mother: 0,
				father: 0,
				is_father: true,
				dick: 0,
				vagina: 1,
				slaveName: preset_lookup[mom]
			});
		}

		let dad = character.father;
		if (dad < -6) {
			if (dad in V.missingTable && V.showMissingSlaves) {
				if (typeof node_lookup[dad] === 'undefined') {
					node_lookup[dad] = family_graph.nodes.length;
					let missing = V.missingTable[dad];
					charList.push({
						ID: dad,
						mother: missing.mother,
						father: missing.father,
						is_father: true,
						dick: missing.dick,
						vagina: missing.vagina,
						slaveName: missing.slaveName
					});
				}
			} else {
				if (typeof outdads[dad] === 'undefined') {
					outdads[dad] = [];
				}
				outdads[dad].push(character.slaveName);
			}
		} else if (dad < 0 && typeof node_lookup[dad] === 'undefined' && typeof preset_lookup[dad] !== 'undefined') {
			node_lookup[dad] = family_graph.nodes.length;
			charList.push({
				ID: dad,
				mother: 0,
				father: 0,
				is_father: true,
				dick: 1,
				vagina: -1,
				slaveName: preset_lookup[dad]
			});
		}
	}
	let mkeys = Object.keys(outmoms);
	for (let i = 0; i < mkeys.length; i++) {
		let name;
		let key = mkeys[i];
		let names = outmoms[key];
		if (names.length === 1) {
			name = names[0];
		} else if (names.length === 2) {
			name = names.join(' and ');
		} else {
			names[-1] = `and ${names[-1]}`;
			name = names.join(', ');
		}
		node_lookup[key] = family_graph.nodes.length;
		// Outside extant slaves set
		charList.push({
			ID: key,
			mother: 0,
			father: 0,
			is_mother: true,
			dick: 0,
			vagina: 1,
			slaveName: `${name}'s mother`
		});
	}

	let dkeys = Object.keys(outdads);
	for (let i = 0; i < dkeys.length; i++) {
		let name;
		let key = dkeys[i];
		let names = outdads[key];
		if (names.length === 1) {
			name = names[0];
		} else if (names.length === 2) {
			name = names.join(' and ');
		} else {
			names[-1] = `and ${names[-1]}`;
			name = names.join(', ');
		}
		node_lookup[key] = family_graph.nodes.length;
		// Outside extant slaves set
		charList.push({
			ID: key,
			mother: 0,
			father: 0,
			is_father: true,
			dick: 1,
			vagina: -1,
			slaveName: `${name}'s father`
		});
	}

	let charHash = {};
	for (let i = 0; i < charList.length; i++) {
		charHash[charList[i].ID] = charList[i];
	}

	let related = {};
	let seen = {};
	let saveTree = {};

	function relatedTo(character, targetID, relIDs = {tree: {}, related: false}) {
		relIDs.tree[character.ID] = true;
		if (related[character.ID]) {
			relIDs.related = true;
			return relIDs;
		}
		if (character.ID === targetID) {
			relIDs.related = true;
		}
		if (seen[character.ID]) {
			return relIDs;
		}
		seen[character.ID] = true;
		if (character.mother !== 0) {
			if (charHash[character.mother]) {
				relatedTo(charHash[character.mother], targetID, relIDs);
			}
		}
		if (character.father !== 0) {
			if (charHash[character.father]) {
				relatedTo(charHash[character.father], targetID, relIDs);
			}
		}
		return relIDs;
	}
	if (filterID) {
		if (charHash[filterID]) {
			let relIDs = relatedTo(charHash[filterID], filterID);
			for (let k in relIDs.tree) {
				related[k] = true;
			}
			for (let i = 0; i < charList.length; i++) {
				if (charHash[charList[i].ID]) {
					let pRelIDs = relatedTo(charHash[charList[i].ID], filterID);
					if (pRelIDs.related) {
						for (let k in pRelIDs.tree) {
							related[k] = true;
							if (saveTree[k]) {
								for (let k2 in saveTree[k].tree) {
									related[k2] = true;
								}
							}
						}
					}
					saveTree[charList[i].ID] = pRelIDs;
				}
			}
		}
	}

	for (let i = 0; i < charList.length; i++) {
		let character = charList[i];
		let char_id = character.ID;
		if (char_id !== filterID) {
			if (character.mother === 0 && character.father === 0 && !kids[char_id]) {
				continue;
			}
			if (filterID && !related[char_id]) {
				continue;
			}
		}
		node_lookup[char_id] = family_graph.nodes.length;
		let char_obj = {
			ID: char_id,
			name: character.slaveName,
			dick: character.dick,
			unborn: !!unborn[char_id],
			vagina: character.vagina
		};
		if (kids[char_id]) {
			char_obj.is_mother = !!kids[char_id].mother;
			char_obj.is_father = !!kids[char_id].father;
		} else {
			char_obj.is_mother = false;
			char_obj.is_father = false;
		}
		family_graph.nodes.push(char_obj);
	}

	for (let i = 0; i < charList.length; i++) {
		let character = charList[i];
		let char_id = character.ID;
		if (character.mother === 0 && character.father === 0 && !kids[char_id]) {
			continue;
		}
		if (filterID && !related[char_id]) {
			if (related[character.mother]) {
				console.log('wtf, mom');
			}
			if (related[character.father]) {
				console.log('wtf, dad');
			}
			continue;
		}
		if (typeof node_lookup[character.mother] !== 'undefined') {
			let ltype;
			if (character.mother === character.father) {
				ltype = 'homologous';
			} else {
				ltype = 'maternal';
			}
			family_graph.links.push({
				type: ltype,
				target: node_lookup[char_id] * 1,
				source: node_lookup[character.mother] * 1
			});
		}
		if (character.mother === character.father) {
			continue;
		}
		if (typeof node_lookup[character.father] !== 'undefined') {
			family_graph.links.push({type: 'paternal', target: node_lookup[char_id] * 1, source: node_lookup[character.father] * 1});
		}
	}
	return family_graph;
};

/* Old version. To use, do something like:

<<run updateFamilyTree($activeSlave, $slaves, $PC)>>
html_script_tag updateFamilyTree() end_html_script_tag
// tweego freaks out if the actual end of script tag is used even inside a comment
If you want to update the tree, just re-call the run line.

If there's no active slave, you can do:

<<run updateFamilyTree(null, $slaves, $PC)>>
*/

globalThis.updateFamilyTree = function(activeSlave = lastActiveSlave, slaves = lastSlaves, PC = lastPC) {
	lastActiveSlave = activeSlave;
	lastSlaves = slaves;
	lastPC = PC;
	let treeDepth = 0;
	let numTreeNodes = 0;

	let graphElement = document.getElementById("graph");
	if (!graphElement) {
		return;
	}
	graphElement.innerHTML = "";

	/* The way this code works is that we start with the activeSlave then we call
		slaveInfo() recursively to work our way up the tree finding their parents.

	*/

	function getSlave(id, expectedGenes) {
		if (id === -1) {
			return {
				"slaveName": "YOU",
				"ID": id,
				"physicalAge": PC.physicalAge,
				"genes": PC.genes,
				"father": PC.father,
				"mother": PC.mother
			};
		}
		if (id === 0) {
			return {"slaveName": "-", "ID": id, "genes": expectedGenes};
		}
		if (id === activeSlave.ID) {
			return activeSlave;
		}
		for (let i = 0; i < slaves.length; ++i) {
			if (slaves[i].ID === id) {
				return slaves[i];
			}
		}
		return {
			"slaveName": "-",
			"ID": id,
			"genes": expectedGenes
		};
	}

	function slaveInfo(slave, activeSlaveId, recursionProtectSlaveId = {}) {
		numTreeNodes = 0;
		treeDepth = 0;
		if (recursionProtectSlaveId[slave.ID]) {
			console.log("Recursion protection");
			return slaveInfo_(slave, activeSlaveId);
		}
		recursionProtectSlaveId[slave.ID] = true;

		if (typeof slave.father === "undefined" || typeof slave.mother === "undefined") {
			return slaveInfo_(slave, activeSlaveId);
		}

		if (slave.father === -1 || slave.mother === -1) {
			return slaveInfo(getSlave(-1), activeSlaveId, recursionProtectSlaveId);
		}
		if (slave.father !== 0) {
			return slaveInfo(getSlave(slave.father, "unknownXY"), activeSlaveId, recursionProtectSlaveId);
		}

		if (slave.mother !== 0) {
			return slaveInfo(getSlave(slave.mother, "unknownXX"), activeSlaveId, recursionProtectSlaveId);
		}
		return slaveInfo_(slave, activeSlaveId);
	}

	function slaveInfo_(slave, activeSlaveId, slavesAdded = {}, depth = 1) {
		numTreeNodes += 1;
		treeDepth = Math.max(treeDepth, depth);
		let shouldAddChildren = false;
		if (!slavesAdded[slave.ID]) {
			shouldAddChildren = true;
			slavesAdded[slave.ID] = true;
		}
		let data = {
			"name": slave.slaveName + (slave.physicalAge ? (` (${slave.physicalAge})`) : ""),
			"class": slave.genes,
			"textClass": (activeSlaveId === slave.ID) ? "emphasis" : "",
			"marriages": [],
		};

		let spouseToChild = {};

		function maybeAddSpouseToChild(child) {
			if (child.ID === slave.ID) {
				return;
			}
			if (child.father === slave.ID) {
				if (!spouseToChild[child.mother]) {
					spouseToChild[child.mother] = [];
				}
				spouseToChild[child.mother].push(child);
			} else if (child.mother === slave.ID) {
				if (!spouseToChild[child.father]) {
					spouseToChild[child.father] = [];
				}
				spouseToChild[child.father].push(child);
			}
		}

		if (activeSlave.ID !== PC.ID) {
			maybeAddSpouseToChild(activeSlave);
		}
		maybeAddSpouseToChild(getSlave(-1));

		for (let i = 0; i < slaves.length; ++i) {
			let child = slaves[i];
			if (child.ID !== activeSlave.ID) {
				maybeAddSpouseToChild(child);
			}
		}

		for (let key in spouseToChild) {
			if (spouseToChild.hasOwnProperty(key)) {
				let children = shouldAddChildren ? spouseToChild[key] : [];
				let spouse = getSlave(key, (slaves.genes === "XX") ? "unknownXY" : (slaves.genes === "XY") ? "unknownXX" : "unknown");
				let spouseName;
				if (spouse.ID !== slave.ID) {
					spouseName = spouse.slaveName + (spouse.physicalAge ? (` (${spouse.physicalAge})`) : "");
				} else {
					spouseName = (spouse.ID === -1) ? "(yourself)" : "(themselves)";
				}
				let marriage = {
					"spouse": {"name": spouseName, "class": spouse.genes},
					"children": children.map(function(x) { return slaveInfo_(x, activeSlaveId, slavesAdded, depth + 1); })
				};
				data.marriages.push(marriage);
			}
		}
		return data;
	}

	if (activeSlave === PC || activeSlave === null) {
		activeSlave = getSlave(-1);
	}
	const treeData = [slaveInfo(activeSlave, activeSlave.ID)];
	console.log("Family tree is", treeData, 'and has:', numTreeNodes);

	let parentWidth = document.getElementById('edit-family').offsetWidth;

	console.log(parentWidth, document.getElementById('passages').offsetWidth);
	if (!parentWidth) {
		parentWidth = document.body.offsetWidth - 483;
	}

	console.log(parentWidth, Math.min(200 + 40 * numTreeNodes, parentWidth - 200) + 200);

	dTree.init(treeData, {
		target: "#graph",
		debug: true,
		height: 50 + 50 * treeDepth,
		/* very rough heuristics */
		width: Math.min(200 + 40 * numTreeNodes,
			parentWidth - 200) + 200,
		callbacks: {
			nodeClick: function( /* name, extra*/ ) {}
		}
	});
};