diff --git a/src/005-passages/interactPassages.js b/src/005-passages/interactPassages.js index df3b388188cb2ea6703e49f7a657638143464077..f462029f6084158d7eb11d8cc6d7630bd706b926 100644 --- a/src/005-passages/interactPassages.js +++ b/src/005-passages/interactPassages.js @@ -30,3 +30,27 @@ new App.DomPassage("Fat Grafting", return App.UI.SlaveInteract.fatGraft(getSlave(V.AS)); } ); + +new App.DomPassage( + "Slave Slave Swap Workaround", + () => { + V.nextButton = "Abort Operation"; + V.nextLink = "Main"; + return bodySwapSelection(getSlave(V.AS)); + } +); + +new App.DomPassage( + "Husk Slave Swap Workaround", + () => { + V.nextButton = "Abort Operation"; + if (V.activeSlave.tankBaby !== 3) { + V.nextLink = "Scheduled Event"; + V.returnTo = "Scheduled Event"; + } else { + V.nextLink = "Main"; + V.returnTo = "Incubator"; + } + return bodySwapSelection(getSlave(V.AS)); + } +); diff --git a/src/endWeek/reports/clinicReport.js b/src/endWeek/reports/clinicReport.js index 9366fb523ede2bf98ee03d5ea15d2e63ca9956b7..65dec26f6805a49a127d461c23cdbdbf1f95fc32 100644 --- a/src/endWeek/reports/clinicReport.js +++ b/src/endWeek/reports/clinicReport.js @@ -46,7 +46,7 @@ App.EndWeek.clinicReport = function() { S.Nurse.fetishStrength += 4; } } - const {He, he, His, his, him, wife} = getPronouns(S.Nurse); + const {He, he, His, his, him, himself, wife} = getPronouns(S.Nurse); r.push(`${SlaveFullName(S.Nurse)} is serving as the clinical nurse.`); if (S.Nurse.relationship === -3 && S.Nurse.devotion > 50) { r.push(`${He} does ${his} best to be a caring and nurturing ${wife}.`); @@ -123,32 +123,43 @@ App.EndWeek.clinicReport = function() { improveCondition(slave, 2); break; case "$He is remembered for winning best in show as a cockmilker.": - if ((slave.balls > 4) && (slave.dick !== 0)) { + if (slave.balls > 4 && slave.dick !== 0) { r.push(`${He} does ${his} best to aid the dribbling ${slave.slaveName}, paying special attention to ${his2} huge cock and balls as well as making sure to milk ${his2}`); if (canAchieveErection(slave)) { r.push(`erect`); } r.push(`dick hourly.`); improveCondition(slave, 4); - if (jsRandom(1, 100) > 65) { + if (jsRandom(1, 100) > 65 && canTaste(S.Nurse)) { if (S.Nurse.fetish === "none") { - S.Nurse.fetish = "cumslut"; // FIXME: no player notification on fetish acquisition? + r.push(`It's not uncommon for ${him} to receive a load to the face; before long, <span class="fetish gain">${he} starts to enjoy the taste.</span>`); + S.Nurse.fetish = "cumslut"; } else if (S.Nurse.fetish === "cumslut") { - S.Nurse.fetishStrength += 4; + r.push(`${canSmell(S.Nurse) ? `The smell of ${his2}` : `${His2}`} semen is irresistable to ${him} and sometimes ${he} can't help but`); + if (slave.chastityPenis === 0 && slave.rules.release.facilityLeader === 1) { + r.push(`wrap ${his} lips around ${him2} and sample the stuff directly,`); + seX(S.Nurse, "oral", slave, "penetrative"); + } else { + r.push(`catch a little of it in a cup for further "analysis",`); + } + r.push(`<span class="fetish inc">further feeding ${his} cum fetishism.</span>`); + Math.clamp(S.Nurse.fetishStrength += 4, 0, 100); } } } break; case "$He is remembered for winning best in show as a dairy cow.": - if ((slave.lactation > 0) && ((slave.boobs-slave.boobsImplant) > 6000)) { + if (slave.lactation > 0 && (slave.boobs - slave.boobsImplant - slave.boobsMilk) > 6000) { r.push(`${He} does ${his} best to aid the leaking ${slave.slaveName}, paying special attention to ${his2} huge breasts as well as making sure to milk ${him2} hourly.`); improveCondition(slave, 4); slave.lactationDuration = 2; - if (jsRandom(1, 100) > 65) { + if (jsRandom(1, 100) > 65 && canTaste(S.Nurse)) { if (S.Nurse.fetish === "none") { - S.Nurse.fetish = "boobs"; // FIXME: no player notification on fetish acquisition? + r.push(`Those lovely globes are just too tantilizing for ${him} to resist, <span class="fetish gain">${he} can't help but take a drink ${himself}.</span>`); + S.Nurse.fetish = "boobs"; } else if (S.Nurse.fetish === "boobs") { - S.Nurse.fetishStrength += 4; + r.push(`${He} takes the opportunity to <span class="fetish gain">feed ${his} breast fetishism</span> by getting up close and personal with ${his2} prized udders.`); + Math.clamp(S.Nurse.fetishStrength += 4, 0, 100); } } } @@ -172,7 +183,7 @@ App.EndWeek.clinicReport = function() { if (slave.devotion > 50) { slave.devotion += 4; slave.trust += 3; - } else if ((slave.devotion >= -20)) { + } else if (slave.devotion >= -20) { slave.trust -= 5; } else { slave.devotion -= 5; diff --git a/src/endWeek/saLongTermEffects.js b/src/endWeek/saLongTermEffects.js index 02643bb3432fcdaada267a12e3bd00c276ed8488..5994a4517e07ced2ed699c2a1b0f575efd65c5b2 100644 --- a/src/endWeek/saLongTermEffects.js +++ b/src/endWeek/saLongTermEffects.js @@ -2547,7 +2547,7 @@ App.SlaveAssignment.longTermEffects = (function() { } } if (V.seeAge === 1) { - deathSeed = ((slave.health.health * 2) - (slave.physicalAge * 2) - (slave.chem * 4) - (slave.addict * 3)); + deathSeed = ((slave.health.health * 2) - (slave.physicalAge * 2) - (slave.assignment === Job.CLINIC && S.Nurse && V.clinicUpgradeFilters >= 1 ? slave.chem : (slave.chem * 4)) - (slave.addict * 3)); if (slave.physicalAge >= Math.max((70 + (slave.health.health / 5) - (slave.addict) - (slave.chem / 20)), 50) && random(1, 1000) > 800 + deathSeed) { planDeath(slave, "oldAge"); } diff --git a/src/facilities/incubator/incubatorRetrievalWorkaround.tw b/src/facilities/incubator/incubatorRetrievalWorkaround.tw index ba3b56c0f2596d1547b6206c6e4da61a9e55bfc2..b5b886d376c5e9ca7529f534384f90917f24cb9a 100644 --- a/src/facilities/incubator/incubatorRetrievalWorkaround.tw +++ b/src/facilities/incubator/incubatorRetrievalWorkaround.tw @@ -25,14 +25,14 @@ <</if>> <<includeDOM App.UI.newChildIntro($readySlave)>> <<else>> - <<set $activeSlave = $readySlave>> /* $activeSlave is used by husk Slave Swap Workaround */ + <<set $activeSlave = $readySlave>> /* $activeSlave is used by Husk Slave Swap Workaround */ A husk is ready to be used. <br> //As expected, $he is a complete vegetable, but that is what you wanted after all. You lack the facilities to care for $him in this state, so you should do what you are planning quickly. Or you could sell $him to the Flesh Heap.// <<set _price = Math.trunc(slaveCost($readySlave)/3)>> <span id="result"> <<if $cash >= $surgeryCost>> - <br>[[Contact the bodyswap surgeon.|husk Slave Swap Workaround]] //Will significantly increase the selected slave's upkeep.// + <br>[[Contact the bodyswap surgeon.|Husk Slave Swap Workaround]] //Will significantly increase the selected slave's upkeep.// <br>[[Sell the husk to Flesh Heap.|Main][cashX(_price, "slaveTransfer")]] //This body can be bought by the Flesh Heap for <<print cashFormat(_price)>>//. <<else>> diff --git a/src/gui/userButton.js b/src/gui/sideBar.js similarity index 66% rename from src/gui/userButton.js rename to src/gui/sideBar.js index 0c8cf11d416de2f83b950d4cc7650cd3bb5bcfad..9c1e8e3508a06265f8f80745c8a64a4805fae3cf 100644 --- a/src/gui/userButton.js +++ b/src/gui/sideBar.js @@ -1,3 +1,26 @@ +/** Notify the game that the sidebar needs to be refreshed as soon as possible, but do not do it immediately. + * This allows us to call this function repeatedly without impacting performance (for example, from repX() and cashX()). + * The game will redraw the sidebar exactly once, as soon as all the scripts have finished executing. + */ +App.Utils.scheduleSidebarRefresh = (function() { + let refresh = false; + + function updateSidebar() { + refresh = false; + UIBar.update(); + App.UI.updateSidebarTooltips(); + } + + function schedule() { + if (!refresh) { + refresh = true; + setTimeout(updateSidebar, 0); + } + } + + return schedule; +})(); + App.Utils.userButton = function(nextButton = V.nextButton, nextLink = V.nextLink) { const el = document.createElement("span"); el.id = "next-button-wrapper"; // We must always have a named element so we have something to refresh even if the button is hidden for a scene diff --git a/src/interaction/prostheticLab.js b/src/interaction/prostheticLab.js new file mode 100644 index 0000000000000000000000000000000000000000..5a34abceb4f036a6730c7dd06fbdf59bcf0ace65 --- /dev/null +++ b/src/interaction/prostheticLab.js @@ -0,0 +1,26 @@ +/** + * @returns {string} + */ + +globalThis.getProstheticsStockpile = function() { + return `<div>Prosthetics interfaces: ${num(V.prosthetics.interfaceP1.amount + V.prosthetics.interfaceP2.amount)}</div>` + + `<div class="choices">Basic: ${V.prosthetics.interfaceP1.amount}</div>` + + `<div class="choices">Advanced: ${V.prosthetics.interfaceP2.amount}</div>` + + `<div>Limbs: ${num(V.prosthetics.basicL.amount + V.prosthetics.sexL.amount + V.prosthetics.beautyL.amount + + V.prosthetics.combatL.amount + V.prosthetics.cyberneticL.amount)}</div>` + + `<div class="choices">Basic: ${V.prosthetics.basicL.amount}</div>` + + `<div class="choices">Sex: ${V.prosthetics.sexL.amount}</div>` + + `<div class="choices">Beauty: ${V.prosthetics.beautyL.amount}</div>` + + `<div class="choices">Combat: ${V.prosthetics.combatL.amount}</div>` + + `<div class="choices">Cybernetic: ${V.prosthetics.cyberneticL.amount}</div>` + + `<div>Implants: ${num(V.prosthetics.ocular.amount + V.prosthetics.cochlear.amount + V.prosthetics.electrolarynx.amount)}</div>` + + `<div class="choices">Ocular: ${V.prosthetics.ocular.amount}</div>` + + `<div class="choices">Cochlear: ${V.prosthetics.cochlear.amount}</div>` + + /* `<div class="choices">Erectile: ${V.prosthetics.erectile.amount}</div>` + */ + `<div class="choices">Electrolarynx: ${V.prosthetics.electrolarynx.amount}</div>` + + `<div>Tail interface: ${V.prosthetics.interfaceTail.amount}</div>` + + `<div>Tails: ${num(V.prosthetics.modT.amount + V.prosthetics.sexT.amount + V.prosthetics.combatT.amount)}</div>` + + `<div class="choices">Modular: ${V.prosthetics.modT.amount}</div>` + + `<div class="choices">Pleasure: ${V.prosthetics.sexT.amount}</div>` + + `<div class="choices">Combat: ${V.prosthetics.combatT.amount}</div>`; +}; diff --git a/src/js/assayJS.js b/src/js/assayJS.js deleted file mode 100644 index 7329abef92b6e99373cb3ae23185683f10181ff0..0000000000000000000000000000000000000000 --- a/src/js/assayJS.js +++ /dev/null @@ -1,1706 +0,0 @@ -/** - * @param {App.Entity.SlaveState} A - * @param {App.Entity.SlaveState} B - * @returns {boolean} - */ -globalThis.sameAssignmentP = function(A, B) { - return A.assignment === B.assignment; -}; - -/** - * @param {App.Entity.SlaveState} slave - * @returns {boolean} - */ -globalThis.canImproveIntelligence = function(slave) { - let origIntel = V.genePool.find(function(s) { return s.ID === slave.ID; }).intelligence; - return (slave.intelligence < origIntel + 15) && (slave.intelligence < 100); -}; - -/** - * @param {App.Entity.SlaveState} slave - * @returns {number} - */ -globalThis.maxHeight = function(slave) { - let max = Math.trunc(Math.clamp((Height.mean(slave) * 1.25), 0, 274)); /* max achievable height is expected height plus 25% */ - - if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism !== 2) { - max = Math.min(max, 160); - } - - return max; -}; - -/** - * @param {App.Entity.SlaveState} slave - * @returns {boolean} - */ -globalThis.canImproveHeight = function(slave) { - return slave.height < maxHeight(slave); -}; - -/** - * @param {App.Entity.SlaveState} slave - * @param {FC.HumanState} target - * @returns {boolean} - */ -globalThis.haveRelationshipP = function(slave, target) { - return slave.relationshipTarget === target.ID; -}; - -/** - * @param {App.Entity.SlaveState} slave - * @param {App.Entity.SlaveState} target - * @returns {boolean} - */ -globalThis.isRivalP = function(slave, target) { - return slave.rivalryTarget === target.ID; -}; - -/** - * @param {FC.HumanState} slave - * @returns {boolean} - */ -globalThis.supremeRaceP = function(slave) { - return V.arcologies[0].FSSupremacistRace === slave.race; -}; - -/** - * @param {FC.HumanState} slave - * @returns {boolean} - */ -globalThis.inferiorRaceP = function(slave) { - return V.arcologies[0].FSSubjugationistRace === slave.race; -}; - -/** - * @param {App.Entity.SlaveState} slave - * @returns {boolean} - */ -globalThis.isLeaderP = function(slave) { - const leaders = [S.HeadGirl, S.Bodyguard, S.Recruiter, S.Concubine, S.Nurse, S.Attendant, S.Matron, S.Madam, S.DJ, S.Milkmaid, S.Farmer, S.Stewardess, S.Schoolteacher, S.Wardeness]; - - return leaders.some(leader => leader && leader.ID === slave.ID); -}; - -/** - * colors skin, eyes and hair based on genetic Color. - * Takes .override_*_Color into account. - * - * @param {App.Entity.SlaveState} slave - */ -globalThis.applyGeneticColor = function(slave) { - if (slave.override_Eye_Color !== 1) { - resetEyeColor(slave, "both"); - } - if (slave.override_H_Color !== 1) { - slave.hColor = getGeneticHairColor(slave); - } - if (slave.override_Arm_H_Color !== 1) { - slave.underArmHColor = getGeneticHairColor(slave); - } - if (slave.override_Pubic_H_Color !== 1) { - slave.pubicHColor = getGeneticHairColor(slave); - } - if (slave.override_Brow_H_Color !== 1) { - slave.eyebrowHColor = getGeneticHairColor(slave); - } - if (slave.override_Skin !== 1) { - if (!(slave.skin === "sun tanned" || slave.skin === "spray tanned")) { - slave.skin = getGeneticSkinColor(slave); - } - } -}; - -/** - * @param {FC.GingeredSlave} slave - */ -globalThis.newSlave = function(slave) { - if (getSlave(slave.ID)) { - throw "Slave already exists"; - } - - // if the slave is gingered, remove the gingering proxy - if (slave.beforeGingering) { - slave = slave.beforeGingering; - } - - if (slave.override_Race !== 1) { - slave.origRace = slave.race; - } - - applyGeneticColor(slave); - - /* eslint-disable camelcase */ - slave.override_Race = 0; - slave.override_H_Color = 0; - slave.override_Arm_H_Color = 0; - slave.override_Pubic_H_Color = 0; - slave.override_Brow_H_Color = 0; - slave.override_Skin = 0; - slave.override_Eye_Color = 0; - /* eslint-enable camelcase */ - - // too tall to be a dwarf catch for event slaves - if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism !== 2 && slave.height > 165) { - slave.geneticQuirks.dwarfism = 1; - } - - if (V.surnamesForbidden === 1) { - slave.slaveSurname = 0; - } - - if (slave.preg > 0) { - slave.pregWeek = slave.preg; - } else { - slave.pregWeek = 0; - } - - if (slave.clone !== 0) { - slave.canRecruit = 0; - } - - slave.sisters = 0; - slave.daughters = 0; - if (slave.mother === -1 || slave.father === -1) { - V.PC.daughters += 1; - } - if (areSisters(V.PC, slave) > 0) { - V.PC.sisters += 1; - } - for (let k = 0; k < V.slaves.length; k++) { - if (V.slaves[k].mother === slave.ID || V.slaves[k].father === slave.ID) { - slave.daughters++; - } - if (slave.mother === V.slaves[k].ID || slave.father === V.slaves[k].ID) { - V.slaves[k].daughters++; - } - if (areSisters(V.slaves[k], slave) > 0) { - slave.sisters++; - V.slaves[k].sisters++; - } - } - - if (slave.genes === "XX") { - if (slave.pubertyXX === 1) { - if (slave.pubertyXY === 1) { - slave.hormoneBalance = 20; - } else { - slave.hormoneBalance = 60; - } - } else { - if (slave.pubertyXY === 1) { - slave.hormoneBalance = -20; - } else { - slave.hormoneBalance = 20; - } - } - } else if (slave.genes === "XY") { - if (slave.pubertyXX === 1) { - if (slave.pubertyXY === 1) { - slave.hormoneBalance = 20; - } else { - slave.hormoneBalance = 40; - } - } else { - if (slave.pubertyXY === 1) { - slave.hormoneBalance = -40; - } else { - slave.hormoneBalance = 20; - } - } - } - - if (slave.dick > 0 && - slave.balls > 0 && - slave.vagina < 0 && - slave.anus === 0 && - slave.genes === "XY" && - slave.faceShape === "masculine" && - slave.attrXY <= 35 && - slave.boobs < 400 && - slave.hormoneBalance < 0) { - V.REFeminizationCheckinIDs.push(slave.ID); - } - if (slave.actualAge > 35 && slave.face <= 10 && slave.faceImplant === 0 && slave.energy <= 60) { - V.REMILFCheckinIDs.push(slave.ID); - } - if (slave.attrXY <= 35 && slave.attrXX > 65) { - V.REOrientationCheckinIDs.push(slave.ID); - } - if (slave.face < -10) { - V.REUglyCheckinIDs.push(slave.ID); - } - if (slave.anus < 2) { - V.REButtholeCheckinIDs.push(slave.ID); - } - if (slave.boobs < 800) { - V.REReductionCheckinIDs.push(slave.ID); - } - - generatePronouns(slave); - SetBellySize(slave); - V.slaveIndices[slave.ID] = V.slaves.push(slave) - 1; - - if (slave.origin !== "$He was your slave, but you freed $him, which $he repaid by participating in a coup attempt against you. It failed, and $he is again your chattel." && slave.ID !== V.boomerangSlave.ID) { - V.genePool.push(clone(slave)); - } else { - if (!V.genePool.some(s => s.ID === slave.ID)) { - V.genePool.push(slave); - } - } - - /* special case for dulling intelligence via drugs in slave acquisition */ - if (slave.dullIntelligence) { - slave.intelligence = -100; - delete slave.dullIntelligence; - } - - if (slave.assignment) { - assignJob(slave, slave.assignment); - } else { - slave.assignment = Job.CHOICE; - } - - /** do not run the Rules Assistant before adding the new slave to the slaves list! **/ - if (V.ui !== "start" && V.universalRulesNewSlavesRA === 1 && V.rulesAssistantAuto !== 0) { - DefaultRules(slave); - } -}; - -/** - * @param {App.Entity.SlaveState[]} [slaves] - * @returns {Object.<number, number>} - */ -globalThis.slaves2indices = function(slaves = V.slaves) { - return slaves.reduce((acc, slave, i) => { acc[slave.ID] = i; return acc; }, {}); -}; - -/** - * @param {number} ID - * @returns {App.Entity.SlaveState} - */ -globalThis.getSlave = function(ID) { - const index = V.slaveIndices[ID]; - return index === undefined ? undefined : V.slaves[index]; -}; - -/** - * @param {number} ID - * @returns {App.Entity.SlaveState} - */ -globalThis.slaveStateById = function(ID) { - const index = V.slaveIndices[ID]; - return index === undefined ? null : V.slaves[index]; -}; - -globalThis.getChild = function(ID) { - return V.cribs.find(s => s.ID === ID); -}; - -/** Get the written title for a given slave, without messing with global state. - * @param {App.Entity.SlaveState} [slave] - * @returns {string} - */ -globalThis.getWrittenTitle = function(slave) { - if (slave && slave.custom.title !== undefined && slave.custom.title !== "" && slave.rudeTitle === 0) { - return slave.custom.title; - } - if (V.PC.customTitle !== undefined) { - return V.PC.customTitle; - } else if (V.PC.title !== 0) { - return "Master"; - } else { - return "Mistress"; - } -}; - -/** - * @param {App.Entity.SlaveState} slave - * @returns {number} - */ -globalThis.fetishChangeChance = function(slave) { - let chance = 0, - fetish = (slave.fetishStrength / 4), - sex = 0; - - if (slave.clitSetting !== slave.fetish) { - // fetish should be more uncertain leading towards puberty and then steadily become more set in stone afterwards - if (slave.balls) { - if (V.potencyAge >= slave.actualAge) { - sex = (50 - ((V.potencyAge - slave.actualAge) * 10)); - fetish = (slave.fetishStrength / 2); - } else { - sex = ((slave.actualAge - V.potencyAge) / 4); - } - } else if (slave.ovaries || slave.mpreg) { - if (V.fertilityAge >= slave.actualAge) { - sex = (50 - ((V.fertilityAge - slave.actualAge) * 10)); - fetish = (slave.fetishStrength / 2); - } else { - sex = ((slave.actualAge - V.fertilityAge) / 4); - } - } - chance = Math.trunc(Math.clamp((slave.devotion / 4) - (fetish) - (sex), 0, 100)); - } - - return chance; -}; - -/** - * @param {App.Entity.SlaveState} slave - * @returns {string} - */ -globalThis.SlaveFullName = function(slave) { - const pair = slave.slaveSurname ? [slave.slaveName, slave.slaveSurname] : [slave.slaveName]; - if ((V.surnameOrder !== 1 && ["Cambodian", "Chinese", "Hungarian", "Japanese", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes(slave.nationality)) || (V.surnameOrder === 2)) { - pair.reverse(); - } - return pair.join(" "); -}; - -/** - * @param {App.Entity.SlaveState} slave - * @returns {string} - */ -globalThis.SlaveFullBirthName = function(slave) { - const pair = slave.birthSurname ? [slave.birthName, slave.birthSurname] : [slave.birthName]; - if ((V.surnameOrder !== 1 && ["Cambodian", "Chinese", "Hungarian", "Japanese", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes(slave.nationality)) || (V.surnameOrder === 2)) { - pair.reverse(); - } - return pair.join(" "); -}; - -/** - * @param {App.Entity.SlaveState} slave - * @returns {string} - */ -globalThis.PoliteRudeTitle = function(slave) { - const PC = V.PC; - const {s, ss, title} = getEnunciation(slave); - - let r = ""; - if (slave.nationality === "Japanese") { - if (slave.trust > 0) { - r += `${PC.slaveName}${PC.title > 0 ? "kun" : "chan"}`; - } else { - r += (PC.slaveSurname ? PC.slaveSurname : `${PC.slaveName}${s}an`); - } - } else { - if (slave.intelligence + slave.intelligenceImplant < -95) { - r += title; - } else if (slave.intelligence + slave.intelligenceImplant > 50) { - r += (PC.title > 0 ? `Ma${s}ter` : `Mi${s}tre${ss}`); - } else if (slave.trust > 0) { - r += PC.slaveName; - } else { - r += (PC.slaveSurname ? PC.slaveSurname : PC.slaveName); - } - } - return r; -}; - -/** - * @param {App.Entity.SlaveState} slave - * @returns {string} - */ -globalThis.SlaveTitle = function(slave) { - let r; - if (V.newDescriptions === 1) { - if (slave.dick > 0 && slave.balls > 0 && slave.boobs > 300 && slave.vagina > -1 && slave.ovaries === 1) { - if (jsRandom(1, 100) > 50) { - r = "futanari"; - } else { - r = "herm"; - } - } else if (slave.dick > 0 && slave.balls === 0 && slave.boobs > 300 && slave.vagina > -1 && slave.ovaries === 1) { - r = "dickgirl"; - } else if (slave.dick > 0 && slave.vagina > -1 && slave.ovaries === 0) { - r = "shemale"; - } else if (slave.dick > 0 && slave.balls === 0 && slave.vagina === -1 && slave.ovaries === 0) { - r = "eunuch"; - } else if (slave.dick > 0 && slave.balls > 0 && slave.vagina === -1 && slave.ovaries === 0) { - if (slave.face > 10 && slave.hips > -1 && slave.shoulders < 1 && slave.faceShape !== "masculine") { - r = "trap"; - } else if (slave.boobs > 800) { - r = "tittyboy"; - } else if (slave.dick === 1 && slave.balls === 1) { - r = "sissy"; - } else if (slave.dick > 1 && slave.balls > 1 && slave.height < 165 && slave.muscles < 5 && slave.visualAge < 19 && slave.faceShape !== "masculine") { - r = "twink"; - } else if (slave.dick > 1 && slave.balls > 1 && slave.height < 160 && slave.muscles < 5 && slave.visualAge < 19) { - r = "boytoy"; - } else if (slave.muscles > 95 && slave.height >= 185) { - r = "titan"; - } else if (slave.muscles > 30) { - r = "muscleboy"; - } else { - r = "slaveboy"; - } - } else if (slave.dick === 0 && slave.balls === 0 && slave.vagina > -1) { - if ((slave.shoulders > slave.hips || slave.faceShape === "masculine") && slave.boobs < 400 && slave.genes === "XY") { - r = "cuntboy"; - } else if (slave.ovaries === 0 && slave.genes === "XY") { - r = "tranny"; - } else if (slave.weight > 10 && slave.boobs > 800 && slave.counter.birthsTotal > 0 && slave.physicalAge > 59) { - r = "GMILF"; - } else if (slave.weight > 10 && slave.boobs > 800 && slave.counter.birthsTotal > 0 && slave.physicalAge > 35) { - r = "MILF"; - } else if (slave.lips > 70 && slave.boobs > 2000 && slave.butt > 3) { - r = "bimbo"; - } else if (slave.hips > 1 && slave.boobs > 2000 && slave.butt > 3 && slave.waist < 50) { - r = "hourglass"; - } else if (slave.muscles > 95 && slave.height >= 185) { - r = "amazon"; - } else if (slave.muscles > 30) { - r = "musclegirl"; - } else { - r = "slavegirl"; - } - } else if (slave.dick === 0 && slave.balls === 0 && slave.vagina === -1) { - r = "neuter"; - } else if (slave.dick === 0 && slave.vagina === -1) { - r = "ballslave"; - } else { - r = "slave"; - } - - if (slave.visualAge < 13) { - if (slave.actualAge < 3) { - if (slave.actualAge < 1) { - r = "baby " + r; - } else { - r = "toddler " + r; - } - } else { - if (slave.genes === "XY" && slave.vagina === -1) { - r = "shota " + r; - } else { - r = "loli " + r; - } - } - } - - if (slave.geneticQuirks.albinism === 2) { - r = `albino ${r}`; - } - - if (slave.dick > 9 && slave.balls > 9 && slave.boobs > 12000) { - r = `hyper ${r}`; - } - - if (slave.boobs > 4000 && slave.lactation > 0) { - if (slave.physicalAge < 13) { - r = `${r} calf`; - } else { - r = `${r} cow`; - } - } else if (slave.lactation > 0) { - r = `milky ${r}`; - } - - if (slave.boobs > 20000) { - r = `supermassive titted ${r}`; - } else if (slave.boobs > 10000) { - r = `giant titted ${r}`; - } else if (slave.boobs > 4000) { - r = `huge titted ${r}`; - } else if (slave.boobs > 1000) { - r = `busty ${r}`; - } - - if (slave.dick > 5 && slave.balls > 5) { - r = `womb filling ${r}`; - } else if (slave.dick > 5) { - r = `well hung ${r}`; - } - - if (slave.butt >= 12) { - r = `colossal assed ${r}`; - } else if (slave.butt >= 10) { - r = `massive assed ${r}`; - } else if (slave.butt >= 8) { - r = `fat assed ${r}`; - } else if (slave.butt >= 6) { - r = `bottom heavy ${r}`; - } else if (slave.butt >= 4) { - r = `big bottomed ${r}`; - } - - if (slave.weight > 10 && slave.weight < 100 && slave.boobs > 5000 && slave.butt > 5 && slave.hips >= 2 && slave.bellyPreg >= 30000 && slave.counter.births >= 10) { - r = `${r} fertility goddess`; - } else if (slave.counter.births >= 6) { - r = `${r} broodmother`; - } else if (slave.counter.births >= 3) { - r = `${r} breeder`; - } - - if (slave.indenture > -1) { - r = `indentured ${r}`; - } - - if (slave.preg > slave.pregData.normalBirth / 4 && slave.pregKnown === 1) { - r = `pregnant ${r}`; - } else if (slave.bellyFluid >= 5000) { - r = `bloated ${r}`; - } else if (slave.belly >= 5000) { - r = `gravid ${r}`; - } - - if (slave.fuckdoll > 0) { - r = `${r} fuckdoll`; - } - } else { - r = "slave"; /* I don't think there is an 'else'? */ - if ((slave.dick === 0) && (slave.vagina === -1)) { - /* NULLS */ - r = "null"; - if ((slave.lactation > 0) && (slave.boobs > 2000)) { - r = `${r} cow`; - } else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) { - r = `${r} bimbo `; - } else if (slave.boobs > 6000) { - r = `${r} boob`; - } else if (slave.butt > 6) { - r = `${r} ass`; - } else if ((slave.muscles > 30) && (slave.height < 185)) { - r = `${r} muscle`; - } - if (slave.visualAge > 55) { - r = `${r}GILF`; - } else if (slave.visualAge > 35) { - r = `${r}MILF`; - } else if (slave.visualAge >= 25) { - r = `${r}slave`; - } else { - r = `${r}girl`; - } - } - - if ((slave.dick === 0) && (slave.vagina !== -1)) { - /* FEMALES */ - if (slave.visualAge > 55) { - r = "GILF"; - } else if (slave.visualAge > 35) { - r = "MILF"; - } else if (slave.visualAge >= 25) { - r = "slave"; - } else { - r = "slavegirl"; - } - if ((slave.muscles > 30) && (slave.height < 185)) { - r = `muscle ${r}`; - } else if ((slave.lactation > 0) && (slave.boobs > 2000)) { - r = `${r} cow`; - } else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) { - r = `${r} bimbo`; - } else if (slave.boobs > 6000) { - r = `boob${r}`; - } else if (slave.butt > 6) { - r = `ass${r}`; - } - } - - if ((slave.dick !== 0) && (slave.vagina !== -1)) { - if (slave.balls > 0) { - /* FUTANARI: cock & balls & vagina */ - r = "futanari "; - } else { - /* FUTANARI: cock & vagina */ - r = "futa "; - } - if ((slave.lactation > 0) && (slave.boobs > 2000)) { - r = `${r}cow`; - } else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) { - r = `${r}bimbo `; - } else if (slave.boobs > 6000) { - r = `${r}boob`; - } else if (slave.butt > 6) { - r = `${r}ass`; - } else if ((slave.muscles > 30) && (slave.height < 185)) { - r = `${r}muscle`; - } - if (slave.visualAge > 55) { - r = `${r}GILF`; - } else if (slave.visualAge > 35) { - r = `${r}MILF`; - } else if (slave.visualAge >= 25) { - r = `${r}slave`; - } else { - r = `${r}girl`; - } - if (slave.dick > 5 && slave.balls > 5 && slave.boobs > 5000) { - r = `hyper ${r}`; - } - } - - if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls > 0) && (slave.boobs > 300) && (slave.butt > 2)) { - /* SHEMALES: cock & balls, T&A above minimum */ - if (slave.visualAge > 55) { - r = "sheGILF"; - } else if (slave.visualAge > 35) { - r = "sheMILF"; - } else if (slave.visualAge >= 25) { - r = "shemale"; - } else { - r = "tgirl"; - } - if ((slave.muscles > 30) && (slave.height < 185)) { - r = `muscle${r}`; - } else if ((slave.lactation > 0) && (slave.boobs > 2000)) { - r = `${r} cow`; - } else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) { - r = `${r} bimbo`; - } else if (slave.boobs > 6000) { - r = `topheavy ${r}`; - } else if (slave.butt > 6) { - r = `bottomheavy ${r}`; - } - } - - if ((slave.boobs < 300) || (slave.butt < 2)) { - if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls > 0)) { - if ((slave.shoulders < 1) || (slave.muscles <= 30)) { - if ((slave.faceShape === "masculine") || (slave.faceShape === "androgynous")) { - /* SISSIES: feminine shoulders or muscles, masculine faces */ - if (slave.visualAge > 55) { - r = "sissyGILF"; - } else if (slave.visualAge > 35) { - r = "sissyMILF"; - } else { - r = "sissy"; - } - } else { - /* TRAPS: feminine shoulders or muscles, feminine faces */ - if (slave.visualAge > 55) { - r = "trapGILF"; - } else if (slave.visualAge > 35) { - r = "trapMILF"; - } else if (slave.visualAge >= 25) { - r = "trap"; - } else { - r = "trapgirl"; - } - } - if (slave.lactation > 0) { - r = `${r} cow`; - } else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) { - r = `${r} bimbo`; - } - } - } - } - - if ((slave.boobs < 300) || (slave.butt < 2)) { - if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls > 0)) { - if ((slave.shoulders > 1) || (slave.muscles >= 30)) { - /* BITCHES: masculine shoulders or muscles */ - r = "bitch"; - if ((slave.muscles > 30) && (slave.height < 185)) { - r = `muscle${r}`; - } else if (slave.lactation > 0) { - r = `${r}cow`; - } else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) { - r = `bimbo ${r}`; - } - if (slave.visualAge > 55) { - r = `aged ${r}`; - } else if (slave.visualAge > 35) { - r = `mature ${r}`; - } else if (slave.visualAge < 25) { - r = `young ${r}`; - } - } - } - } - - if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls === 0)) { - r = "dick"; - if (slave.visualAge > 55) { - r = `${r}GILF`; - } else if (slave.visualAge > 35) { - r = `${r}MILF`; - } else if (slave.visualAge >= 25) { - r = `${r}slave`; - } else { - r = `${r}girl`; - } - if ((slave.muscles > 30) && (slave.height < 185)) { - r = `muscle${r}`; - } else if ((slave.lactation > 0) && (slave.boobs > 2000)) { - r = `${r} cow`; - } else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) { - r = `${r} bimbo`; - } else if (slave.boobs > 6000) { - r = `boob ${r}`; - } else if (slave.butt > 6) { - r = `ass ${r}`; - } - } - - if ((slave.muscles > 30) && (slave.height > 185)) { - r = `amazon ${r}`; - } else if ((slave.muscles < 30) && (slave.height > 185)) { - r = `statuesque ${r}`; - } else if ((slave.boobs < 800) && (slave.height < 150)) { - r = `petite ${r}`; - } else if ((slave.boobs > 800) && (slave.height < 150)) { - r = `shortstack ${r}`; - } - - if (slave.counter.births >= 5) { - r = `${r} broodmother`; - } else if (slave.counter.births >= 2) { - r = `${r} breeder`; - } - - if (slave.geneticQuirks.albinism === 2) { - r = `albino ${r}`; - } - - if (slave.indenture > -1) { - r = `indentured ${r}`; - } - - if (slave.preg > slave.pregData.normalBirth / 4 && slave.pregKnown === 1) { - r = `pregnant ${r}`; - } else if (slave.bellyFluid >= 5000) { - r = `bloated ${r}`; - } else if (slave.belly >= 5000) { - r = `gravid ${r}`; - } - - if (slave.fuckdoll > 0) { - r = `${r} fuckdoll`; - } - } - return r; -}; - -/** - * @param {App.Entity.SlaveState} slave - */ -globalThis.DegradingName = function(slave) { - const leadershipPosition = [ - Job.ATTENDANT, - Job.MATRON, - Job.STEWARD, - Job.MILKMAID, - Job.FARMER, - Job.DJ, - Job.CONCUBINE, - Job.MADAM, - Job.TEACHER, - Job.WARDEN, - Job.NURSE, - Job.HEADGIRL, - Job.BODYGUARD, - Job.RECRUITER - ]; - const names = []; - const suffixes = []; - - if (slave.fuckdoll > 0) { - slave.slaveName = `Fuckdoll No. ${slave.ID}`; - slave.slaveSurname = 0; - } else if (slave.assignment === Job.DAIRY && V.dairyRestraintsSetting >= 2) { - slave.slaveName = `Bioreactor No. ${slave.ID}`; - slave.slaveSurname = 0; - } else { - if (V.seeRace === 1) { - switch (slave.race) { - case "white": - names.push("Pale", "White"); - break; - case "asian": - names.push("Asian", "Yellow"); - break; - case "latina": - names.push("Brown", "Latina"); - break; - case "black": - names.push("Black", "Dark"); - break; - case "pacific islander": - names.push("Islander", "Pacific", "Sea"); - break; - case "malay": - names.push("Cinnamon", "Pinoy", "Spice"); - break; - case "southern european": - names.push("Mediterranean", "Olive"); - break; - case "amerindian": - names.push("Indian", "Reservation"); - break; - case "semitic": - names.push("Semite", "Semitic"); - break; - case "middle eastern": - names.push("Arab", "Sand"); - break; - case "indo-aryan": - names.push("Brown", "Indian"); - break; - case "mixed race": - names.push("Mixed", "Mulatto", "Mutt"); - break; - } - } - names.push(slave.hColor); - if (!hasAnyEyes(slave)) { - names.push("Blind", "Eyeless", "Sightless"); - } - if (slave.hears === -2) { - names.push("Deaf", "Earless", "Unhearing"); - } - if (slave.boobs >= 2000) { - suffixes.push("Boob", "Boobs", "Titty"); - } - if (slave.boobs < 500 && slave.butt < 3) { - names.push("Girly", "Slim", "Thin"); - } - if (slave.boobs < 300) { - names.push("Flat"); - } - if (slave.anus > 2 || slave.vagina > 2) { - names.push("Gaping", "Hallway", "Slit", "Wideopen"); - } - if (slave.weight > 160) { - names.push("Blimp", "Cow", "Fat", "Fatass", "Whale"); - } else if (slave.weight > 30) { - names.push("Chubby", "Fat", "Whale"); - } else if (slave.weight <= -30) { - names.push("Bony", "Rail", "Skinny"); - } - if (slave.muscles > 30) { - names.push("Huge", "Muscles", "Ripped", "Strong"); - } - if (slave.fetishKnown === 1) { - if (slave.fetish === "buttslut") { - names.push("Anal", "Sodomy"); - } - if (slave.fetish === "cumslut") { - names.push("Cum", "Dicksuck", "Sucker"); - } - if (slave.fetish === "humiliation") { - names.push("Rape"); - } - if (slave.fetish === "masochist") { - names.push("Pain", "Rape", "Struggle"); - } - if (slave.fetish === "pregnancy") { - names.push("Fertile"); - } - } - if (slave.boobs * slave.lactation > 1000) { - names.push("Creamy", "Milky"); - suffixes.push("Cow"); - } - if (slave.skill.oral <= 30 && slave.skill.anal <= 30) { - names.push("Cheap", "Fail", "Gutter"); - } - if (slave.nipples === "fuckable") { - names.push("Nipplefuck", "Nipplecunt"); - } else if (slave.nipples !== "tiny" && slave.nipples !== "cute") { - names.push("Pointy", "Titclit"); - suffixes.push("Nipples"); - } - if (slave.visualAge > 35) { - names.push("Mature"); - suffixes.push("Cougar", "MILF"); - } else if (slave.visualAge < 25) { - names.push("Girly", "Thin", "Young"); - } - if (isAmputee(slave)) { - names.push("Stumpy"); - suffixes.push("Stumpy"); - } - if (slave.boobsImplant > 1000 || slave.buttImplant > 3) { - names.push("Fake", "Plastic", "Silicone"); - } - if (slave.dick > 5 && slave.balls > 5) { - names.push("Potent"); - suffixes.push("Cannon", "Daddy"); - } - if (slave.preg > slave.pregData.normalBirth / 1.33) { - if (slave.broodmother === 2) { - names.push("Bursting", "Seeded"); - suffixes.push("Factory", "Nursery"); - } else if (slave.broodmother === 1) { - names.push("Bloated", "Stuffed"); - suffixes.push("Breeder", "Factory"); - } - } - if (slave.bellyPreg >= 450000) { - names.push("Bulging", "Squirming"); - } - if (slave.bellyPreg >= 5000) { - names.push("Preg"); - suffixes.push("Belly", "Mommy"); - } - if (slave.belly > 150000) { - suffixes.push("Balloon"); - } - if (slave.belly > 1500) { - suffixes.push("Belly"); - } - if (slave.dick > 0) { - if (slave.dick > 4) { - names.push("Dangle", "Hung"); - suffixes.push("Cock", "Dick"); - } - if (slave.balls === 0) { - names.push("Cut", "Gelded", "Soft"); - } else { - names.push("Erect", "Hard", "Stiff"); - } - } - if (slave.dick === 1) { - names.push("Micro", "Tiny"); - suffixes.push("Bitch"); - } - if (slave.height >= 185) { - names.push("Tall", "Top"); - suffixes.push("Tower"); - } else if (slave.height < 150) { - names.push("Stumpy", "Tiny"); - suffixes.push("Shortstack", "Stumpy"); - } - if (slave.skill.whoring > 95) { - names.push("Money", "Street"); - suffixes.push("Whore"); - } - if (slave.skill.entertainment > 95) { - names.push("Easy", "Club"); - suffixes.push("Slut"); - } - if (slave.skill.oral > 95) { - names.push("Suck"); - suffixes.push("Throat"); - } - if (slave.skill.vaginal > 95) { - suffixes.push("Channel", "Kegel", "Pussy"); - } - if (slave.skill.anal > 95) { - suffixes.push("Asspussy", "Sphincter"); - } - if (slave.intelligence + slave.intelligenceImplant > 50) { - names.push("Bright", "Clever", "Smart"); - if (slave.intelligenceImplant >= 15) { - names.push("College", "Graduate", "Nerdy"); - } - } else if (slave.intelligence + slave.intelligenceImplant < -50) { - names.push("Cretin", "Dumb", "Retarded", "Stupid"); - } - if (slave.vagina === 1 && slave.skill.vaginal <= 10) { - names.push("Fresh", "New", "Tight"); - } - if (slave.devotion < -75) { - names.push("Angry", "Biter", "Caged"); - } else if (slave.devotion < -50) { - names.push("Cell", "Cuffs"); - } else if (slave.devotion < -20) { - names.push("Bag", "Box"); - } else if (slave.devotion <= 20) { - names.push("Sad", "Whiner"); - } else if (slave.devotion > 50) { - names.push("Prize"); - if (slave.visualAge > 35) { - names.push("Queen"); - } else if (slave.visualAge < 25) { - names.push("Princess"); - } - } - if (slave.trust < -50) { - names.push("Screaming"); - suffixes.push("Sobber"); - } else if (slave.trust < -20) { - names.push("Crying"); - suffixes.push("Meat", "Tears", "Thing", "Weeper"); - } else if (slave.trust < 20) { - names.push("Begging"); - } - - if (slave.dick === 0) { - if (slave.vagina === -1) { - suffixes.push("Null"); - } else { - if (slave.visualAge < 25) { - suffixes.push("Girl"); - } - } - } else { - if (slave.vagina !== -1) { - suffixes.push("Futa"); - } else { - if (slave.balls > 0) { - if (slave.boobs > 300 && slave.butt > 2) { - /* SHEMALES: cock & balls, T&A above minimum */ - suffixes.push("Shemale"); - } else { - if (slave.shoulders < 1 && slave.muscles <= 30) { - if (slave.faceShape === "masculine" || slave.faceShape === "androgynous") { - /* SISSIES: feminine shoulders or muscles, masculine faces */ - suffixes.push("Sissy"); - } else { - /* TRAPS: feminine shoulders or muscles, feminine faces */ - suffixes.push("Trap"); - } - } else { - /* BITCHES: masculine shoulders or muscles */ - suffixes.push("Bitch"); - } - } - } else { - if (slave.visualAge > 35) { - suffixes.push("DickMILF"); - } else if (slave.visualAge >= 25) { - suffixes.push("Dickslave"); - } else { - suffixes.push("Dickgirl"); - } - } - } - } - if (slave.anus > 0) { - suffixes.push("Anus", "Asshole", "Backdoor", "Butt", "Butthole"); - } - if (slave.anus === 1) { - suffixes.push("Tightass", "Tightbutt"); - } - if (slave.vagina > 0) { - suffixes.push("Cunt", "Pussy", "Vagina"); - } - if (slave.boobs < 500 && slave.butt < 3 && slave.dick > 0) { - suffixes.push("Bitch", "Bottom", "Sissy", "Trap"); - } - if (slave.energy > 95) { - suffixes.push("Fuck", "Fuckaddict", "Nympho", "Sexaddict"); - } - if (slave.fetishKnown === 1) { - if (slave.fetish === "humiliation") { - suffixes.push("Rapebait", "Showgirl"); - } - if (slave.fetish === "submissive") { - suffixes.push("Bottom", "Fuckee", "Rapebait"); - } - if (slave.fetish === "dom") { - suffixes.push("Dom", "Fucker", "Top"); - } - if (slave.fetish === "pregnancy") { - suffixes.push("Breeder", "Mommy"); - } - if (slave.fetish === "boobs") { - suffixes.push("Boob", "Boobie", "Tit", "Titty"); - } - } - if (slave.counter.births >= 2) { - suffixes.push("Breeder"); - if (slave.counter.births >= 5) { - suffixes.push("Broodmother"); - } - } - if (slave.areolae > 2) { - suffixes.push("Areolas", "Headlights"); - } - if (slave.lips > 40) { - suffixes.push("Lips", "Pillows"); - } - if (slave.labia > 1) { - suffixes.push("Curtains", "Flower", "Lips"); - } - if (slave.breedingMark === 1 && V.propOutcome === 1 && V.arcologies[0].FSRestart !== "unset") { - suffixes.push("Breeder", "Oven", "Womb"); - } - if (slave.butt > 5) { - suffixes.push("Ass", "Bottom", "Butt"); - } - if (slave.vagina === 0) { - suffixes.push("Virgin"); - } - - slave.slaveName = jsEither(names); - } - if (leadershipPosition.includes(slave.assignment)) { - switch (slave.assignment) { - case Job.ATTENDANT: - slave.slaveName = jsEither(["Bath", "Spa"]); - break; - case Job.MATRON: - slave.slaveName = jsEither(["Matron", "Nursery"]); - break; - case Job.STEWARD: - slave.slaveName = jsEither(["Maid", "Servant"]); - break; - case Job.MILKMAID: - if (V.cumSlaves > 3) { - slave.slaveName = jsEither(["Fucker", "Milker"]); - } else { - slave.slaveName = jsEither(["Dairy", "Farm"]); - } - break; - case Job.FARMER: - slave.slaveName = jsEither(["Farmer", "Farmhand"]); - break; - case Job.DJ: - slave.slaveName = jsEither(["Bass", "Booth"]); - break; - case Job.CONCUBINE: - slave.slaveName = jsEither(["Bed", "Master"]); - break; - case Job.MADAM: - slave.slaveName = jsEither(["Madam", "Pimp"]); - break; - case Job.TEACHER: - slave.slaveName = jsEither(["Classroom", "Teacher"]); - break; - case Job.WARDEN: - slave.slaveName = jsEither(["Jail", "Prison"]); - break; - case Job.NURSE: - slave.slaveName = jsEither(["Clinic", "Nurse"]); - break; - case Job.HEADGIRL: - slave.slaveName = jsEither(["Chief", "Head"]); - break; - case Job.BODYGUARD: - slave.slaveName = jsEither(["Battle", "Guard"]); - break; - case Job.RECRUITER: - slave.slaveName = jsEither(["Cam", "Recruiter"]); - break; - } - } - const surname = jsEither(suffixes); - if (typeof surname === "string" && surname.toLowerCase() === slave.slaveName.toLowerCase()) { - DegradingName(slave); - } - slave.slaveName = capFirstChar(slave.slaveName); - slave.slaveSurname = surname; -}; - -globalThis.PaternalistName = function(slave) { - if (slave.slaveName.search("Miss") === -1) { - if (slave.slaveName.search("Ms.") === -1) { - if (slave.slaveName.search("Mrs.") === -1) { - if (slave.relationship > 4) { - slave.slaveName = ("Mrs. " + slave.slaveName); - } else if (slave.actualAge > 24) { - slave.slaveName = ("Ms. " + slave.slaveName); - } else { - slave.slaveName = ("Miss " + slave.slaveName); - } - } - } - } -}; - -globalThis.parentNames = function(parent, child) { - const slaves = V.slaves; - - let currentSlaveNames = slaves.map(s => s.slaveName); - let continentNationality; - const useMaleName = (child.genes === "XY" && V.allowMaleSlaveNames === true); - - child.slaveName = generateName(parent.nationality, child.race, useMaleName, sn => !currentSlaveNames.includes(sn)); - - if (!child.slaveName) { - for (let i = 0; i < 10; i++) { - continentNationality = hashChoice(V.nationalities); - child.slaveName = generateName(continentNationality, child.race, useMaleName, sn => !currentSlaveNames.includes(sn)); // jshint ignore: line - } - } - if (!child.slaveName) { - child.slaveName = generateName(parent.nationality, child.race, useMaleName); - } -}; - -globalThis.SlaveSort = function() { - const effectivePreg = (slave) => { - // slave.preg is only *mostly* usable for sorting - if (slave.preg > 0 && !slave.pregKnown) { - // don't reveal unknown pregnancies - return 0; - } - if (slave.pubertyXX === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) { - // not ovulating yet - sort between barren slaves and slaves on contraceptives - return -1.2; - } else if (slave.ovaryAge >= 47 && (slave.ovaries === 1 || slave.mpreg === 1)) { - // menopausal - sort between barren slaves and slaves on contraceptives - return -1.1; - } else if (slave.pregWeek < 0) { - // postpartum - sort between slaves on contraceptives and fertile slaves - return -0.1; - } - return slave.preg; - }; - - const effectiveEnergy = (slave) => { - return slave.attrKnown === 1 ? slave.energy : -101; - }; - - const comparators = { - Aassignment: (a, b) => a.assignment < b.assignment ? -1 : 1, - Dassignment: (a, b) => a.assignment > b.assignment ? -1 : 1, - Aname: (a, b) => a.slaveName < b.slaveName ? -1 : 1, - Dname: (a, b) => a.slaveName > b.slaveName ? -1 : 1, - Aseniority: (a, b) => b.weekAcquired - a.weekAcquired, - Dseniority: (a, b) => a.weekAcquired - b.weekAcquired, - AactualAge: (a, b) => a.actualAge - b.actualAge, - DactualAge: (a, b) => b.actualAge - a.actualAge, - AvisualAge: (a, b) => a.visualAge - b.visualAge, - DvisualAge: (a, b) => b.visualAge - a.visualAge, - AphysicalAge: (a, b) => a.physicalAge - b.physicalAge, - DphysicalAge: (a, b) => b.physicalAge - a.physicalAge, - Adevotion: (a, b) => a.devotion - b.devotion, - Ddevotion: (a, b) => b.devotion - a.devotion, - AID: (a, b) => a.ID - b.ID, - DID: (a, b) => b.ID - a.ID, - AweeklyIncome: (a, b) => a.lastWeeksCashIncome - b.lastWeeksCashIncome, - DweeklyIncome: (a, b) => b.lastWeeksCashIncome - a.lastWeeksCashIncome, - Ahealth: (a, b) => a.health.health - b.health.health, - Dhealth: (a, b) => b.health.health - a.health.health, - Aweight: (a, b) => a.weight - b.weight, - Dweight: (a, b) => b.weight - a.weight, - Amuscles: (a, b) => a.muscles - b.muscles, - Dmuscles: (a, b) => b.muscles - a.muscles, - AsexDrive: (a, b) => effectiveEnergy(a) - effectiveEnergy(b), - DsexDrive: (a, b) => effectiveEnergy(b) - effectiveEnergy(a), - Apregnancy: (a, b) => effectivePreg(a) - effectivePreg(b), - Dpregnancy: (a, b) => effectivePreg(b) - effectivePreg(a), - }; - - return { - slaves: sortSlaves, - IDs: sortIDs, - indices: sortIndices - }; - - /** @param {App.Entity.SlaveState[]} [slaves] */ - function sortSlaves(slaves) { - slaves = slaves || V.slaves; - slaves.sort(_comparator()); - if (slaves === V.slaves) { - V.slaveIndices = slaves2indices(); - } - } - - /** @param {number[]} [slaveIDs] */ - function sortIDs(slaveIDs) { - const slaves = V.slaves; - const slaveIndices = V.slaveIndices; - const cmp = _comparator(); - slaveIDs = slaveIDs || slaves.map(s => s.ID); - slaveIDs.sort((IDa, IDb) => cmp(slaves[slaveIndices[IDa]], slaves[slaveIndices[IDb]])); - } - - /** @param {number[]} [slaveIdxs] */ - function sortIndices(slaveIdxs) { - const slaves = V.slaves; - const cmp = _comparator(); - slaveIdxs = slaveIdxs || [...slaves.keys()]; - slaveIdxs.sort((ia, ib) => cmp(slaves[ia], slaves[ib])); - } - - /** - * @callback slaveComparator - * @param {App.Entity.SlaveState} a - * @param {App.Entity.SlaveState} b - * @returns {number} - */ - /** @returns {slaveComparator} */ - function _comparator() { - return _makeStableComparator(comparators[(V.sortSlavesOrder === "ascending" ? 'A' : 'D') + V.sortSlavesBy]); - } - - /** secondary-sort by ascending ID if the primary comparator would return 0 (equal), so we have a guaranteed stable order regardless of input - * @param {slaveComparator} comparator - * @returns {slaveComparator} - */ - function _makeStableComparator(comparator) { - return function(a, b) { - return comparator(a, b) || comparators.AID(a, b); - }; - } -}(); - -/** - * @param {App.Entity.SlaveState[]} slaves - */ -globalThis.slaveSortMinor = function(slaves) { - slaves.sort((a, b) => a.slaveName < b.slaveName ? -1 : 1); -}; - -globalThis.menialPopCap = function() { - let r = ""; - - let popCap = 500 * (1 + V.building.findCells(cell => cell instanceof App.Arcology.Cell.Manufacturing && cell.type === "Pens").length); - - let overMenialCap = V.menials + V.fuckdolls + V.menialBioreactors - popCap; - if (overMenialCap > 0) { - const price = menialSlaveCost(-overMenialCap); - if (V.menials > 0) { - if (V.menials > overMenialCap) { - cashX((overMenialCap * price), "menialTrades"); - V.menialDemandFactor -= overMenialCap; - V.menials -= overMenialCap; - overMenialCap = 0; - r += "You don't have enough room for all your menials and are obliged to sell some."; - } else { - cashX((V.menials * price), "menialTrades"); - V.menialDemandFactor -= V.menials; - overMenialCap -= V.menials; - V.menials = 0; - r += "You don't have enough room for your menials and are obliged to sell them."; - } - } - if (overMenialCap > 0 && V.fuckdolls > 0) { - if (V.fuckdolls > overMenialCap) { - cashX(overMenialCap * (price * 2), "menialTrades"); - V.menialDemandFactor -= overMenialCap; - V.fuckdolls -= overMenialCap; - overMenialCap = 0; - r += "You don't have enough room for all your Fuckdolls and are obliged to sell some."; - } else { - cashX(V.fuckdolls * (price * 2), "menialTrades"); - V.menialDemandFactor -= V.fuckdolls; - overMenialCap -= V.fuckdolls; - V.fuckdolls = 0; - r += "You don't have enough room for your Fuckdolls and are obliged to sell them."; - } - } - if (overMenialCap > 0 && V.menialBioreactors > 0) { - cashX(overMenialCap * (price - 100), "menialTrades"); - V.menialDemandFactor -= overMenialCap; - V.menialBioreactors -= overMenialCap; - r += "You don't have enough room for all your menial bioreactors and are obliged to sell some."; - } - } - return {text: r, value: popCap}; -}; - -/** - * @param {App.Entity.SlaveState} slave - * @param {number} amount - * @returns {string} - */ -globalThis.faceIncrease = function(slave, amount) { - const pronouns = getPronouns(slave); - const his = pronouns.possessive; - const His = capFirstChar(his); - let r = ""; - if (slave.face <= -95) { - r += `<span class="green">${His} face is no longer horrifying,</span> and is now merely ugly.`; - } else if (slave.face <= -40 && slave.face + amount > -40) { - r += `<span class="green">${His} face is no longer ugly,</span> and is now merely unattractive.`; - } else if (slave.face <= -10 && slave.face + amount > -10) { - r += `<span class="green">${His} face is no longer unattractive,</span> and is now somewhat tolerable.`; - } else if (slave.face <= 10 && slave.face + amount > 10) { - r += `<span class="green">${His} face is now decently attractive,</span> rather than merely tolerable.`; - } else if (slave.face <= 40 && slave.face + amount > 40) { - r += `<span class="green">${His} face is now quite beautiful,</span> rather than merely pretty.`; - } else if (slave.face <= 95 && slave.face + amount > 95) { - r += `<span class="green">${His} face is now perfect.</span> It's difficult to imagine how it could be any more beautiful.`; - } - slave.face = Math.clamp(slave.face + amount, -100, 100); - if (slave.face > 95) { - slave.face = 100; - } - return r; -}; - -/** - * @param {App.Entity.SlaveState} slave - * @returns {number} - */ -globalThis.deadliness = function(slave) { - let deadliness = 2; - - if (slave.skill.combat > 0) { - deadliness += 2; - } - - if (App.Data.Careers.Leader.bodyguard.includes(slave.career)) { - deadliness += 1; - } else if (slave.skill.bodyguard >= V.masteredXP) { - deadliness += 1; - } - - if (V.AgePenalty !== 0) { - if (slave.physicalAge >= 100) { - deadliness -= 10; - } else if (slave.physicalAge >= 85) { - deadliness -= 3; - } else if (slave.physicalAge >= 70) { - deadliness -= 1; - } - } - - if (slave.muscles > 30 && slave.muscles <= 95) { - deadliness += 1; - } else if (slave.muscles > 95 && slave.height >= 185) { - deadliness += 2; - } else if (slave.muscles > 95) { - deadliness -= 1; - } else if (slave.muscles < -95) { - deadliness -= 20; - } else if (slave.muscles < -30) { - deadliness -= 7; - } else if (slave.muscles < -5) { - deadliness -= 3; - } - - if (slave.height >= 170) { - deadliness += 1; - } - - if (slave.health.condition > 50) { - deadliness += 1; - } else if (slave.health.condition < -50) { - deadliness -= 1; - } - - if (slave.health.illness > 3) { - deadliness -= 3; - } else if (slave.health.illness > 1) { - deadliness -= 2; - } else if (slave.health.illness > 0) { - deadliness -= 1; - } - - if (slave.boobs > 4000) { - deadliness -= 2; - } else if (slave.boobs > 2000) { - deadliness -= 1; - } - - if (slave.butt > 6) { - deadliness -= 1; - } - - if (slave.hips > 2) { - deadliness -= 1; - } - - if (slave.weight > 190) { - deadliness -= 20; - } else if (slave.weight > 160) { - deadliness -= 10; - } else if (slave.weight > 130) { - deadliness -= 3; - } else if (slave.weight > 30 || slave.weight < -10) { - deadliness -= 1; - } - - if (slave.bellyFluid >= 10000) { - deadliness -= 3; - } else if (slave.bellyFluid >= 5000) { - deadliness -= 2; - } else if (slave.bellyFluid >= 2000) { - deadliness -= 1; - } - - if (slave.pregKnown === 1 || slave.bellyPreg >= 1500 || slave.bellyImplant >= 1500) { - if (slave.belly >= 750000) { - deadliness -= 50; - } else if (slave.belly >= 600000) { - deadliness -= 25; - } else if (slave.belly >= 450000) { - deadliness -= 15; - } else if (slave.belly >= 300000) { - deadliness -= 10; - } else if (slave.belly >= 150000) { - deadliness -= 8; - } else if (slave.belly >= 100000) { - deadliness -= 7; - } else if (slave.belly >= 10000) { - deadliness -= 3; - } else if (slave.belly >= 5000) { - deadliness -= 2; - } else { - deadliness -= 1; - } - } - - if (isInLabor(slave)) { - deadliness -= 15; - } else if (slave.preg >= slave.pregData.normalBirth && slave.pregControl !== "labor suppressors") { - deadliness -= 5; - } - - if (slave.balls >= 15) { - deadliness -= 1; - } - - if (slave.dick >= 10) { - deadliness -= 1; - } - - deadliness -= getLimbCount(slave, 0) * 5; - deadliness -= getLimbCount(slave, 2) * 0.25; - deadliness -= getLimbCount(slave, 3) * 0.25; - deadliness -= getLimbCount(slave, 4) * 0.25; - deadliness += getLimbCount(slave, 5) * 1.25; - deadliness += getLimbCount(slave, 6) * 2.5; - if (hasBothLegs(slave) && !canStand(slave)) { - deadliness -= 20; - } - - if (!canSee(slave)) { - deadliness -= 8; - } else if (!canSeePerfectly(slave)) { - deadliness -= 1; - } - - if (!canHear(slave)) { - deadliness -= 4; - } else if ((slave.hears === -1 && slave.earwear !== "hearing aids") || (slave.hears === 0 && slave.earwear === "muffling ear plugs")) { - deadliness -= 1; - } - - if (slave.tail === "combat") { - deadliness += 2; - } - - if (slave.health.tired > 90) { - deadliness -= 10; - } else if (slave.health.tired > 60) { - deadliness -= 3; - } else if (slave.health.tired > 30) { - deadliness -= 1; - } - - return Math.max(deadliness, 1); -}; - -/** Is the slave ready to retire? - * @param {App.Entity.SlaveState} slave - * @returns {boolean} - */ -globalThis.retirementReady = function(slave) { - // indentured slaves don't retire, they expire - if (slave.indenture >= 0) { - return false; - } - - // retirement by age - if (V.policies.retirement.physicalAgePolicy === 0 && slave.actualAge >= V.retirementAge) { - return true; - } else if (V.policies.retirement.physicalAgePolicy === 1 && slave.physicalAge >= V.retirementAge) { - return true; - } - - // retirement by milestone - if (V.policies.retirement.sex > 0 && (slave.counter.oral + slave.counter.anal + slave.counter.vaginal + slave.counter.penetrative + slave.counter.mammary) > V.policies.retirement.sex) { - return true; - } - if (V.policies.retirement.milk > 0 && slave.counter.milk > V.policies.retirement.milk) { - return true; - } - if (V.policies.retirement.cum > 0 && slave.counter.cum > V.policies.retirement.cum) { - return true; - } - if (V.policies.retirement.births > 0 && slave.counter.births > V.policies.retirement.births) { - return true; - } - if (V.policies.retirement.kills > 0 && slave.counter.pitKills > V.policies.retirement.kills) { - return true; - } - - // no retirement for you - return false; -}; - -/** marks some weeks of time passage for a slave, counting birthdays and invoking aging if game settings require it - * @param {App.Entity.SlaveState} slave - * @param {number} [weeks=1] - */ -globalThis.ageSlaveWeeks = function(slave, weeks = 1) { - if (V.seeAge !== 0) { // birthdays enabled - for (let i = 0; i < weeks; ++i) { - slave.birthWeek++; - if (slave.birthWeek >= 52) { - slave.birthWeek = 0; - if (V.seeAge === 1) { // actual aging enabled - ageSlave(slave); - } - } - } - } -}; - -/** advances the age of a slave by one year, incurring all aging side effects - * @param {App.Entity.SlaveState} slave - * @param {boolean} [forceDevelopment=false] - */ -globalThis.ageSlave = function(slave, forceDevelopment = false) { - slave.physicalAge++; - slave.actualAge++; - if (slave.geneMods.NCS === 1 || (slave.geneticQuirks.neoteny >= 2 && slave.geneticQuirks.progeria !== 2)) { - /* Induced NCS completely takes over visual aging. Additionally, because of the neoteny aspects of NCS, ovaries don't age quite as fast. */ - /* Unsurprisingly, actual neoteny has the same effect as long as progeria isn't in play. */ - slave.ovaryAge += either(0.5, 0.6, 0.7, 0.7, 0.8, 0.9, 1.0); - } else { - slave.visualAge++; - slave.ovaryAge += either(0.8, 0.9, 0.9, 1.0, 1.0, 1.0, 1.1); - } - if (slave.broodmother === 1) { - slave.ovaryAge += 0.2; - } - if (slave.physicalAge <= 18 && (forceDevelopment || V.loliGrow > 0)) { - physicalDevelopment(slave); - } -}; - -/** Is the slave a shelter slave? - * @param {App.Entity.SlaveState} slave - * @returns {boolean} - */ -globalThis.isShelterSlave = function(slave) { - return (typeof slave.origin === "string" && slave.origin.includes("Slave Shelter")); -}; - -/** - * Returns if a slave appears male, female, or androgynous. - * - * @param {App.Entity.SlaveState} slave - * @returns {number} - */ -globalThis.perceivedGender = function(slave) { - return -1; -}; - -globalThis.initRules = function() { - const rule = emptyDefaultRule(); - rule.name = "Obedient Slaves"; - rule.condition.function = "between"; - rule.condition.data.attribute = "devotion"; - rule.condition.data.value = [20, null]; - rule.set.removalAssignment = "rest"; - - V.defaultRules = [rule]; - V.rulesToApplyOnce = {}; -}; - -/** - * - * @param {App.Entity.SlaveState} slave - * @param {number} partner The ID of the slave's partner. - * - * | ***ID*** | **Type** | - * |---------:|:----------------------| - * | *-1* | PC | - * | *-2* | Citizen | - * | *-3* | PC's former master | - * | *-4* | Fellow arcology owner | - * | *-6* | Societal Elite | - * | *-8* | Animal | - * | *-9* | Futanari Sister | - */ -globalThis.addPartner = function(slave, partner) { - slave.partners.add(partner); - - if (partner > 0) { - getSlave(partner).partners.add(slave.ID); - } -}; diff --git a/src/js/rulesAssistant.js b/src/js/rulesAssistant.js index b25965885ae9744963c8a840e280933f6bd85ffa..52199fd9c050ef135adb64295328edc9dd7e1c47 100644 --- a/src/js/rulesAssistant.js +++ b/src/js/rulesAssistant.js @@ -602,3 +602,15 @@ App.RA.ruleDeepAssign = function deepAssign(target, source) { } return target; }; + +globalThis.initRules = function() { + const rule = emptyDefaultRule(); + rule.name = "Obedient Slaves"; + rule.condition.function = "between"; + rule.condition.data.attribute = "devotion"; + rule.condition.data.value = [20, null]; + rule.set.removalAssignment = "rest"; + + V.defaultRules = [rule]; + V.rulesToApplyOnce = {}; +}; diff --git a/src/js/utilsArcology.js b/src/js/utilsArcology.js new file mode 100644 index 0000000000000000000000000000000000000000..4dd1a73849d74394f265fc3c2fe54401ba66624a --- /dev/null +++ b/src/js/utilsArcology.js @@ -0,0 +1,104 @@ +/** Returns the revivalist nationality associated with the player's arcology, or 0 if none + * @returns {string|0} + */ +globalThis.getRevivalistNationality = function() { + if (V.arcologies[0].FSRomanRevivalist > 90) { + return "Roman Revivalist"; + } else if (V.arcologies[0].FSAztecRevivalist > 90) { + return "Aztec Revivalist"; + } else if (V.arcologies[0].FSEgyptianRevivalist > 90) { + return "Ancient Egyptian Revivalist"; + } else if (V.arcologies[0].FSEdoRevivalist > 90) { + return "Edo Revivalist"; + } else if (V.arcologies[0].FSArabianRevivalist > 90) { + return "Arabian Revivalist"; + } else if (V.arcologies[0].FSChineseRevivalist > 90) { + return "Ancient Chinese Revivalist"; + } + return 0; +}; + +/** Calculate and return economic uncertainty multiplier for a given arcology + * @param {number} arcologyID + * @returns {number} + */ +App.Utils.economicUncertainty = function(arcologyID) { + let uncertainty = arcologyID === 0 ? 5 : 10; + if (assistant.power === 1) { + uncertainty -= Math.max(Math.trunc(uncertainty/2), 0); + } else if (assistant.power > 1) { + uncertainty = 0; + } + return jsRandom(100 - uncertainty, 100 + uncertainty) / 100; +}; + +/** + * @returns {number} + */ +App.Utils.schoolCounter = function() { + return Array.from(App.Data.misc.schools.keys()).filter(s => V[s].schoolPresent).length; +}; + +/** + * @returns {string} + */ +App.Utils.schoolFailure = function() { + return Array.from(App.Data.misc.schools.keys()).find(s => V[s].schoolPresent && V[s].schoolProsperity <= -10); +}; + +/** + * @typedef {Object} menialObject + * @property {string} text + * @property {number} value + */ + +/** + * @returns {menialObject} + */ +globalThis.menialPopCap = function() { + let r = ""; + + let popCap = 500 * (1 + V.building.findCells(cell => cell instanceof App.Arcology.Cell.Manufacturing && cell.type === "Pens").length); + + let overMenialCap = V.menials + V.fuckdolls + V.menialBioreactors - popCap; + if (overMenialCap > 0) { + const price = menialSlaveCost(-overMenialCap); + if (V.menials > 0) { + if (V.menials > overMenialCap) { + cashX((overMenialCap * price), "menialTrades"); + V.menialDemandFactor -= overMenialCap; + V.menials -= overMenialCap; + overMenialCap = 0; + r += "You don't have enough room for all your menials and are obliged to sell some."; + } else { + cashX((V.menials * price), "menialTrades"); + V.menialDemandFactor -= V.menials; + overMenialCap -= V.menials; + V.menials = 0; + r += "You don't have enough room for your menials and are obliged to sell them."; + } + } + if (overMenialCap > 0 && V.fuckdolls > 0) { + if (V.fuckdolls > overMenialCap) { + cashX(overMenialCap * (price * 2), "menialTrades"); + V.menialDemandFactor -= overMenialCap; + V.fuckdolls -= overMenialCap; + overMenialCap = 0; + r += "You don't have enough room for all your Fuckdolls and are obliged to sell some."; + } else { + cashX(V.fuckdolls * (price * 2), "menialTrades"); + V.menialDemandFactor -= V.fuckdolls; + overMenialCap -= V.fuckdolls; + V.fuckdolls = 0; + r += "You don't have enough room for your Fuckdolls and are obliged to sell them."; + } + } + if (overMenialCap > 0 && V.menialBioreactors > 0) { + cashX(overMenialCap * (price - 100), "menialTrades"); + V.menialDemandFactor -= overMenialCap; + V.menialBioreactors -= overMenialCap; + r += "You don't have enough room for all your menial bioreactors and are obliged to sell some."; + } + } + return {text: r, value: popCap}; +}; diff --git a/src/js/utilsAssessSlave.js b/src/js/utilsAssessSlave.js new file mode 100644 index 0000000000000000000000000000000000000000..f42e0ad0677b34ed9d951769daf66905feea0e47 --- /dev/null +++ b/src/js/utilsAssessSlave.js @@ -0,0 +1,243 @@ +/* +* +* This file focuses on slave related functions that assess qualities about slaves. Are they/can they X? +* +*/ + +/** + * @param {App.Entity.SlaveState} slave + * @returns {string} + */ +globalThis.getSlaveDevotionClass = function(slave) { + if ((!slave) || (!State)) { + return undefined; + } + if (slave.fetish === "mindbroken") { + return "mindbroken"; + } + if (slave.devotion < -95) { + return "very-hateful"; + } else if (slave.devotion < -50) { + return "hateful"; + } else if (slave.devotion < -20) { + return "resistant"; + } else if (slave.devotion <= 20) { + return "ambivalent"; + } else if (slave.devotion <= 50) { + return "accepting"; + } else if (slave.devotion <= 95) { + return "devoted"; + } else { + return "worshipful"; + } +}; + +/** + * @param {App.Entity.SlaveState} slave + * @returns {string} + */ +globalThis.getSlaveTrustClass = function(slave) { + if ((!slave) || (!State)) { + return undefined; + } + + if (slave.fetish === "mindbroken") { + return ""; + } + + if (slave.trust < -95) { + return "extremely-terrified"; + } else if (slave.trust < -50) { + return "terrified"; + } else if (slave.trust < -20) { + return "frightened"; + } else if (slave.trust <= 20) { + return "fearful"; + } else if (slave.trust <= 50) { + if (slave.devotion < -20) { + return "hate-careful"; + } else { + return "careful"; + } + } else if (slave.trust <= 95) { + if (slave.devotion < -20) { + return "bold"; + } else { + return "trusting"; + } + } else if (slave.devotion < -20) { + return "defiant"; + } else { + return "profoundly-trusting"; + } +}; + +/** + * Returns a "disobedience factor" between 0 (perfectly obedient) and 100 (completely defiant) + * @param {App.Entity.SlaveState} slave + * @returns {number} + */ +globalThis.disobedience = function(slave) { + const devotionBaseline = 20; // with devotion above this number slaves will obey completely + const trustBaseline = -20; // with trust below this number slaves will obey completely + + if (slave.devotion > devotionBaseline || slave.trust < trustBaseline) { + return 0; // no chance of disobedience + } + + // factors are between 0 (right on the boundary of perfectly obedient) and 10 (completely disobedient) + let devotionFactor = 10 - ((10 * (slave.devotion + 100)) / (devotionBaseline + 100)); + let trustFactor = (10 * (slave.trust - trustBaseline)) / (100 - trustBaseline); + return Math.round(devotionFactor * trustFactor); +}; + +/** + * Returns how exposing a slave's outfit is, after taking into consideration a topless outfit is more revealing for beboobed slaves or female ones. + * @param {App.Entity.SlaveState} slave + * @returns {0|1|2|3|4} + */ +globalThis.getExposure = function(slave) { + const clothes = App.Data.clothes.get(slave.clothes); + return (clothes.topless && clothes.exposure < 3 && (slave.boobs > 299 || (slave.genes === 'XX' && slave.vagina >= 0))) ? 3 : clothes.exposure; +}; + +/** + * @param {App.Entity.SlaveState} slave + * @returns {boolean} + */ +globalThis.canImproveIntelligence = function(slave) { + let origIntel = V.genePool.find(function(s) { return s.ID === slave.ID; }).intelligence; + return (slave.intelligence < origIntel + 15) && (slave.intelligence < 100); +}; + +/** + * @param {App.Entity.SlaveState} slave + * @returns {number} + */ +globalThis.maxHeight = function(slave) { + let max = Math.trunc(Math.clamp((Height.mean(slave) * 1.25), 0, 274)); /* max achievable height is expected height plus 25% */ + + if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism !== 2) { + max = Math.min(max, 160); + } + + return max; +}; + +/** + * @param {App.Entity.SlaveState} slave + * @returns {boolean} + */ +globalThis.canImproveHeight = function(slave) { + return slave.height < maxHeight(slave); +}; + +/** + * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} target + * @returns {boolean} + */ +globalThis.haveRelationshipP = function(slave, target) { + return slave.relationshipTarget === target.ID; +}; + +/** + * @param {App.Entity.SlaveState} slave + * @param {App.Entity.SlaveState} target + * @returns {boolean} + */ +globalThis.isRivalP = function(slave, target) { + return slave.rivalryTarget === target.ID; +}; + +/** + * @param {FC.HumanState} slave + * @returns {boolean} + */ +globalThis.supremeRaceP = function(slave) { + return V.arcologies[0].FSSupremacistRace === slave.race; +}; + +/** + * @param {FC.HumanState} slave + * @returns {boolean} + */ +globalThis.inferiorRaceP = function(slave) { + return V.arcologies[0].FSSubjugationistRace === slave.race; +}; + +/** + * @param {App.Entity.SlaveState} slave + * @returns {boolean} + */ +globalThis.isLeaderP = function(slave) { + const leaders = [S.HeadGirl, S.Bodyguard, S.Recruiter, S.Concubine, S.Nurse, S.Attendant, S.Matron, S.Madam, S.DJ, S.Milkmaid, S.Farmer, S.Stewardess, S.Schoolteacher, S.Wardeness]; + + return leaders.some(leader => leader && leader.ID === slave.ID); +}; + +/** Get the written title for a given slave, without messing with global state. + * @param {App.Entity.SlaveState} [slave] + * @returns {string} + */ +globalThis.getWrittenTitle = function(slave) { + if (slave && slave.custom.title !== undefined && slave.custom.title !== "" && slave.rudeTitle === 0) { + return slave.custom.title; + } + if (V.PC.customTitle !== undefined) { + return V.PC.customTitle; + } else if (V.PC.title !== 0) { + return "Master"; + } else { + return "Mistress"; + } +}; + +/** + * @param {App.Entity.SlaveState} slave + * @returns {string} + */ +globalThis.SlaveFullName = function(slave) { + const pair = slave.slaveSurname ? [slave.slaveName, slave.slaveSurname] : [slave.slaveName]; + if ((V.surnameOrder !== 1 && ["Cambodian", "Chinese", "Hungarian", "Japanese", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes(slave.nationality)) || (V.surnameOrder === 2)) { + pair.reverse(); + } + return pair.join(" "); +}; + +/** Is the slave a shelter slave? + * @param {App.Entity.SlaveState} slave + * @returns {boolean} + */ +globalThis.isShelterSlave = function(slave) { + return (typeof slave.origin === "string" && slave.origin.includes("Slave Shelter")); +}; + +/** + * Returns if a slave appears male, female, or androgynous. + * + * @param {App.Entity.SlaveState} slave + * @returns {number} + */ +globalThis.perceivedGender = function(slave) { + return -1; +}; + +/** + * @param {App.Entity.SlaveState} A + * @param {App.Entity.SlaveState} B + * @returns {boolean} + */ +globalThis.sameAssignmentP = function(A, B) { + return A.assignment === B.assignment; +}; + +/** Determine whether a given penthouse slave can move into a private room or not. + * @param {App.Entity.SlaveState} slave + * @returns {boolean} + */ +globalThis.canMoveToRoom = function(slave) { + const partner = slave.relationship >= 4 ? getSlave(slave.relationshipTarget) : null; + const partnerHasRoom = partner && assignmentVisible(partner) && partner.rules.living === "luxurious"; + return partnerHasRoom || V.rooms - V.roomsPopulation >= 1; +}; diff --git a/src/js/utilsMisc.js b/src/js/utilsMisc.js new file mode 100644 index 0000000000000000000000000000000000000000..ab9e8f6928c3a475339641231554e4dd3d127654 --- /dev/null +++ b/src/js/utilsMisc.js @@ -0,0 +1,75 @@ +globalThis.Categorizer = class { + /** + * @param {...[]} pairs + */ + constructor(...pairs) { + this.cats = Array.prototype.slice.call(pairs) + .filter(function(e, i, a) { + return Array.isArray(e) && e.length === 2 && typeof e[0] === "number" && !isNaN(e[0]) && + a.findIndex(function(val) { + return e[0] === val[0]; + }) === i; /* uniqueness test */ + }) + .sort(function(a, b) { + return b[0] - a[0]; /* reverse sort */ + }); + } + + cat(val, def) { + let result = def; + if (typeof val === "number" && !isNaN(val)) { + let foundCat = this.cats.find(function(e) { + return val >= e[0]; + }); + if (foundCat) { + result = foundCat[1]; + } + } + // Record the value for the result's getter, if it is an object + // and doesn't have the property yet + if (typeof result === "object" && !isNaN(result)) { + result.value = val; + } + return result; + } +}; + +/** + * Converts an array of strings into a sentence parted by commas. + * @param {Array} array ["apple", "banana", "carrot"] + * @returns {string} "apple, banana and carrot" + */ +globalThis.arrayToSentence = function(array) { + return array.reduce((res, ch, i, arr) => res + (i === arr.length - 1 ? ' and ' : ', ') + ch); +}; + +App.Utils.alphabetizeIterable = function(iterable) { + const compare = function(a, b) { + let aTitle = a.toLowerCase(); + let bTitle = b.toLowerCase(); + + aTitle = removeArticles(aTitle); + bTitle = removeArticles(bTitle); + + if (aTitle > bTitle) { + return 1; + } + if (aTitle < bTitle) { + return -1; + } + return 0; + }; + + function removeArticles(str) { + const words = str.split(" "); + if (words.length <= 1) { + return str; + } + if ( words[0] === 'a' || words[0] === 'the' || words[0] === 'an' ) { + return words.splice(1).join(" "); + } + return str; + } + const clonedArray = (Array.from(iterable)); + return clonedArray.sort(compare); +}; diff --git a/src/js/utilsFC.js b/src/js/utilsSlave.js similarity index 70% rename from src/js/utilsFC.js rename to src/js/utilsSlave.js index 14f26e503ccbfd87a4971cc1de810d4e86f5bb46..9d055f0f8b2cd0565aced7afa8dc2983195db75f 100644 --- a/src/js/utilsFC.js +++ b/src/js/utilsSlave.js @@ -1,5 +1,3 @@ -/* contains functions that rely on FC specific variables/conventions */ -/* eslint-disable no-unused-vars */ /* * Height.mean(nationality, race, genes, age) - returns the mean height for the given combination and age in years (>=2) * Height.mean(nationality, race, genes) - returns the mean adult height for the given combination @@ -912,736 +910,6 @@ As a categorizer <<print $cats.muscleCat.cat(_Slave.muscles)>> */ -globalThis.Categorizer = class { - /** - * @param {...[]} pairs - */ - constructor(...pairs) { - this.cats = Array.prototype.slice.call(pairs) - .filter(function(e, i, a) { - return Array.isArray(e) && e.length === 2 && typeof e[0] === "number" && !isNaN(e[0]) && - a.findIndex(function(val) { - return e[0] === val[0]; - }) === i; /* uniqueness test */ - }) - .sort(function(a, b) { - return b[0] - a[0]; /* reverse sort */ - }); - } - - cat(val, def) { - let result = def; - if (typeof val === "number" && !isNaN(val)) { - let foundCat = this.cats.find(function(e) { - return val >= e[0]; - }); - if (foundCat) { - result = foundCat[1]; - } - } - // Record the value for the result's getter, if it is an object - // and doesn't have the property yet - if (typeof result === "object" && !isNaN(result)) { - result.value = val; - } - return result; - } -}; - -/** - * Returns numbers as text, e.g. 10 as "ten", according to the player's settings - * @param {number} x - * @param {boolean} [printText=false] (optional) - * @returns {string} - */ -globalThis.num = function(x, printText = false) { - const max = V.showNumbersMax; - - const ONE_TO_NINETEEN = [ - "one", "two", "three", "four", "five", - "six", "seven", "eight", "nine", "ten", - "eleven", "twelve", "thirteen", "fourteen", "fifteen", - "sixteen", "seventeen", "eighteen", "nineteen", - ]; - - const TENS = [ - "ten", "twenty", "thirty", "forty", "fifty", - "sixty", "seventy", "eighty", "ninety", - ]; - - const SCALES = ["thousand", "million", "billion", "trillion", "quadrillion", "quintillion", "sextillion", "septillion", "octillion", "nonillion", "decillion"]; - - /** - * helper function for use with Array.filter - * @param {any} item - * @returns {boolean} - */ - function isTruthy(item) { - return !!item; - } - - /** - * convert a number into "chunks" of 0-999 - * @param {number} number - * @returns {number[]} - */ - function chunk(number) { - const thousands = []; - - while (number > 0) { - thousands.push(number % 1000); - number = Math.floor(number / 1000); - } - - return thousands; - } - - /** - * translate a number from 1-999 into English - * @param {number} number - * @returns {string} - */ - function inEnglish(number) { - let hundreds; - let tens; - let ones; - const words = []; - - if (number === 0) { - return "zero"; - } - - if (number < 20) { - return ONE_TO_NINETEEN[number - 1]; // may be undefined - } - - if (number < 100) { - ones = number % 10; - tens = number / 10 | 0; // equivalent to Math.floor(number / 10) - - words.push(TENS[tens - 1]); - words.push(inEnglish(ones)); - - return words.filter(isTruthy).join("-"); - } - - hundreds = number / 100 | 0; - words.push(inEnglish(hundreds)); - words.push("hundred"); - words.push(inEnglish(number % 100)); - - return words.filter(isTruthy).join(" "); - } - - if (printText) { - return inEnglish(x); - } - - /** - * append the word for a scale. Made for use with Array.map - * @param {string} chunk - * @param {number} exp - * @returns {string} - */ - function appendScale(chunk, exp) { - let scale; - if (!chunk) { - return null; - } - scale = SCALES[exp - 1]; - return [chunk, scale].filter(isTruthy).join(" "); - } - - if (V.showNumbers === 2) { - return commaNum(x); - } else { - if (x === 0) { - return "zero"; - } - - if (V.showNumbers === 1 && Math.abs(x) > max) { - return commaNum(x); - } - - let numberAsString = chunk(Math.abs(x)) - .map(inEnglish) - .map(appendScale) - .filter(isTruthy) - .reverse() - .join(" "); - - if (x > 0) { - return numberAsString; - } else { - return `negative ${numberAsString}`; - } - } -}; - -globalThis.asPlural = function(single, plural) { - if (typeof single !== 'string') { - let asObj = single; - single = asObj.single; - plural = asObj.plural; - } - if (plural == null) { - plural = single + "s"; - } - return plural; -}; -globalThis.asSingular = function(single) { - if (typeof single !== 'string') { - let asObj = single; - single = asObj.single; - } - return single; -}; -// When 1, shows "a (slave)" -globalThis.numberWithPlural = function(number, single, plural) { - if (number === 0) { - return "no " + asPlural(single, plural); - } else if (number === 1) { - return addA(asSingular(single)); - } else if (number > 0 && number < 1) { - return "less than one " + asSingular(single); - } else { - return number + " " + asPlural(single, plural); - } -}; - -// when 1, shows "one (slave)" -globalThis.numberWithPluralOne = function(number, single, plural) { - if (number === 0) { - return "no " + asPlural(single, plural); - } else if (number === 1) { - return "one " + asSingular(single); - } else if (number > 0 && number < 1) { - return "less than one " + asSingular(single); - } else { - return number + " " + asPlural(single, plural); - } -}; -// shows "less than one (slave)" instead of "no (slaves)" when number is 0. -globalThis.numberWithPluralNonZero = function(number, single, plural) { - if (number === 0) { - number = 0.1; - } - return numberWithPlural(number, single, plural); -}; -globalThis.onlyPlural = function(number, single, plural) { - if (number > 0 && number <= 1) { - return asSingular(single); - } - return asPlural(single, plural); -}; -globalThis.Separator = function(SeparatorObject) { - if (SeparatorObject.need) { - return SeparatorObject.text; - } - SeparatorObject.need = true; - return ""; -}; -/** - * Returns numbers with comma, e.g. 10000 as "10,000", according to the player's settings - * @param {number} s - * @returns {string} - */ -globalThis.commaNum = function(s) { - // Separated from num because some places in code (like long lists, tables) should never have numbers spelled out, but still benefit from commas - if (!s) { - return "0"; - } - if (V.formatNumbers !== 1) { - return s.toString(); - } else { - return s.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); - } -}; - -/** - * Returns the number of weeks in a years / months / weeks format - * @param {number} weeks - * @returns {string} - */ -globalThis.years = function(weeks) { - let years = 0; - let quarters = 0; // needed for calc, not user facing - let months = 0; - let array = []; - - // A year is always 52 weeks - // that could be 13 months, but lets say 4 quarters each getting an extra week (13 weeks) - - // Find years - years = Math.trunc(weeks / 52); - - if (years >= 1) { // Is there at least 1 year - weeks = weeks - (years * 52); // Find leftover weeks - } - if (weeks && weeks / 13 >= 1) { // Is there at least 1 quarter - quarters = Math.trunc(weeks / 13); // How many quarters? - weeks = weeks - (quarters * 13); // A quarter contains 13 weeks, how many extra weeks do we have? - } - if (weeks && weeks / 4 >= 1) { // Is there at least 1 month - months = Math.trunc(weeks / 4); // How many months? - if (months === 3) { // Almost a quarter of a year - months--; // Quarters have 13 weeks though, so let's be sure the extra is in weeks. Otherwise 51 will return "12 months" instead of "11 months and 4 weeks." - } - weeks = weeks - (months * 4); // A month contains 4 weeks, how many extra weeks do we have? - } - - // So we have years, quarters, months, and weeks. - - // Quarters are useless so: - - months += quarters * 3; // Each quarter has three months. - - if (years) { - array.push(`${num(years)} year${years !== 1 ? `s` : ``}`); - } - - if (months) { - array.push(`${num(months)} month${months !== 1 ? `s` : ``}`); - } - - if (weeks) { - array.push(`${num(weeks)} week${weeks !== 1 ? `s` : ``}`); - } - - return array.toStringExt(); -}; -/** - * @param {number} [weeks] - * @param {number} [bonusDay] - * @returns {Date} - */ -globalThis.asDate = function(weeks = null, bonusDay = 0) { - if (weeks == null) { - weeks = V.week; - } - let d = new Date(2037, 0, 12); - d.setDate(d.getDate() + weeks * 7 + bonusDay); - return d; -}; -/** - * @param {number} [weeks] - * @param {number} [bonusDay] - * @returns {string} - */ -globalThis.asDateString = function(weeks = null, bonusDay = 0) { - return asDate(weeks, bonusDay).toLocaleString(undefined, {year: 'numeric', month: 'long', day: 'numeric'}); -}; - -/** - * @param {number} s - * @returns {string} - */ -globalThis.cashFormat = function(s) { - if (s < 0) { - return `-¤${commaNum(Math.abs(s))}`; - } - return `¤${commaNum(s)}`; -}; -globalThis.cashFormatColor = function(s, invert = false) { - if (invert) { - s = -1 * s; - } - // Display red if the value is negative, unless invert is true - if (s < 0) { - return `<span class='red'>${cashFormat(s)}</span>`; - // White for exactly zero - } else if (s === 0) { - return `<span>${cashFormat(s)}</span>`; - // Yellow for positive - } else { - return `<span class='yellowgreen'>${cashFormat(s)}</span>`; - } -}; - -/** - * @param {number} s - * @returns {string} - */ -globalThis.repFormat = function(s) { - /* if (!s) { s = 0; }*/ - if (V.cheatMode === 1 || V.debugMode === 1) { - if (s > 0) { - return `<span class="green">${commaNum(Math.round(s * 100) / 100)} rep</span>`; - } else if (s < 0) { - return `<span class="red">${commaNum(Math.round(s * 100) / 100)} rep</span>`; - } else { - return `${commaNum(Math.round(s * 100) / 100)} rep`; - } - } else { - /* In order to calculate just how much any one category matters so we can show a "fuzzy" symbolic value to the player, we need to know how "busy" reputation was this week. To calculate this, I ADD income to expenses. Why? 100 - 100 and 10000 - 10000 BOTH are 0, but a +50 event matters a lot more in the first case than the second. I exclude overflow from the calculation because it's not a "real" expense for our purposes, and divide by half just to make percentages a bit easier. */ - let weight = s / (((V.lastWeeksRepIncome.Total - V.lastWeeksRepExpenses.Total) + V.lastWeeksRepExpenses.overflow) / 2); - if (weight > 0.60) { - return `<span class="green">+++++ rep</span>`; - } else if (weight > 0.45) { - return `<span class="green">++++ rep</span>`; - } else if (weight > 0.30) { - return `<span class="green">+++ rep</span>`; - } else if (weight > 0.15) { - return `<span class="green">++ rep</span>`; - } else if (weight > 0.0) { - return `<span class="green">+ rep</span>`; - } else if (weight === 0) { - return "0 rep"; - } else if (weight < -0.60) { - return `<span class="red">−−−−− rep</span>`; - } else if (weight < -0.45) { - return `<span class="red">−−−− rep</span>`; - } else if (weight < -0.30) { - return `<span class="red">−−− rep</span>`; - } else if (weight < -0.15) { - return `<span class="red">−− rep</span>`; - } else if (weight < 0) { - return `<span class="red">− rep</span>`; - } - /* return weight;*/ - } -}; - -/** - * @param {number} s - * @returns {string} - */ -globalThis.massFormat = function(s) { - if (!s) { - s = 0; - } - if (Math.abs(s) >= 1000) { - s = Math.trunc(s / 1000); - if (s !== 1) { - return `${num(s)} tons`; - } else { - return `${num(s)} ton`; - } - } else { - return `${num(s)} kg`; - } -}; - -/** - * @param {string} category - * @param {string} title - * @returns {string} - */ -globalThis.budgetLine = function(category, title) { - let income; - let expenses; - - if (passage() === "Rep Budget") { - income = "lastWeeksRepIncome"; - expenses = "lastWeeksRepExpenses"; - - if (V[income][category] || V[expenses][category] || V.showAllEntries.repBudget) { - return `<tr>\ - <td>${title}</td>\ - <td>${repFormat(V[income][category])}</td>\ - <td>${repFormat(V[expenses][category])}</td>\ - <td>${repFormat(V[income][category] + V[expenses][category])}</td>\ - </tr>`; - } - } else if (passage() === "Costs Budget") { - income = "lastWeeksCashIncome"; - expenses = "lastWeeksCashExpenses"; - - if (V[income][category] || V[expenses][category] || V.showAllEntries.costsBudget) { - return `<tr>\ - <td>${title}</td>\ - <td>${cashFormatColor(V[income][category])}</td>\ - <td>${cashFormatColor(-Math.abs(V[expenses][category]))}</td>\ - <td>${cashFormatColor(V[income][category] + V[expenses][category])}</td>\ - </tr>`; - } - } - return ``; -}; - -/* -Make everything waiting for this execute. Usage: - -let doSomething = function() { - ... your initialization code goes here ... -}; -if(typeof Categorizer === 'function') { - doSomething(); -} else { - jQuery(document).one('categorizer.ready', doSomething); -} -*/ -jQuery(document).trigger("categorizer.ready"); - -/** - * @param {App.Entity.SlaveState} slave - * @returns {string} - */ -globalThis.getSlaveDevotionClass = function(slave) { - if ((!slave) || (!State)) { - return undefined; - } - if (slave.fetish === "mindbroken") { - return "mindbroken"; - } - if (slave.devotion < -95) { - return "very-hateful"; - } else if (slave.devotion < -50) { - return "hateful"; - } else if (slave.devotion < -20) { - return "resistant"; - } else if (slave.devotion <= 20) { - return "ambivalent"; - } else if (slave.devotion <= 50) { - return "accepting"; - } else if (slave.devotion <= 95) { - return "devoted"; - } else { - return "worshipful"; - } -}; - -/** - * @param {App.Entity.SlaveState} slave - * @returns {string} - */ -globalThis.getSlaveTrustClass = function(slave) { - if ((!slave) || (!State)) { - return undefined; - } - - if (slave.fetish === "mindbroken") { - return ""; - } - - if (slave.trust < -95) { - return "extremely-terrified"; - } else if (slave.trust < -50) { - return "terrified"; - } else if (slave.trust < -20) { - return "frightened"; - } else if (slave.trust <= 20) { - return "fearful"; - } else if (slave.trust <= 50) { - if (slave.devotion < -20) { - return "hate-careful"; - } else { - return "careful"; - } - } else if (slave.trust <= 95) { - if (slave.devotion < -20) { - return "bold"; - } else { - return "trusting"; - } - } else if (slave.devotion < -20) { - return "defiant"; - } else { - return "profoundly-trusting"; - } -}; - -/** - * Takes an integer e.g. slave.hLength, returns a string in the format 10 inches - * @param {number} cm - * @returns {string} - */ -globalThis.cmToInchString = function(cm) { - let inches = cm / 2.54; - if (inches > 0 && inches < 1) { - return "less than an inch"; - } - inches = Math.round(inches); - if (inches === 1) { - return "1 inch"; - } - return `${inches} inches`; -}; - -/** - * takes an integer e.g. slave.height, returns a string in the format 6'5" - * @param {number} cm - * @returns {string} - */ -globalThis.cmToFootInchString = function(cm) { - if (Math.round(cm / 2.54) < 12) { - return cmToInchString(cm); - } - return `${Math.trunc(Math.round(cm / 2.54) / 12)}'${Math.round(cm / 2.54) % 12}"`; -}; - -/** - * takes a dick value e.g. slave.dick, returns a string in the format 6 inches - * @param {number} dick - * @returns {string} - */ -globalThis.dickToInchString = function(dick) { - return cmToInchString(dickToCM(dick)); -}; - -/** - * takes a dick value e.g. slave.dick, returns an int of the dick length in cm - * @param {number} dick - * @returns {number} - */ -globalThis.dickToCM = function(dick) { - if (dick < 9) { - return dick * 5; - } else if (dick === 9) { - return 50; - } - return dick * 6; -}; -/** - * takes a ball value e.g. slave.balls, returns a string in the format 3 inches - * @param {number} balls - * @returns {string} - */ -globalThis.ballsToInchString = function(balls) { - return cmToInchString(ballsToCM(balls)); -}; - -/** - * takes a ball value e.g. slave.balls, returns an int of the ball size in cm - * @param {number} balls - * @returns {number} - */ -globalThis.ballsToCM = function(balls) { - if (balls < 2) { - return 0; - } - return (balls < 10 ? (balls - 1) * 2 : balls * 2); -}; - -/** - * takes a dick value e.g. slave.dick, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm` - * @param {number} dick - * @returns {string} - */ -globalThis.dickToEitherUnit = function(dick) { - if (V.showInches === 1) { - return `${dickToCM(dick)}cm (${dickToInchString(dick)})`; - } - if (V.showInches === 2) { - return dickToInchString(dick); - } - return `${dickToCM(dick)}cm`; -}; - -/** - * takes a ball value e.g. slave.balls, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm` - * @param {number} balls - * @returns {string} - */ -globalThis.ballsToEitherUnit = function(balls) { - if (V.showInches === 1) { - return `${ballsToCM(balls)}cm (${ballsToInchString(balls)})`; - } - if (V.showInches === 2) { - return ballsToInchString(balls); - } - return `${ballsToCM(balls)}cm`; -}; - -/** - * takes an int in centimeters e.g. slave.height, returns a string in the format of either `200cm (6'7")`, `6'7"`, or `200cm` - * @param {number} height - * @returns {string} - */ -globalThis.heightToEitherUnit = function(height) { - if (V.showInches === 1) { - return `${height}cm (${cmToFootInchString(height)})`; - } - if (V.showInches === 2) { - return cmToFootInchString(height); - } - return `${height}cm`; -}; - -/** - * takes an int in centimeters e.g. slave.hLength, returns a string in the format of either `30cm (12 inches)`, `12 inches`, or `30cm` - * @param {number} length - * @returns {string} - */ -globalThis.lengthToEitherUnit = function(length) { - if (V.showInches === 1) { - return `${length}cm (${cmToInchString(length)})`; - } - if (V.showInches === 2) { - return cmToInchString(length); - } - return `${length}cm`; -}; - -/** - * @param {App.Entity.SlaveState} slave - * @param {number} [induce] - * @returns {string} - */ -globalThis.induceLactation = function(slave, induce = 0) { - const {His} = getPronouns(slave); - let r = ""; - let lactationStartChance = jsRandom(10, 100); - slave.induceLactation += induce; - if (slave.boobs < 300) { - lactationStartChance *= 1.5; - } else if (slave.boobs < 400 || slave.boobs >= 5000) { - lactationStartChance *= 1.2; - } - if (slave.pubertyXX === 0) { - lactationStartChance *= 1.5; - } - if (slave.preg > (slave.pregData.normalBirth / 1.33)) { - lactationStartChance *= .5; - } - if (slave.health.condition < -20) { - lactationStartChance *= 2; - } - if (slave.weight <= -30) { - lactationStartChance *= 1.5; - } - if (slave.boobsImplant > 0) { - lactationStartChance *= (1 + (slave.boobsImplant / slave.boobs)); - } - if (slave.lactationAdaptation > 0) { - lactationStartChance = (lactationStartChance / (slave.lactationAdaptation / 10)); - } - if (slave.geneticQuirks.galactorrhea === 2) { - lactationStartChance *= .5; - } - lactationStartChance = Math.floor(lactationStartChance); - if (slave.induceLactation >= lactationStartChance) { - r += `${His} breasts have been stimulated often enough to <span class="lime">induce lactation.</span>`; - slave.induceLactation = 0; - slave.lactationDuration = 2; - slave.lactation = 1; - } - return r; -}; - -globalThis.getProstheticsStockpile = function() { - return `<div>Prosthetics interfaces: ${num(V.prosthetics.interfaceP1.amount + V.prosthetics.interfaceP2.amount)}</div>` + - `<div class="choices">Basic: ${V.prosthetics.interfaceP1.amount}</div>` + - `<div class="choices">Advanced: ${V.prosthetics.interfaceP2.amount}</div>` + - `<div>Limbs: ${num(V.prosthetics.basicL.amount + V.prosthetics.sexL.amount + V.prosthetics.beautyL.amount + - V.prosthetics.combatL.amount + V.prosthetics.cyberneticL.amount)}</div>` + - `<div class="choices">Basic: ${V.prosthetics.basicL.amount}</div>` + - `<div class="choices">Sex: ${V.prosthetics.sexL.amount}</div>` + - `<div class="choices">Beauty: ${V.prosthetics.beautyL.amount}</div>` + - `<div class="choices">Combat: ${V.prosthetics.combatL.amount}</div>` + - `<div class="choices">Cybernetic: ${V.prosthetics.cyberneticL.amount}</div>` + - `<div>Implants: ${num(V.prosthetics.ocular.amount + V.prosthetics.cochlear.amount + V.prosthetics.electrolarynx.amount)}</div>` + - `<div class="choices">Ocular: ${V.prosthetics.ocular.amount}</div>` + - `<div class="choices">Cochlear: ${V.prosthetics.cochlear.amount}</div>` + - /* `<div class="choices">Erectile: ${V.prosthetics.erectile.amount}</div>` + */ - `<div class="choices">Electrolarynx: ${V.prosthetics.electrolarynx.amount}</div>` + - `<div>Tail interface: ${V.prosthetics.interfaceTail.amount}</div>` + - `<div>Tails: ${num(V.prosthetics.modT.amount + V.prosthetics.sexT.amount + V.prosthetics.combatT.amount)}</div>` + - `<div class="choices">Modular: ${V.prosthetics.modT.amount}</div>` + - `<div class="choices">Pleasure: ${V.prosthetics.sexT.amount}</div>` + - `<div class="choices">Combat: ${V.prosthetics.combatT.amount}</div>`; -}; - globalThis.pronounReplacer = function(slavetext) { switch (slavetext) { case "After her short but very promising slave racing career, during which she made it through several competitions as a virgin, many people fondly remember fantasizing about taking her.": @@ -1947,899 +1215,2236 @@ globalThis.pronounReplacer = function(slavetext) { case "You kept her after her owner failed to pay your bill for performing surgery on her.": slavetext = "You kept $him after $his owner failed to pay your bill for performing surgery on $him."; break; - case "You purchased her as a favor to her father.": - slavetext = "You purchased $him as a favor to $his father."; + case "You purchased her as a favor to her father.": + slavetext = "You purchased $him as a favor to $his father."; + break; + case "You purchased her from a King after his son put an illegitimate heir in her womb.": + slavetext = "You purchased $him from a King after his son put an illegitimate heir in $his womb."; + break; + case "You purchased her in order to pave the way for her brother to take the throne.": + slavetext = "You purchased $him in order to pave the way for $his brother to take the throne."; + break; + case "You purchased her indenture contract, making her yours for as long as it lasts.": + slavetext = "You purchased $his indenture contract, making $him yours for as long as it lasts."; + break; + case "You sentenced her to enslavement as a punishment for attempted theft of a slave.": + slavetext = "You sentenced $him to enslavement as a punishment for attempted theft of a slave."; + break; + case "You sentenced her to enslavement as a punishment for dereliction of her duty to you as a mercenary and for theft.": + slavetext = "You sentenced $him to enslavement as a punishment for dereliction of $his duty to you as a mercenary and for theft."; + break; + case "You sentenced her to enslavement as a punishment for smuggling slaves within her body.": + slavetext = "You sentenced $him to enslavement as a punishment for smuggling slaves within $his body."; + break; + case "You stormed her arcology, killed her guards and enslaved her in revenge for insulting you at a dinner party.": + slavetext = "You stormed $his arcology, killed $his guards, and enslaved $him in revenge for insulting you at a dinner party."; + break; + case "You tricked her into enslavement, manipulating her based on her surgical addiction.": + slavetext = "You tricked $him into enslavement, manipulating $him based on $his surgical addiction."; + break; + case "You tricked her mother into selling her into slavery to clear addiction debts.": + slavetext = "You tricked $his mother into selling $him into slavery to clear addiction debts."; + break; + case "You were acquainted with her before you were an arcology owner; your rival tried to use her to manipulate you, but you rescued her.": + slavetext = "You were acquainted with $him before you were an arcology owner; your rival tried to use $him to manipulate you, but you rescued $him."; + break; + case "Your slaving troop kept several girls as fucktoys; you sired her in your favorite.": + slavetext = "Your slaving troop kept several girls as fucktoys; you sired $him in your favorite."; + break; + case "She was enslaved by you when you purchased her debt.": + slavetext = "$He was enslaved by you when you purchased $his debt."; + break; + case "A fresh capture once overpowered you and had his way with you. You kept her as a painful reminder to never lower your guard again.": + case "Drugs and alcohol can be a potent mix; the night that followed it can sometimes be hard to remember. Needless to say, once your belly began swelling with her, you had to temporarily switch to a desk job for your mercenary group.": + case "Her musky milky aura drives men and women around her giggly and dumb with lust.": + case "She chose to be a slave because the romanticized view of it she had turns her on.": + case "She grew up sheltered and submissive, making her an easy target for enslavement.": + case "She has a faint air of fatigue about her, and strength too: that of a survivor.": + case "She has a following in slave pornography. Thousands have enjoyed her getting off from the suffering she caused.": + case "She has a following in slave pornography. Thousands have enjoyed her humiliating herself.": + case "She has a following in slave pornography. Thousands have enjoyed the sight of her being raped.": + case "She has a following in slave pornography. Thousands have enjoyed the sight of her being used.": + case "She has a following in slave pornography. Thousands have enjoyed the sight of her eating and gaining weight.": + case "She has a following in slave pornography. Thousands have enjoyed watching her abuse others.": + case "She has a following in slave pornography. Thousands have enjoyed watching her do anything and everything for cum.": + case "She has a following in slave pornography. Thousands have enjoyed watching her do anything for attention.": + case "She has a following in slave pornography. Thousands have enjoyed watching her happily suffer.": + case "She has a following in slave pornography. Thousands have enjoyed watching her obsess over pumping out babies.": + case "She has a following in slave pornography. Thousands have enjoyed watching her swell with child.": + case "She has a verbal tic that causes her to say 'ho, ho, ho' frequently.": + case "She has many surgical scars and something seems off about her.": + case "She is a complete mental blank; to her, there is only the Master.": + case "She is one of the longest legally-enslaved persons in the world, having been a slave for 15 years. She has spent almost all that time working as a slave prostitute, and has been heavily modified to keep her productive.": + case "She is the winner of a martial arts slave tournament. You won her in a bet.": + case "She offered herself to you for enslavement to escape having plastic surgery foisted on her.": + case "She was a runaway slave captured by a gang outside your arcology. You bought her cheap after she was harshly used by them.": + case "She was a student you enslaved when you evacuated her from a threatened old world grade school.": + case "She was a volleyball player you enslaved when you evacuated her from a broken down bus.": + case "She was an expectant mother you enslaved when you evacuated her from a threatened old world hospital.": + case "She was an orphan forced to live and steal on the streets until you adopted her.": + case "She was enslaved by you when you overcharged her for surgery.": + case "She was fresh from the slave markets when you acquired her.": + case "She was homeless and willing to do anything for food, which in the end resulted in her becoming a slave.": + case "She was previously owned by a creative sadist, who has left a variety of mental scars on her.": + case "She was sold to you by an anonymous person who wanted her to suffer.": + case "She was taken as a slave by a Sultan, who presented her as a gift to a surveyor.": + case "She was taken into your custody from an owner who treated her as an equal.": + case "She was the private slave of a con artist cult leader before he had to abandon her and flee.": + case "She was the result of an intruder brute forcing your firewall, overloading your pleasure sensors, and allowing a corrupted packet to slip by. With a quick wipe of your RAM and cache with some powerful liquor, you have no idea who planted her in your womb.": + case "You acquired her in the last stages of your career as a noted private military contractor.": + case "You acquired her in the last stages of your career as a successful venture capitalist.": + case "You bought her at auction.": + case "You bought her from The Cattle Ranch.": + case "You bought her from the enigmatic Futanari Sisters after they sold her into slavery.": + case "You bought her from the household liquidator.": + case "You bought her from the kidnappers' slave market, so she was probably forced into slavery.": + case "You bought her from the prestigious Hippolyta Academy.": + case "You bought her from the trainers' slave market after they put her through basic training.": + case "You bought her from the underage raiders' slave market.": + case "You bought out a deal involving her training to be an expert gelded sex slave.": + case "You brought her into the arcology mindbroken, little more than a human onahole.": + case "You brought her into the arcology mindbroken, little more than a walking collection of fuckable holes.": + case "You captured her during your transition to the arcology": + case "You conceived her after a male arcology owner, impressed by your work, rewarded you with a night you'll never forget.": + case "You enslaved her personally during the last stages of your slaving career.": + case "You helped her give birth, leaving her deeply indebted to you.": + case "You never thought you would be capable of impregnating yourself, but years of pleasuring yourself with yourself after missions managed to create her.": + case "You purchased her by special order.": + case "You purchased her from a King after she expressed knowledge of the prince's affair with another servant.": + case "You purchased her from FCTV's Home Slave Shopping stream channel.": + case "You received her as a gift from an arcology owner impressed by your work.": + case "You received her from a surgeon who botched an implant operation on her and needed to get her out of sight.": + case "You reserved a mindless slave like her from the Flesh Heap.": + case "You sentenced her to enslavement as a punishment for attempted burglary.": + case "You sentenced her to enslavement as a punishment for defying local racial segregation laws.": + case "You sentenced her to enslavement as a punishment for fraud and theft.": + case "You sentenced her to enslavement as a punishment for suspected escapism.": + case "You sentenced her to enslavement as a punishment for theft and battery.": + case "You sentenced her to enslavement for smuggling drugs into the arcology.": + case "You sired her after a female arcology owner, impressed by your work, rewarded you with a night you'll never forget.": + case "You sired her in yourself after an arcology owner, impressed by your work, rewarded you with a night you'll never forget.": + case "You turned her into a slave girl after she fell into debt to you.": + case "You won her at a shotgun match against other arcology owners.": + case "You won her at cards, a memento from your life as one of the idle rich before you became an arcology owner.": + slavetext = slavetext.replace(/\bherself\b/g, "$himself"); + slavetext = slavetext.replace(/\bHerself\b/g, "$Himself"); + slavetext = slavetext.replace(/\bshe\b/g, "$he"); + slavetext = slavetext.replace(/\bShe\b/g, "$He"); + slavetext = slavetext.replace(/\bher\b/g, "$him"); + slavetext = slavetext.replace(/\bHer\b/g, "$His"); + slavetext = slavetext.replace(/\b girl\b/g, " $girl"); + slavetext = slavetext.replace(/\b woman\b/g, " $woman"); + slavetext = slavetext.replace(/\${2,}/g, ''); + break; + default: + if ((slavetext.includes("was serving the public")) || (slavetext.includes("You bought her from"))) { + slavetext = slavetext.replace(/\bher\b/g, "$him"); + } else if (((slavetext.includes("Your lurcher")) && (slavetext.includes("coursing"))) || ((slavetext.includes("Your")) && (slavetext.includes("while raiding")))) { + slavetext = slavetext.replace(/\bher\b/g, "$him"); + slavetext = slavetext.replace(/\bshe\b/g, "$he"); + } else if (slavetext.includes("was once the young trophy husband of a powerful woman in the old world, but she sold")) { + slavetext = "$He was once the young trophy husband of a powerful woman in the old world, but she sold $him into slavery in revenge for $his infidelities."; + } else if (slavetext.includes("gargantuan dick to be a truly unique slave")) { + slavetext = "$He was raised as a girl despite $his gargantuan dick to be a truly unique slave."; + } else if (slavetext.includes("to enslavement for the attempted rape of a free woman")) { + slavetext = "You sentenced $him to enslavement for the attempted rape of a free woman."; + } else if (slavetext.includes("to enslavement as a punishment for the rape of a free woman")) { + slavetext = "You sentenced $him to enslavement as a punishment for the rape of a free woman."; + } else if (slavetext.includes("only way to obtain surgery to transform $him into a woman")) { + slavetext = "$He submitted to enslavement as $his only way to obtain surgery to transform $him into a woman."; + } else if (slavetext.includes("was sold as a slave to satisfy her spousal maintenance after divorce")) { + slavetext = "Once $he was an arcology security officer, lured to aphrodisiacs addiction and feminized by $his boss (and former wife), to whom $he was sold as a slave to satisfy her spousal maintenance after divorce."; + } else if (slavetext.includes("asked to be enslaved in the hope you'd treat a fellow woman well")) { + slavetext = "$He asked to be enslaved in the hope you'd treat a fellow woman well."; + } else { + slavetext = slavetext.replace(/\bherself\b/g, "$himself"); + slavetext = slavetext.replace(/\bHerself\b/g, "$Himself"); + slavetext = slavetext.replace(/\bshe\b/g, "$he"); + slavetext = slavetext.replace(/\bShe\b/g, "$He"); + slavetext = slavetext.replace(/\bher\b/g, "$his"); + slavetext = slavetext.replace(/\bHer\b/g, "$His"); + slavetext = slavetext.replace(/\b girl\b/g, " $girl"); + slavetext = slavetext.replace(/\b woman\b/g, " $woman"); + slavetext = slavetext.replace(/\${2,}/g, ''); + } + break; + } + return slavetext; +}; + +globalThis.convertCareer = function(slave) { + let job = slave.career; + if ((V.diversePronouns === 1) && (slave.pronoun === App.Data.Pronouns.Kind.male)) { + switch (job) { + case "a dominatrix": + job = "a dominator"; + break; + case "a farmer's daughter": + job = "a farmer's son"; + break; + case "a handmaiden": + job = "a handservant"; + break; + case "a lady courtier": + job = "a gentleman courtier"; + break; + case "a landlady": + job = "a landlord"; + break; + case "a madam": + job = "a brothel owner"; + break; + case "a maid": + job = "a houseservant"; + break; + case "a mail-order bride": + job = "a mail-order groom"; + break; + case "a mistress": + job = "a kept man"; + break; + case "a nun": + job = "a monk"; + break; + case "a nursemaid": + job = "a child's nurse"; + break; + case "a procuress": + job = "a procurer"; + break; + case "a shrine maiden": + job = "a shrine priest"; + break; + case "a trophy spouse": + job = "a trophy husband"; + break; + case "a weathergirl": + job = "a weatherman"; + break; + case "an air hostess": + job = "an air host"; + break; + case "being homeschooled by her parents": + job = "being homeschooled by his parents"; + break; + case "a camgirl": + case "a cowgirl": + case "a girl scout": + case "a paper girl": + case "a party girl": + job = job.replace(/girl/g, "boy"); + break; + case "a businesswoman": + case "a criminal businesswoman": + case "a delivery woman": + case "a fisherwoman": + case "a noblewoman": + case "a saleswoman": + case "a stuntwoman": + job = job.replace(/woman/g, "man"); + break; + case "a housewife": + case "a trophy wife": + job = job.replace(/wife/g, "husband"); + break; + case "a cocktail waitress": + case "a waitress": + case "a seamstress": + job = job.replace(/ress/g, "er"); + break; + case "a child actress": + case "an actress": + job = job.replace(/ress/g, "or"); + break; + } + } else if (slave.pronoun === App.Data.Pronouns.Kind.female) { + switch (job) { + case "a priest": + job = "a priestess"; + break; + case "a trophy spouse": + job = "a trophy wife"; + break; + case "a businessman": + case "a repairman": + job = job.replace(/man/g, "woman"); + break; + } + } + return job; +}; + +/** + * + * @param {App.Entity.SlaveState} slave + * @returns {string|null} + */ +globalThis.tutorForSlave = function(slave) { + for (const tutor of Object.keys(V.slaveTutor)) { + const pupils = V.slaveTutor[tutor]; + if (pupils.contains(slave.ID)) { + return tutor; + } + } + return null; +}; + +/** + * + * @param {string} skill + * @returns {number} + */ +globalThis.upgradeMultiplier = function(skill) { + if (skill === 'medicine' && V.PC.career === "medicine" || skill === 'engineering' && V.PC.career === "engineer" + || ((skill === 'medicine' || skill === 'engineering') && V.arcologies[0].FSRestartDecoration >= 100 && V.eugenicsFullControl === 0)) { + return 0.6; + } + if (V.PC.skill[skill] <= -100) { + return 1.5; + } else if (V.PC.skill[skill] <= -75) { + return 1.35; + } else if (V.PC.skill[skill] <= -50) { + return 1.25; + } else if (V.PC.skill[skill] <= -25) { + return 1.15; + } else if (V.PC.skill[skill] < 0) { + return 1.10; + } else if (V.PC.skill[skill] === 0) { + return 1; + } else if (V.PC.skill[skill] <= 10) { + return 0.97; + } else if (V.PC.skill[skill] <= 25) { + return 0.95; + } else if (V.PC.skill[skill] <= 50) { + return 0.90; + } else if (V.PC.skill[skill] <= 75) { + return 0.85; + } else if (V.PC.skill[skill] <= 100) { + return 0.80; + } else { + return 0.75; + } +}; + +/** + * Return a career at random that would be suitable for the given slave. + * Currently only considers their age + * @param {App.Entity.SlaveState} slave + * @returns {string} + */ +globalThis.randomCareer = function(slave) { + if (slave.actualAge < 16) { + return App.Data.Careers.General.veryYoung.random(); + } else if (slave.actualAge <= 24) { + return App.Data.Careers.General.young.random(); + } else if (slave.intelligenceImplant >= 10) { + return App.Data.Careers.General.educated.random(); + } else { + return App.Data.Careers.General.uneducated.random(); + } +}; + +/** + * @param {App.Entity.SlaveState} slave + */ +globalThis.resyncSlaveHight = function(slave) { + slave.height = Math.round(Height.random(slave)); +}; + +/** + * @param {App.Entity.SlaveState} slave + */ +globalThis.resyncSlaveToAge = function(slave) { + resyncSlaveHight(slave); + slave.pubertyXX = slave.actualAge < slave.pubertyAgeXX ? 0 : 1; + slave.pubertyXY = slave.actualAge < slave.pubertyAgeXY ? 0 : 1; + if (slave.actualAge < 12) { + slave.vagina = 0; + slave.trueVirgin = 1; + slave.preg = -1; + slave.belly = 0; + slave.bellyPreg = 0; + slave.ovaries = 1; + slave.anus = 0; + slave.skill.anal = 0; + slave.skill.oral = 0; + slave.skill.whoring = 0; + slave.skill.entertainment = 0; + slave.skill.combat = 0; + slave.skill.vaginal = 0; + slave.attrXY = 50; + slave.attrXX = 50; + slave.boobs = 200; + slave.birthWeek = 0; + SetBellySize(slave); + if (slave.dick > 0) { + slave.dick = 1; + } + if (slave.balls > 0) { + slave.balls = 1; + } + } else { + slave.boobs = Math.max(slave.boobs, 500); + if (slave.dick > 2) { + slave.dick = 2; + } + if (slave.balls > 2) { + slave.balls = 2; + } + } + slave.career = randomCareer(slave); +}; + +/** + * @param {string} raceName + * @returns {string} + */ +globalThis.randomRaceSkin = function(raceName) { + let skin; + switch (raceName) { + case "asian": + skin = jsEither(["dark olive", "light olive", "light"]); + break; + case "amerindian": + case "indo-aryan": + case "malay": + case "pacific islander": + skin = jsEither(["dark", "light"]); break; - case "You purchased her from a King after his son put an illegitimate heir in her womb.": - slavetext = "You purchased $him from a King after his son put an illegitimate heir in $his womb."; + case "black": + skin = jsEither(["black", "brown", "dark brown"]); break; - case "You purchased her in order to pave the way for her brother to take the throne.": - slavetext = "You purchased $him in order to pave the way for $his brother to take the throne."; + case "latina": + skin = jsEither(["brown", "dark brown", "dark olive", "light olive", "tan"]); break; - case "You purchased her indenture contract, making her yours for as long as it lasts.": - slavetext = "You purchased $his indenture contract, making $him yours for as long as it lasts."; + case "middle eastern": + case "semitic": + case "southern european": + skin = jsEither(["fair", "light olive", "light", "tan"]); break; - case "You sentenced her to enslavement as a punishment for attempted theft of a slave.": - slavetext = "You sentenced $him to enslavement as a punishment for attempted theft of a slave."; + case "white": + skin = jsEither(["fair", "light", "pale"]); break; - case "You sentenced her to enslavement as a punishment for dereliction of her duty to you as a mercenary and for theft.": - slavetext = "You sentenced $him to enslavement as a punishment for dereliction of $his duty to you as a mercenary and for theft."; + default: + skin = jsEither(["dark", "light", "pale"]); break; - case "You sentenced her to enslavement as a punishment for smuggling slaves within her body.": - slavetext = "You sentenced $him to enslavement as a punishment for smuggling slaves within $his body."; + } + return skin; +}; + +/** + * @param {FC.Race} raceName + * @returns {string} + */ +globalThis.randomRaceEye = function(raceName) { + let eye; + switch (raceName) { + case "asian": + case "black": + case "indo-aryan": + case "middle eastern": + case "pacific islander": + eye = jsEither(["brown", "brown", "brown", "brown", "green"]); break; - case "You stormed her arcology, killed her guards and enslaved her in revenge for insulting you at a dinner party.": - slavetext = "You stormed $his arcology, killed $his guards, and enslaved $him in revenge for insulting you at a dinner party."; + case "amerindian": + case "latina": + eye = jsEither(["brown", "brown", "brown", "green"]); break; - case "You tricked her into enslavement, manipulating her based on her surgical addiction.": - slavetext = "You tricked $him into enslavement, manipulating $him based on $his surgical addiction."; + case "malay": + case "southern european": + eye = jsEither(["blue", "brown", "brown", "brown", "brown", "brown", "brown", "green"]); break; - case "You tricked her mother into selling her into slavery to clear addiction debts.": - slavetext = "You tricked $his mother into selling $him into slavery to clear addiction debts."; + case "semitic": + case "white": + eye = jsEither(["blue", "brown", "brown", "brown", "green", "green"]); break; - case "You were acquainted with her before you were an arcology owner; your rival tried to use her to manipulate you, but you rescued her.": - slavetext = "You were acquainted with $him before you were an arcology owner; your rival tried to use $him to manipulate you, but you rescued $him."; + default: + eye = jsEither(["blue", "brown", "green"]); break; - case "Your slaving troop kept several girls as fucktoys; you sired her in your favorite.": - slavetext = "Your slaving troop kept several girls as fucktoys; you sired $him in your favorite."; + } + return eye; +}; + +/** + * @param {string} raceName + * @returns {string} + */ +globalThis.randomRaceHair = function(raceName) { + let hair; + switch (raceName) { + case "asian": + case "amerindian": + case "indo-aryan": + case "malay": + case "middle eastern": + case "pacific islander": + hair = jsEither(["black", "black", "black", "black", "black", "brown"]); break; - case "She was enslaved by you when you purchased her debt.": - slavetext = "$He was enslaved by you when you purchased $his debt."; + case "black": + case "latina": + case "semitic": + case "southern european": + hair = jsEither(["black", "black", "brown", "brown"]); break; - case "A fresh capture once overpowered you and had his way with you. You kept her as a painful reminder to never lower your guard again.": - case "Drugs and alcohol can be a potent mix; the night that followed it can sometimes be hard to remember. Needless to say, once your belly began swelling with her, you had to temporarily switch to a desk job for your mercenary group.": - case "Her musky milky aura drives men and women around her giggly and dumb with lust.": - case "She chose to be a slave because the romanticized view of it she had turns her on.": - case "She grew up sheltered and submissive, making her an easy target for enslavement.": - case "She has a faint air of fatigue about her, and strength too: that of a survivor.": - case "She has a following in slave pornography. Thousands have enjoyed her getting off from the suffering she caused.": - case "She has a following in slave pornography. Thousands have enjoyed her humiliating herself.": - case "She has a following in slave pornography. Thousands have enjoyed the sight of her being raped.": - case "She has a following in slave pornography. Thousands have enjoyed the sight of her being used.": - case "She has a following in slave pornography. Thousands have enjoyed the sight of her eating and gaining weight.": - case "She has a following in slave pornography. Thousands have enjoyed watching her abuse others.": - case "She has a following in slave pornography. Thousands have enjoyed watching her do anything and everything for cum.": - case "She has a following in slave pornography. Thousands have enjoyed watching her do anything for attention.": - case "She has a following in slave pornography. Thousands have enjoyed watching her happily suffer.": - case "She has a following in slave pornography. Thousands have enjoyed watching her obsess over pumping out babies.": - case "She has a following in slave pornography. Thousands have enjoyed watching her swell with child.": - case "She has a verbal tic that causes her to say 'ho, ho, ho' frequently.": - case "She has many surgical scars and something seems off about her.": - case "She is a complete mental blank; to her, there is only the Master.": - case "She is one of the longest legally-enslaved persons in the world, having been a slave for 15 years. She has spent almost all that time working as a slave prostitute, and has been heavily modified to keep her productive.": - case "She is the winner of a martial arts slave tournament. You won her in a bet.": - case "She offered herself to you for enslavement to escape having plastic surgery foisted on her.": - case "She was a runaway slave captured by a gang outside your arcology. You bought her cheap after she was harshly used by them.": - case "She was a student you enslaved when you evacuated her from a threatened old world grade school.": - case "She was a volleyball player you enslaved when you evacuated her from a broken down bus.": - case "She was an expectant mother you enslaved when you evacuated her from a threatened old world hospital.": - case "She was an orphan forced to live and steal on the streets until you adopted her.": - case "She was enslaved by you when you overcharged her for surgery.": - case "She was fresh from the slave markets when you acquired her.": - case "She was homeless and willing to do anything for food, which in the end resulted in her becoming a slave.": - case "She was previously owned by a creative sadist, who has left a variety of mental scars on her.": - case "She was sold to you by an anonymous person who wanted her to suffer.": - case "She was taken as a slave by a Sultan, who presented her as a gift to a surveyor.": - case "She was taken into your custody from an owner who treated her as an equal.": - case "She was the private slave of a con artist cult leader before he had to abandon her and flee.": - case "She was the result of an intruder brute forcing your firewall, overloading your pleasure sensors, and allowing a corrupted packet to slip by. With a quick wipe of your RAM and cache with some powerful liquor, you have no idea who planted her in your womb.": - case "You acquired her in the last stages of your career as a noted private military contractor.": - case "You acquired her in the last stages of your career as a successful venture capitalist.": - case "You bought her at auction.": - case "You bought her from The Cattle Ranch.": - case "You bought her from the enigmatic Futanari Sisters after they sold her into slavery.": - case "You bought her from the household liquidator.": - case "You bought her from the kidnappers' slave market, so she was probably forced into slavery.": - case "You bought her from the prestigious Hippolyta Academy.": - case "You bought her from the trainers' slave market after they put her through basic training.": - case "You bought her from the underage raiders' slave market.": - case "You bought out a deal involving her training to be an expert gelded sex slave.": - case "You brought her into the arcology mindbroken, little more than a human onahole.": - case "You brought her into the arcology mindbroken, little more than a walking collection of fuckable holes.": - case "You captured her during your transition to the arcology": - case "You conceived her after a male arcology owner, impressed by your work, rewarded you with a night you'll never forget.": - case "You enslaved her personally during the last stages of your slaving career.": - case "You helped her give birth, leaving her deeply indebted to you.": - case "You never thought you would be capable of impregnating yourself, but years of pleasuring yourself with yourself after missions managed to create her.": - case "You purchased her by special order.": - case "You purchased her from a King after she expressed knowledge of the prince's affair with another servant.": - case "You purchased her from FCTV's Home Slave Shopping stream channel.": - case "You received her as a gift from an arcology owner impressed by your work.": - case "You received her from a surgeon who botched an implant operation on her and needed to get her out of sight.": - case "You reserved a mindless slave like her from the Flesh Heap.": - case "You sentenced her to enslavement as a punishment for attempted burglary.": - case "You sentenced her to enslavement as a punishment for defying local racial segregation laws.": - case "You sentenced her to enslavement as a punishment for fraud and theft.": - case "You sentenced her to enslavement as a punishment for suspected escapism.": - case "You sentenced her to enslavement as a punishment for theft and battery.": - case "You sentenced her to enslavement for smuggling drugs into the arcology.": - case "You sired her after a female arcology owner, impressed by your work, rewarded you with a night you'll never forget.": - case "You sired her in yourself after an arcology owner, impressed by your work, rewarded you with a night you'll never forget.": - case "You turned her into a slave girl after she fell into debt to you.": - case "You won her at a shotgun match against other arcology owners.": - case "You won her at cards, a memento from your life as one of the idle rich before you became an arcology owner.": - slavetext = slavetext.replace(/\bherself\b/g, "$himself"); - slavetext = slavetext.replace(/\bHerself\b/g, "$Himself"); - slavetext = slavetext.replace(/\bshe\b/g, "$he"); - slavetext = slavetext.replace(/\bShe\b/g, "$He"); - slavetext = slavetext.replace(/\bher\b/g, "$him"); - slavetext = slavetext.replace(/\bHer\b/g, "$His"); - slavetext = slavetext.replace(/\b girl\b/g, " $girl"); - slavetext = slavetext.replace(/\b woman\b/g, " $woman"); - slavetext = slavetext.replace(/\${2,}/g, ''); + case "white": + hair = jsEither(["black", "black", "blonde", "brown", "brown", "red"]); break; default: - if ((slavetext.includes("was serving the public")) || (slavetext.includes("You bought her from"))) { - slavetext = slavetext.replace(/\bher\b/g, "$him"); - } else if (((slavetext.includes("Your lurcher")) && (slavetext.includes("coursing"))) || ((slavetext.includes("Your")) && (slavetext.includes("while raiding")))) { - slavetext = slavetext.replace(/\bher\b/g, "$him"); - slavetext = slavetext.replace(/\bshe\b/g, "$he"); - } else if (slavetext.includes("was once the young trophy husband of a powerful woman in the old world, but she sold")) { - slavetext = "$He was once the young trophy husband of a powerful woman in the old world, but she sold $him into slavery in revenge for $his infidelities."; - } else if (slavetext.includes("gargantuan dick to be a truly unique slave")) { - slavetext = "$He was raised as a girl despite $his gargantuan dick to be a truly unique slave."; - } else if (slavetext.includes("to enslavement for the attempted rape of a free woman")) { - slavetext = "You sentenced $him to enslavement for the attempted rape of a free woman."; - } else if (slavetext.includes("to enslavement as a punishment for the rape of a free woman")) { - slavetext = "You sentenced $him to enslavement as a punishment for the rape of a free woman."; - } else if (slavetext.includes("only way to obtain surgery to transform $him into a woman")) { - slavetext = "$He submitted to enslavement as $his only way to obtain surgery to transform $him into a woman."; - } else if (slavetext.includes("was sold as a slave to satisfy her spousal maintenance after divorce")) { - slavetext = "Once $he was an arcology security officer, lured to aphrodisiacs addiction and feminized by $his boss (and former wife), to whom $he was sold as a slave to satisfy her spousal maintenance after divorce."; - } else if (slavetext.includes("asked to be enslaved in the hope you'd treat a fellow woman well")) { - slavetext = "$He asked to be enslaved in the hope you'd treat a fellow woman well."; - } else { - slavetext = slavetext.replace(/\bherself\b/g, "$himself"); - slavetext = slavetext.replace(/\bHerself\b/g, "$Himself"); - slavetext = slavetext.replace(/\bshe\b/g, "$he"); - slavetext = slavetext.replace(/\bShe\b/g, "$He"); - slavetext = slavetext.replace(/\bher\b/g, "$his"); - slavetext = slavetext.replace(/\bHer\b/g, "$His"); - slavetext = slavetext.replace(/\b girl\b/g, " $girl"); - slavetext = slavetext.replace(/\b woman\b/g, " $woman"); - slavetext = slavetext.replace(/\${2,}/g, ''); - } + hair = jsEither(["black", "black", "black", "black", "blonde", "brown", "brown", "red"]); break; } - return slavetext; + return hair; +}; + +/** + * @param {string} skinTone + * @returns {number} + */ +globalThis.skinToneLevel = function(skinTone) { + if (!App.Medicine.Modification.naturalSkins.includes(skinTone)) { + return undefined; + } + const skinToMelanin = { + "pure black": 25, + "ebony": 24, + "black": 23, + "dark brown": 22, + "brown": 21, + "light brown": 20, + "dark beige": 19, + "beige": 18, + "light beige": 17, + "dark": 16, + "dark olive": 15, + "bronze": 14, + "olive": 13, + "tan": 12, + "light olive": 11, + "light": 10, + "fair": 9, + "very fair": 8, + "extremely fair": 7, + "pale": 6, + "very pale": 5, + "extremely pale": 4, + "white": 3, + "ivory": 2, + "pure white": 1 + }; + return skinToMelanin[skinTone]; +}; + +/** + * Increase or decrease skinTone + * @param {string} skin + * @param {number} value + * @returns {string} + */ +globalThis.changeSkinTone = function(skin, value) { + if (!App.Medicine.Modification.naturalSkins.includes(skin)) { + return skin; + } + const skinToMelanin = { + "pure black": 25, + "ebony": 24, + "black": 23, + "dark brown": 22, + "brown": 21, + "light brown": 20, + "dark beige": 19, + "beige": 18, + "light beige": 17, + "dark": 16, + "dark olive": 15, + "bronze": 14, + "olive": 13, + "tan": 12, + "light olive": 11, + "light": 10, + "fair": 9, + "very fair": 8, + "extremely fair": 7, + "pale": 6, + "very pale": 5, + "extremely pale": 4, + "white": 3, + "ivory": 2, + "pure white": 1 + }; + let newSkin = (skinToMelanin[skin] + value); + if (newSkin > 25) { + newSkin = 25; + } else if (newSkin < 1) { + newSkin = 1; + } + let prop; + for (prop in skinToMelanin) { + if (!skinToMelanin.hasOwnProperty(prop)) { + continue; + } + if (newSkin >= skinToMelanin[prop]) { + return prop; + } + } + return prop; }; + /** - * Converts an array of strings into a sentence parted by commas. - * @param {Array} array ["apple", "bannana", "carrot"] - * @returns {string} "apple, bannana and carrot" + * @param {string} color + * @returns {number} */ -globalThis.arrayToSentence = function(array) { - return array.reduce((res, ch, i, arr) => res + (i === arr.length - 1 ? ' and ' : ', ') + ch); +globalThis.nippleColorLevel = function(color) { + if (!App.Medicine.Modification.naturalNippleColors.includes(color)) { + return undefined; + } + const nippleColor = { + "ebony": 8, + "black": 7, + "dark brown": 6, + "brown": 5, + "light brown": 4, + "pink": 3, + "pale pink": 2, + "ivory": 1, + }; + return nippleColor[color]; }; -globalThis.convertCareer = function(slave) { - let job = slave.career; - if ((V.diversePronouns === 1) && (slave.pronoun === App.Data.Pronouns.Kind.male)) { - switch (job) { - case "a dominatrix": - job = "a dominator"; - break; - case "a farmer's daughter": - job = "a farmer's son"; - break; - case "a handmaiden": - job = "a handservant"; - break; - case "a lady courtier": - job = "a gentleman courtier"; - break; - case "a landlady": - job = "a landlord"; - break; - case "a madam": - job = "a brothel owner"; - break; - case "a maid": - job = "a houseservant"; - break; - case "a mail-order bride": - job = "a mail-order groom"; - break; - case "a mistress": - job = "a kept man"; - break; - case "a nun": - job = "a monk"; - break; - case "a nursemaid": - job = "a child's nurse"; - break; - case "a procuress": - job = "a procurer"; - break; - case "a shrine maiden": - job = "a shrine priest"; - break; - case "a trophy spouse": - job = "a trophy husband"; - break; - case "a weathergirl": - job = "a weatherman"; - break; - case "an air hostess": - job = "an air host"; - break; - case "being homeschooled by her parents": - job = "being homeschooled by his parents"; - break; - case "a camgirl": - case "a cowgirl": - case "a girl scout": - case "a paper girl": - case "a party girl": - job = job.replace(/girl/g, "boy"); - break; - case "a businesswoman": - case "a criminal businesswoman": - case "a delivery woman": - case "a fisherwoman": - case "a noblewoman": - case "a saleswoman": - case "a stuntwoman": - job = job.replace(/woman/g, "man"); - break; - case "a housewife": - case "a trophy wife": - job = job.replace(/wife/g, "husband"); - break; - case "a cocktail waitress": - case "a waitress": - case "a seamstress": - job = job.replace(/ress/g, "er"); - break; - case "a child actress": - case "an actress": - job = job.replace(/ress/g, "or"); - break; - } - } else if (slave.pronoun === App.Data.Pronouns.Kind.female) { - switch (job) { - case "a priest": - job = "a priestess"; - break; - case "a trophy spouse": - job = "a trophy wife"; - break; - case "a businessman": - case "a repairman": - job = job.replace(/man/g, "woman"); - break; - } - } - return job; +/** + * Sets temporary variables named by the scheme, described below, to pronouns for the given slave + * @param {App.Entity.SlaveState} slave + * @param {any} [suffix] pronounsSuffix. Anything that can be converted to string. + * @param {string[]} [pronouns] requested pronouns. Defaults to all pronoun forms. + * + * The variables naming scheme is the pronoun name (he, his, etc.) and optional suffix. If the suffix is empty, the variables + * will be set as story variables, otherwise as temporary variables. + * This way for a call App.Utils.setLocalPronouns(slave) there will be story variables "$he", "$his", for + * App.Utils.setLocalPronouns(slave, 1): _he1, _his1 and so on. + */ +App.Utils.setLocalPronouns = function(slave, suffix, pronouns) { + const ps = getPronouns(slave); + /** @type {string} */ + const pSuffix = suffix !== undefined ? suffix.toString() : ''; + pronouns = pronouns || [ // Object.getOwnPropertyNames(ps) ? + 'he', 'him', 'his', 'himself', 'boy', + 'He', 'Him', 'His', 'Himself', 'Boy', + 'man', 'men', 'shota', 'son', 'brother', 'husband', 'husbands', 'father', 'fathers', + 'Man', 'Men', 'Shota', 'Son', 'Brother', 'Husband', 'Husbands', 'Father', 'Fathers', + 'she', 'her', 'hers', 'herself', 'girl', + 'She', 'Her', 'Hers', 'Herself', 'Girl', + 'woman', 'women', 'loli', 'daughter', 'sister', 'wife', 'wives', 'mother', 'mothers', + 'Woman', 'Women', 'Loli', 'Daughter', 'Sister', 'Wife', 'Wives', 'Mother', 'Mothers' + ]; // Pronouns always refer to the slave in question, never any relation of theirs. It is "mother" as in "she is a mother of many" not "you are her mother". Plural pronouns would refer to "wives like her," not "her wives." + + const scope = pSuffix.length === 0 ? V : State.temporary; + pronouns.forEach(p => { + scope[p + pSuffix] = ps[p]; + }); }; /** - * @param {string} targetSkill - Skill to be checked. - * @param {Object} slave - Slave to be checked. - * @param {number} [skillIncrease=1] + * Fix nationalities as adjectives + * @param {string} nation * @returns {string} */ -globalThis.slaveSkillIncrease = function(targetSkill, slave, skillIncrease = 1) { - let r = "", skillDec; - const {He, his, him} = getPronouns(slave); - const isleadershipRole = function() { - if (['headGirl', 'recruiter', 'bodyguard', 'madam', 'DJ', 'nurse', 'teacher', 'attendant', 'matron', 'stewardess', 'milkmaid', 'farmer', 'wardeness'].includes(targetSkill)) { - return true; - } - return false; - }; +globalThis.aNational = function(nation) { + let country; + if (nation === "a Cook Islander") { + country = "Cook Islander"; + } else if (nation === "a Liechtensteiner") { + country = "Liechtensteiner"; + } else if (nation === "a New Zealander") { + country = "New Zealander"; + } else if (nation === "a Solomon Islander") { + country = "Solomon Islander"; + } else { + country = nation; + } + return country; +}; - if (slave.skill[targetSkill] <= 10) { - switch(targetSkill) { - case 'oral': - case 'vaginal': - case 'anal': - skillDec = `knowledge about ${targetSkill} sex,`; break; - case 'whoring': - skillDec = `knowledge about how to whore,`; break; - case 'entertainment': - skillDec = `knowledge about how to be entertaining,`; break; - } - if (isleadershipRole()) { - skillDec = `${capFirstChar(targetSkill)} skills.`; - } +/** + * Fix nationalities as plurals + * @param {string} nation + * @returns {string} + */ +globalThis.moreNational = function(nation) { + let country; + if (nation === "a Cook Islander") { + country = "Cook Islander"; + } else if (nation === "a Liechtensteiner") { + country = "Liechtensteiner"; + } else if (nation === "Mosotho") { + country = "Basotho"; + } else if (nation === "Motswana") { + country = "Batswana"; + } else if (nation === "a New Zealander") { + country = "New Zealander"; + } else if (nation === "a Solomon Islander") { + country = "Solomon Islander"; + } else { + country = nation; + } + return country; +}; - if (slave.skill[targetSkill] + skillIncrease > 10) { - r = `<span class="green">${He} now has basic ${skillDec}</span>`; - switch(targetSkill) { - case 'oral': - r += ` and at least suck a dick without constant gagging.`; break; - case 'vaginal': - r += ` and can avoid some of the common pitfalls and turnoffs.`; break; - case 'anal': - r += ` and can accept penetration of ${his} anus without danger.`; break; - case 'whoring': - r += ` and can avoid some potentially dangerous situations.`; break; - case 'entertainment': - r += ` and can usually avoid serious faux pas.`; break; - } - } - } else if (slave.skill[targetSkill] <= 30) { - switch(targetSkill) { - case 'oral': - case 'vaginal': - case 'anal': - skillDec = `${targetSkill} skills,`; break; - case 'whoring': - skillDec = `skill as a whore,`; break; - case 'entertainment': - skillDec = `skill as an entertainer,`; break; - } - if (isleadershipRole()) { - skillDec = `skill as a ${capFirstChar(targetSkill)}.`; +/** Deflate a slave (reset inflation to none) + * @param {App.Entity.SlaveState} slave + */ +globalThis.deflate = function(slave) { + slave.inflation = 0; + slave.inflationType = "none"; + slave.inflationMethod = 0; + slave.milkSource = 0; + slave.cumSource = 0; + SetBellySize(slave); +}; + +/** + * colors skin, eyes and hair based on genetic Color. + * Takes .override_*_Color into account. + * + * @param {App.Entity.SlaveState} slave + */ +globalThis.applyGeneticColor = function(slave) { + if (slave.override_Eye_Color !== 1) { + resetEyeColor(slave, "both"); + } + if (slave.override_H_Color !== 1) { + slave.hColor = getGeneticHairColor(slave); + } + if (slave.override_Arm_H_Color !== 1) { + slave.underArmHColor = getGeneticHairColor(slave); + } + if (slave.override_Pubic_H_Color !== 1) { + slave.pubicHColor = getGeneticHairColor(slave); + } + if (slave.override_Brow_H_Color !== 1) { + slave.eyebrowHColor = getGeneticHairColor(slave); + } + if (slave.override_Skin !== 1) { + if (!(slave.skin === "sun tanned" || slave.skin === "spray tanned")) { + slave.skin = getGeneticSkinColor(slave); } + } +}; - if (slave.skill.oral + skillIncrease > 30) { - r = `<span class="green">${He} now has some ${skillDec}</span>`; - switch(targetSkill) { - case 'oral': - r += ` and can reliably bring dicks and pussies to climax with ${his} mouth.`; break; - case 'vaginal': - r += ` and can do more than just lie there and take it.`; break; - case 'anal': - r += ` and needs less preparation before taking rough penetration.`; break; - case 'whoring': - r += ` and knows how to sell ${his} body at a good price.`; break; - case 'entertainment': - r += ` and can flirt, dance, and strip acceptably.`; break; - } +/** + * @param {FC.GingeredSlave} slave + */ +globalThis.newSlave = function(slave) { + if (getSlave(slave.ID)) { + throw "Slave already exists"; + } + + // if the slave is gingered, remove the gingering proxy + if (slave.beforeGingering) { + slave = slave.beforeGingering; + } + + if (slave.override_Race !== 1) { + slave.origRace = slave.race; + } + + applyGeneticColor(slave); + + /* eslint-disable camelcase */ + slave.override_Race = 0; + slave.override_H_Color = 0; + slave.override_Arm_H_Color = 0; + slave.override_Pubic_H_Color = 0; + slave.override_Brow_H_Color = 0; + slave.override_Skin = 0; + slave.override_Eye_Color = 0; + /* eslint-enable camelcase */ + + // too tall to be a dwarf catch for event slaves + if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism !== 2 && slave.height > 165) { + slave.geneticQuirks.dwarfism = 1; + } + + if (V.surnamesForbidden === 1) { + slave.slaveSurname = 0; + } + + if (slave.preg > 0) { + slave.pregWeek = slave.preg; + } else { + slave.pregWeek = 0; + } + + if (slave.clone !== 0) { + slave.canRecruit = 0; + } + + slave.sisters = 0; + slave.daughters = 0; + if (slave.mother === -1 || slave.father === -1) { + V.PC.daughters += 1; + } + if (areSisters(V.PC, slave) > 0) { + V.PC.sisters += 1; + } + for (let k = 0; k < V.slaves.length; k++) { + if (V.slaves[k].mother === slave.ID || V.slaves[k].father === slave.ID) { + slave.daughters++; } - } else if (slave.skill[targetSkill] <= 60) { - switch(targetSkill) { - case 'oral': - case 'vaginal': - case 'anal': - skillDec = `${targetSkill} sex expert,`; break; - case 'whoring': - skillDec = `expert whore,`; break; - case 'entertainment': - skillDec = `expert entertainer,`; break; + if (slave.mother === V.slaves[k].ID || slave.father === V.slaves[k].ID) { + V.slaves[k].daughters++; } - if (isleadershipRole()) { - skillDec = `expert ${capFirstChar(targetSkill)}.`; + if (areSisters(V.slaves[k], slave) > 0) { + slave.sisters++; + V.slaves[k].sisters++; } + } - if (slave.skill[targetSkill] + skillIncrease > 60) { - r = `<span class="green">${He} is now an ${skillDec}</span>`; - switch(targetSkill) { - case 'oral': - r += ` and has a delightfully experienced tongue.`; break; - case 'vaginal': - r += ` and has the muscular control to massage anything that's inside ${him}.`; break; - case 'anal': - r += ` and knows how to use ${his} sphincter to please.`; break; - case 'whoring': - r += ` and can often make clients forget that $he's a prostitute they're paying for.`; break; - case 'entertainment': - r += ` and can flirt engagingly, dance alluringly, and strip arousingly.`; break; + if (slave.genes === "XX") { + if (slave.pubertyXX === 1) { + if (slave.pubertyXY === 1) { + slave.hormoneBalance = 20; + } else { + slave.hormoneBalance = 60; + } + } else { + if (slave.pubertyXY === 1) { + slave.hormoneBalance = -20; + } else { + slave.hormoneBalance = 20; } } - } else if (slave.skill[targetSkill] < 100) { - switch(targetSkill) { - case 'oral': - case 'vaginal': - case 'anal': - skillDec = `has mastered ${targetSkill} sex,`; break; - case 'whoring': - skillDec = `is now a masterful whore,`; break; - case 'entertainment': - skillDec = `is now a masterful entertainer,`; break; - } - if (isleadershipRole()) { - skillDec = `is now a masterful ${capFirstChar(targetSkill)}.`; - } - - if (slave.skill[targetSkill] + skillIncrease >= 100) { - r = `<span class="green">${He} ${skillDec}</span>`; - switch(targetSkill) { - case 'oral': - r += ` and can learn nothing more about sucking dick or eating pussy.`; break; - case 'vaginal': - r += ` and can learn nothing more about tribbing or taking dick.`; break; - case 'anal': - r += ` and can learn nothing more about taking it up the ass.`; break; - case 'whoring': - r += ` and can learn nothing more about prostitution.`; break; - case 'entertainment': - r += ` and can learn nothing more about flirting, dancing, or stripping.`; break; + } else if (slave.genes === "XY") { + if (slave.pubertyXX === 1) { + if (slave.pubertyXY === 1) { + slave.hormoneBalance = 20; + } else { + slave.hormoneBalance = 40; + } + } else { + if (slave.pubertyXY === 1) { + slave.hormoneBalance = -40; + } else { + slave.hormoneBalance = 20; } } } - if (isleadershipRole() && slave.skill[targetSkill] + skillIncrease >= 100) { - V.tutorGraduate.push(slave.ID); - V.slaveTutor[capFirstChar(targetSkill)].delete(slave.ID); + if (slave.dick > 0 && + slave.balls > 0 && + slave.vagina < 0 && + slave.anus === 0 && + slave.genes === "XY" && + slave.faceShape === "masculine" && + slave.attrXY <= 35 && + slave.boobs < 400 && + slave.hormoneBalance < 0) { + V.REFeminizationCheckinIDs.push(slave.ID); + } + if (slave.actualAge > 35 && slave.face <= 10 && slave.faceImplant === 0 && slave.energy <= 60) { + V.REMILFCheckinIDs.push(slave.ID); + } + if (slave.attrXY <= 35 && slave.attrXX > 65) { + V.REOrientationCheckinIDs.push(slave.ID); + } + if (slave.face < -10) { + V.REUglyCheckinIDs.push(slave.ID); + } + if (slave.anus < 2) { + V.REButtholeCheckinIDs.push(slave.ID); + } + if (slave.boobs < 800) { + V.REReductionCheckinIDs.push(slave.ID); } - slave.skill[targetSkill] += skillIncrease; - return r; -}; -globalThis.tutorForSlave = function(slave) { - for (const tutor of Object.keys(V.slaveTutor)) { - const pupils = V.slaveTutor[tutor]; - if (pupils.contains(slave.ID)) { - return tutor; + generatePronouns(slave); + SetBellySize(slave); + V.slaveIndices[slave.ID] = V.slaves.push(slave) - 1; + + if (slave.origin !== "$He was your slave, but you freed $him, which $he repaid by participating in a coup attempt against you. It failed, and $he is again your chattel." && slave.ID !== V.boomerangSlave.ID) { + V.genePool.push(clone(slave)); + } else { + if (!V.genePool.some(s => s.ID === slave.ID)) { + V.genePool.push(slave); } } - return null; -}; -globalThis.upgradeMultiplier = function(skill) { - if (skill === 'medicine' && V.PC.career === "medicine" || skill === 'engineering' && V.PC.career === "engineer" - || ((skill === 'medicine' || skill === 'engineering') && V.arcologies[0].FSRestartDecoration >= 100 && V.eugenicsFullControl === 0)) { - return 0.6; - } - if (V.PC.skill[skill] <= -100) { - return 1.5; - } else if (V.PC.skill[skill] <= -75) { - return 1.35; - } else if (V.PC.skill[skill] <= -50) { - return 1.25; - } else if (V.PC.skill[skill] <= -25) { - return 1.15; - } else if (V.PC.skill[skill] < 0) { - return 1.10; - } else if (V.PC.skill[skill] === 0) { - return 1; - } else if (V.PC.skill[skill] <= 10) { - return 0.97; - } else if (V.PC.skill[skill] <= 25) { - return 0.95; - } else if (V.PC.skill[skill] <= 50) { - return 0.90; - } else if (V.PC.skill[skill] <= 75) { - return 0.85; - } else if (V.PC.skill[skill] <= 100) { - return 0.80; - } else { - return 0.75; + /* special case for dulling intelligence via drugs in slave acquisition */ + if (slave.dullIntelligence) { + slave.intelligence = -100; + delete slave.dullIntelligence; } -}; -/** - * Return a career at random that would be suitable for the given slave. - * Currently only considers their age - * @param {App.Entity.SlaveState} slave - * @returns {string} - */ -globalThis.randomCareer = function(slave) { - if (slave.actualAge < 16) { - return App.Data.Careers.General.veryYoung.random(); - } else if (slave.actualAge <= 24) { - return App.Data.Careers.General.young.random(); - } else if (slave.intelligenceImplant >= 10) { - return App.Data.Careers.General.educated.random(); + if (slave.assignment) { + assignJob(slave, slave.assignment); } else { - return App.Data.Careers.General.uneducated.random(); + slave.assignment = Job.CHOICE; } -}; -/** - * @param {App.Entity.SlaveState} slave - */ -globalThis.resyncSlaveHight = function(slave) { - slave.height = Math.round(Height.random(slave)); + /** do not run the Rules Assistant before adding the new slave to the slaves list! **/ + if (V.ui !== "start" && V.universalRulesNewSlavesRA === 1 && V.rulesAssistantAuto !== 0) { + DefaultRules(slave); + } }; /** * @param {App.Entity.SlaveState} slave + * @returns {number} */ -globalThis.resyncSlaveToAge = function(slave) { - resyncSlaveHight(slave); - slave.pubertyXX = slave.actualAge < slave.pubertyAgeXX ? 0 : 1; - slave.pubertyXY = slave.actualAge < slave.pubertyAgeXY ? 0 : 1; - if (slave.actualAge < 12) { - slave.vagina = 0; - slave.trueVirgin = 1; - slave.preg = -1; - slave.belly = 0; - slave.bellyPreg = 0; - slave.ovaries = 1; - slave.anus = 0; - slave.skill.anal = 0; - slave.skill.oral = 0; - slave.skill.whoring = 0; - slave.skill.entertainment = 0; - slave.skill.combat = 0; - slave.skill.vaginal = 0; - slave.attrXY = 50; - slave.attrXX = 50; - slave.boobs = 200; - slave.birthWeek = 0; - SetBellySize(slave); - if (slave.dick > 0) { - slave.dick = 1; - } - if (slave.balls > 0) { - slave.balls = 1; - } - } else { - slave.boobs = Math.max(slave.boobs, 500); - if (slave.dick > 2) { - slave.dick = 2; - } - if (slave.balls > 2) { - slave.balls = 2; +globalThis.fetishChangeChance = function(slave) { + let chance = 0, + fetish = (slave.fetishStrength / 4), + sex = 0; + + if (slave.clitSetting !== slave.fetish) { + // fetish should be more uncertain leading towards puberty and then steadily become more set in stone afterwards + if (slave.balls) { + if (V.potencyAge >= slave.actualAge) { + sex = (50 - ((V.potencyAge - slave.actualAge) * 10)); + fetish = (slave.fetishStrength / 2); + } else { + sex = ((slave.actualAge - V.potencyAge) / 4); + } + } else if (slave.ovaries || slave.mpreg) { + if (V.fertilityAge >= slave.actualAge) { + sex = (50 - ((V.fertilityAge - slave.actualAge) * 10)); + fetish = (slave.fetishStrength / 2); + } else { + sex = ((slave.actualAge - V.fertilityAge) / 4); + } } + chance = Math.trunc(Math.clamp((slave.devotion / 4) - (fetish) - (sex), 0, 100)); } - slave.career = randomCareer(slave); + + return chance; }; /** - * @param {string} raceName + * @param {App.Entity.SlaveState} slave * @returns {string} */ -globalThis.randomRaceSkin = function(raceName) { - let skin; - switch (raceName) { - case "asian": - skin = jsEither(["dark olive", "light olive", "light"]); - break; - case "amerindian": - case "indo-aryan": - case "malay": - case "pacific islander": - skin = jsEither(["dark", "light"]); - break; - case "black": - skin = jsEither(["black", "brown", "dark brown"]); - break; - case "latina": - skin = jsEither(["brown", "dark brown", "dark olive", "light olive", "tan"]); - break; - case "middle eastern": - case "semitic": - case "southern european": - skin = jsEither(["fair", "light olive", "light", "tan"]); - break; - case "white": - skin = jsEither(["fair", "light", "pale"]); - break; - default: - skin = jsEither(["dark", "light", "pale"]); - break; +globalThis.SlaveFullBirthName = function(slave) { + const pair = slave.birthSurname ? [slave.birthName, slave.birthSurname] : [slave.birthName]; + if ((V.surnameOrder !== 1 && ["Cambodian", "Chinese", "Hungarian", "Japanese", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes(slave.nationality)) || (V.surnameOrder === 2)) { + pair.reverse(); } - return skin; + return pair.join(" "); }; /** - * @param {FC.Race} raceName + * @param {App.Entity.SlaveState} slave * @returns {string} */ -globalThis.randomRaceEye = function(raceName) { - let eye; - switch (raceName) { - case "asian": - case "black": - case "indo-aryan": - case "middle eastern": - case "pacific islander": - eye = jsEither(["brown", "brown", "brown", "brown", "green"]); - break; - case "amerindian": - case "latina": - eye = jsEither(["brown", "brown", "brown", "green"]); - break; - case "malay": - case "southern european": - eye = jsEither(["blue", "brown", "brown", "brown", "brown", "brown", "brown", "green"]); - break; - case "semitic": - case "white": - eye = jsEither(["blue", "brown", "brown", "brown", "green", "green"]); - break; - default: - eye = jsEither(["blue", "brown", "green"]); - break; +globalThis.PoliteRudeTitle = function(slave) { + const PC = V.PC; + const {s, ss, title} = getEnunciation(slave); + + let r = ""; + if (slave.nationality === "Japanese") { + if (slave.trust > 0) { + r += `${PC.slaveName}${PC.title > 0 ? "kun" : "chan"}`; + } else { + r += (PC.slaveSurname ? PC.slaveSurname : `${PC.slaveName}${s}an`); + } + } else { + if (slave.intelligence + slave.intelligenceImplant < -95) { + r += title; + } else if (slave.intelligence + slave.intelligenceImplant > 50) { + r += (PC.title > 0 ? `Ma${s}ter` : `Mi${s}tre${ss}`); + } else if (slave.trust > 0) { + r += PC.slaveName; + } else { + r += (PC.slaveSurname ? PC.slaveSurname : PC.slaveName); + } } - return eye; + return r; }; /** - * @param {string} raceName + * @param {App.Entity.SlaveState} slave * @returns {string} */ -globalThis.randomRaceHair = function(raceName) { - let hair; - switch (raceName) { - case "asian": - case "amerindian": - case "indo-aryan": - case "malay": - case "middle eastern": - case "pacific islander": - hair = jsEither(["black", "black", "black", "black", "black", "brown"]); - break; - case "black": - case "latina": - case "semitic": - case "southern european": - hair = jsEither(["black", "black", "brown", "brown"]); - break; - case "white": - hair = jsEither(["black", "black", "blonde", "brown", "brown", "red"]); - break; - default: - hair = jsEither(["black", "black", "black", "black", "blonde", "brown", "brown", "red"]); - break; +globalThis.SlaveTitle = function(slave) { + let r; + if (V.newDescriptions === 1) { + if (slave.dick > 0 && slave.balls > 0 && slave.boobs > 300 && slave.vagina > -1 && slave.ovaries === 1) { + if (jsRandom(1, 100) > 50) { + r = "futanari"; + } else { + r = "herm"; + } + } else if (slave.dick > 0 && slave.balls === 0 && slave.boobs > 300 && slave.vagina > -1 && slave.ovaries === 1) { + r = "dickgirl"; + } else if (slave.dick > 0 && slave.vagina > -1 && slave.ovaries === 0) { + r = "shemale"; + } else if (slave.dick > 0 && slave.balls === 0 && slave.vagina === -1 && slave.ovaries === 0) { + r = "eunuch"; + } else if (slave.dick > 0 && slave.balls > 0 && slave.vagina === -1 && slave.ovaries === 0) { + if (slave.face > 10 && slave.hips > -1 && slave.shoulders < 1 && slave.faceShape !== "masculine") { + r = "trap"; + } else if (slave.boobs > 800) { + r = "tittyboy"; + } else if (slave.dick === 1 && slave.balls === 1) { + r = "sissy"; + } else if (slave.dick > 1 && slave.balls > 1 && slave.height < 165 && slave.muscles < 5 && slave.visualAge < 19 && slave.faceShape !== "masculine") { + r = "twink"; + } else if (slave.dick > 1 && slave.balls > 1 && slave.height < 160 && slave.muscles < 5 && slave.visualAge < 19) { + r = "boytoy"; + } else if (slave.muscles > 95 && slave.height >= 185) { + r = "titan"; + } else if (slave.muscles > 30) { + r = "muscleboy"; + } else { + r = "slaveboy"; + } + } else if (slave.dick === 0 && slave.balls === 0 && slave.vagina > -1) { + if ((slave.shoulders > slave.hips || slave.faceShape === "masculine") && slave.boobs < 400 && slave.genes === "XY") { + r = "cuntboy"; + } else if (slave.ovaries === 0 && slave.genes === "XY") { + r = "tranny"; + } else if (slave.weight > 10 && slave.boobs > 800 && slave.counter.birthsTotal > 0 && slave.physicalAge > 59) { + r = "GMILF"; + } else if (slave.weight > 10 && slave.boobs > 800 && slave.counter.birthsTotal > 0 && slave.physicalAge > 35) { + r = "MILF"; + } else if (slave.lips > 70 && slave.boobs > 2000 && slave.butt > 3) { + r = "bimbo"; + } else if (slave.hips > 1 && slave.boobs > 2000 && slave.butt > 3 && slave.waist < 50) { + r = "hourglass"; + } else if (slave.muscles > 95 && slave.height >= 185) { + r = "amazon"; + } else if (slave.muscles > 30) { + r = "musclegirl"; + } else { + r = "slavegirl"; + } + } else if (slave.dick === 0 && slave.balls === 0 && slave.vagina === -1) { + r = "neuter"; + } else if (slave.dick === 0 && slave.vagina === -1) { + r = "ballslave"; + } else { + r = "slave"; + } + + if (slave.visualAge < 13) { + if (slave.actualAge < 3) { + if (slave.actualAge < 1) { + r = "baby " + r; + } else { + r = "toddler " + r; + } + } else { + if (slave.genes === "XY" && slave.vagina === -1) { + r = "shota " + r; + } else { + r = "loli " + r; + } + } + } + + if (slave.geneticQuirks.albinism === 2) { + r = `albino ${r}`; + } + + if (slave.dick > 9 && slave.balls > 9 && slave.boobs > 12000) { + r = `hyper ${r}`; + } + + if (slave.boobs > 4000 && slave.lactation > 0) { + if (slave.physicalAge < 13) { + r = `${r} calf`; + } else { + r = `${r} cow`; + } + } else if (slave.lactation > 0) { + r = `milky ${r}`; + } + + if (slave.boobs > 20000) { + r = `supermassive titted ${r}`; + } else if (slave.boobs > 10000) { + r = `giant titted ${r}`; + } else if (slave.boobs > 4000) { + r = `huge titted ${r}`; + } else if (slave.boobs > 1000) { + r = `busty ${r}`; + } + + if (slave.dick > 5 && slave.balls > 5) { + r = `womb filling ${r}`; + } else if (slave.dick > 5) { + r = `well hung ${r}`; + } + + if (slave.butt >= 12) { + r = `colossal assed ${r}`; + } else if (slave.butt >= 10) { + r = `massive assed ${r}`; + } else if (slave.butt >= 8) { + r = `fat assed ${r}`; + } else if (slave.butt >= 6) { + r = `bottom heavy ${r}`; + } else if (slave.butt >= 4) { + r = `big bottomed ${r}`; + } + + if (slave.weight > 10 && slave.weight < 100 && slave.boobs > 5000 && slave.butt > 5 && slave.hips >= 2 && slave.bellyPreg >= 30000 && slave.counter.births >= 10) { + r = `${r} fertility goddess`; + } else if (slave.counter.births >= 6) { + r = `${r} broodmother`; + } else if (slave.counter.births >= 3) { + r = `${r} breeder`; + } + + if (slave.indenture > -1) { + r = `indentured ${r}`; + } + + if (slave.preg > slave.pregData.normalBirth / 4 && slave.pregKnown === 1) { + r = `pregnant ${r}`; + } else if (slave.bellyFluid >= 5000) { + r = `bloated ${r}`; + } else if (slave.belly >= 5000) { + r = `gravid ${r}`; + } + + if (slave.fuckdoll > 0) { + r = `${r} fuckdoll`; + } + } else { + r = "slave"; /* I don't think there is an 'else'? */ + if ((slave.dick === 0) && (slave.vagina === -1)) { + /* NULLS */ + r = "null"; + if ((slave.lactation > 0) && (slave.boobs > 2000)) { + r = `${r} cow`; + } else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) { + r = `${r} bimbo `; + } else if (slave.boobs > 6000) { + r = `${r} boob`; + } else if (slave.butt > 6) { + r = `${r} ass`; + } else if ((slave.muscles > 30) && (slave.height < 185)) { + r = `${r} muscle`; + } + if (slave.visualAge > 55) { + r = `${r}GILF`; + } else if (slave.visualAge > 35) { + r = `${r}MILF`; + } else if (slave.visualAge >= 25) { + r = `${r}slave`; + } else { + r = `${r}girl`; + } + } + + if ((slave.dick === 0) && (slave.vagina !== -1)) { + /* FEMALES */ + if (slave.visualAge > 55) { + r = "GILF"; + } else if (slave.visualAge > 35) { + r = "MILF"; + } else if (slave.visualAge >= 25) { + r = "slave"; + } else { + r = "slavegirl"; + } + if ((slave.muscles > 30) && (slave.height < 185)) { + r = `muscle ${r}`; + } else if ((slave.lactation > 0) && (slave.boobs > 2000)) { + r = `${r} cow`; + } else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) { + r = `${r} bimbo`; + } else if (slave.boobs > 6000) { + r = `boob${r}`; + } else if (slave.butt > 6) { + r = `ass${r}`; + } + } + + if ((slave.dick !== 0) && (slave.vagina !== -1)) { + if (slave.balls > 0) { + /* FUTANARI: cock & balls & vagina */ + r = "futanari "; + } else { + /* FUTANARI: cock & vagina */ + r = "futa "; + } + if ((slave.lactation > 0) && (slave.boobs > 2000)) { + r = `${r}cow`; + } else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) { + r = `${r}bimbo `; + } else if (slave.boobs > 6000) { + r = `${r}boob`; + } else if (slave.butt > 6) { + r = `${r}ass`; + } else if ((slave.muscles > 30) && (slave.height < 185)) { + r = `${r}muscle`; + } + if (slave.visualAge > 55) { + r = `${r}GILF`; + } else if (slave.visualAge > 35) { + r = `${r}MILF`; + } else if (slave.visualAge >= 25) { + r = `${r}slave`; + } else { + r = `${r}girl`; + } + if (slave.dick > 5 && slave.balls > 5 && slave.boobs > 5000) { + r = `hyper ${r}`; + } + } + + if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls > 0) && (slave.boobs > 300) && (slave.butt > 2)) { + /* SHEMALES: cock & balls, T&A above minimum */ + if (slave.visualAge > 55) { + r = "sheGILF"; + } else if (slave.visualAge > 35) { + r = "sheMILF"; + } else if (slave.visualAge >= 25) { + r = "shemale"; + } else { + r = "tgirl"; + } + if ((slave.muscles > 30) && (slave.height < 185)) { + r = `muscle${r}`; + } else if ((slave.lactation > 0) && (slave.boobs > 2000)) { + r = `${r} cow`; + } else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) { + r = `${r} bimbo`; + } else if (slave.boobs > 6000) { + r = `topheavy ${r}`; + } else if (slave.butt > 6) { + r = `bottomheavy ${r}`; + } + } + + if ((slave.boobs < 300) || (slave.butt < 2)) { + if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls > 0)) { + if ((slave.shoulders < 1) || (slave.muscles <= 30)) { + if ((slave.faceShape === "masculine") || (slave.faceShape === "androgynous")) { + /* SISSIES: feminine shoulders or muscles, masculine faces */ + if (slave.visualAge > 55) { + r = "sissyGILF"; + } else if (slave.visualAge > 35) { + r = "sissyMILF"; + } else { + r = "sissy"; + } + } else { + /* TRAPS: feminine shoulders or muscles, feminine faces */ + if (slave.visualAge > 55) { + r = "trapGILF"; + } else if (slave.visualAge > 35) { + r = "trapMILF"; + } else if (slave.visualAge >= 25) { + r = "trap"; + } else { + r = "trapgirl"; + } + } + if (slave.lactation > 0) { + r = `${r} cow`; + } else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) { + r = `${r} bimbo`; + } + } + } + } + + if ((slave.boobs < 300) || (slave.butt < 2)) { + if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls > 0)) { + if ((slave.shoulders > 1) || (slave.muscles >= 30)) { + /* BITCHES: masculine shoulders or muscles */ + r = "bitch"; + if ((slave.muscles > 30) && (slave.height < 185)) { + r = `muscle${r}`; + } else if (slave.lactation > 0) { + r = `${r}cow`; + } else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) { + r = `bimbo ${r}`; + } + if (slave.visualAge > 55) { + r = `aged ${r}`; + } else if (slave.visualAge > 35) { + r = `mature ${r}`; + } else if (slave.visualAge < 25) { + r = `young ${r}`; + } + } + } + } + + if ((slave.dick !== 0) && (slave.vagina === -1) && (slave.balls === 0)) { + r = "dick"; + if (slave.visualAge > 55) { + r = `${r}GILF`; + } else if (slave.visualAge > 35) { + r = `${r}MILF`; + } else if (slave.visualAge >= 25) { + r = `${r}slave`; + } else { + r = `${r}girl`; + } + if ((slave.muscles > 30) && (slave.height < 185)) { + r = `muscle${r}`; + } else if ((slave.lactation > 0) && (slave.boobs > 2000)) { + r = `${r} cow`; + } else if ((slave.boobsImplant > 0) && (slave.buttImplant > 0)) { + r = `${r} bimbo`; + } else if (slave.boobs > 6000) { + r = `boob ${r}`; + } else if (slave.butt > 6) { + r = `ass ${r}`; + } + } + + if ((slave.muscles > 30) && (slave.height > 185)) { + r = `amazon ${r}`; + } else if ((slave.muscles < 30) && (slave.height > 185)) { + r = `statuesque ${r}`; + } else if ((slave.boobs < 800) && (slave.height < 150)) { + r = `petite ${r}`; + } else if ((slave.boobs > 800) && (slave.height < 150)) { + r = `shortstack ${r}`; + } + + if (slave.counter.births >= 5) { + r = `${r} broodmother`; + } else if (slave.counter.births >= 2) { + r = `${r} breeder`; + } + + if (slave.geneticQuirks.albinism === 2) { + r = `albino ${r}`; + } + + if (slave.indenture > -1) { + r = `indentured ${r}`; + } + + if (slave.preg > slave.pregData.normalBirth / 4 && slave.pregKnown === 1) { + r = `pregnant ${r}`; + } else if (slave.bellyFluid >= 5000) { + r = `bloated ${r}`; + } else if (slave.belly >= 5000) { + r = `gravid ${r}`; + } + + if (slave.fuckdoll > 0) { + r = `${r} fuckdoll`; + } } - return hair; + return r; }; /** - * @param {string} skinTone - * @returns {number} + * @param {App.Entity.SlaveState} slave */ -globalThis.skinToneLevel = function(skinTone) { - if (!App.Medicine.Modification.naturalSkins.includes(skinTone)) { - return undefined; +globalThis.DegradingName = function(slave) { + const leadershipPosition = [ + Job.ATTENDANT, + Job.MATRON, + Job.STEWARD, + Job.MILKMAID, + Job.FARMER, + Job.DJ, + Job.CONCUBINE, + Job.MADAM, + Job.TEACHER, + Job.WARDEN, + Job.NURSE, + Job.HEADGIRL, + Job.BODYGUARD, + Job.RECRUITER + ]; + const names = []; + const suffixes = []; + + if (slave.fuckdoll > 0) { + slave.slaveName = `Fuckdoll No. ${slave.ID}`; + slave.slaveSurname = 0; + } else if (slave.assignment === Job.DAIRY && V.dairyRestraintsSetting >= 2) { + slave.slaveName = `Bioreactor No. ${slave.ID}`; + slave.slaveSurname = 0; + } else { + if (V.seeRace === 1) { + switch (slave.race) { + case "white": + names.push("Pale", "White"); + break; + case "asian": + names.push("Asian", "Yellow"); + break; + case "latina": + names.push("Brown", "Latina"); + break; + case "black": + names.push("Black", "Dark"); + break; + case "pacific islander": + names.push("Islander", "Pacific", "Sea"); + break; + case "malay": + names.push("Cinnamon", "Pinoy", "Spice"); + break; + case "southern european": + names.push("Mediterranean", "Olive"); + break; + case "amerindian": + names.push("Indian", "Reservation"); + break; + case "semitic": + names.push("Semite", "Semitic"); + break; + case "middle eastern": + names.push("Arab", "Sand"); + break; + case "indo-aryan": + names.push("Brown", "Indian"); + break; + case "mixed race": + names.push("Mixed", "Mulatto", "Mutt"); + break; + } + } + names.push(slave.hColor); + if (!hasAnyEyes(slave)) { + names.push("Blind", "Eyeless", "Sightless"); + } + if (slave.hears === -2) { + names.push("Deaf", "Earless", "Unhearing"); + } + if (slave.boobs >= 2000) { + suffixes.push("Boob", "Boobs", "Titty"); + } + if (slave.boobs < 500 && slave.butt < 3) { + names.push("Girly", "Slim", "Thin"); + } + if (slave.boobs < 300) { + names.push("Flat"); + } + if (slave.anus > 2 || slave.vagina > 2) { + names.push("Gaping", "Hallway", "Slit", "Wideopen"); + } + if (slave.weight > 160) { + names.push("Blimp", "Cow", "Fat", "Fatass", "Whale"); + } else if (slave.weight > 30) { + names.push("Chubby", "Fat", "Whale"); + } else if (slave.weight <= -30) { + names.push("Bony", "Rail", "Skinny"); + } + if (slave.muscles > 30) { + names.push("Huge", "Muscles", "Ripped", "Strong"); + } + if (slave.fetishKnown === 1) { + if (slave.fetish === "buttslut") { + names.push("Anal", "Sodomy"); + } + if (slave.fetish === "cumslut") { + names.push("Cum", "Dicksuck", "Sucker"); + } + if (slave.fetish === "humiliation") { + names.push("Rape"); + } + if (slave.fetish === "masochist") { + names.push("Pain", "Rape", "Struggle"); + } + if (slave.fetish === "pregnancy") { + names.push("Fertile"); + } + } + if (slave.boobs * slave.lactation > 1000) { + names.push("Creamy", "Milky"); + suffixes.push("Cow"); + } + if (slave.skill.oral <= 30 && slave.skill.anal <= 30) { + names.push("Cheap", "Fail", "Gutter"); + } + if (slave.nipples === "fuckable") { + names.push("Nipplefuck", "Nipplecunt"); + } else if (slave.nipples !== "tiny" && slave.nipples !== "cute") { + names.push("Pointy", "Titclit"); + suffixes.push("Nipples"); + } + if (slave.visualAge > 35) { + names.push("Mature"); + suffixes.push("Cougar", "MILF"); + } else if (slave.visualAge < 25) { + names.push("Girly", "Thin", "Young"); + } + if (isAmputee(slave)) { + names.push("Stumpy"); + suffixes.push("Stumpy"); + } + if (slave.boobsImplant > 1000 || slave.buttImplant > 3) { + names.push("Fake", "Plastic", "Silicone"); + } + if (slave.dick > 5 && slave.balls > 5) { + names.push("Potent"); + suffixes.push("Cannon", "Daddy"); + } + if (slave.preg > slave.pregData.normalBirth / 1.33) { + if (slave.broodmother === 2) { + names.push("Bursting", "Seeded"); + suffixes.push("Factory", "Nursery"); + } else if (slave.broodmother === 1) { + names.push("Bloated", "Stuffed"); + suffixes.push("Breeder", "Factory"); + } + } + if (slave.bellyPreg >= 450000) { + names.push("Bulging", "Squirming"); + } + if (slave.bellyPreg >= 5000) { + names.push("Preg"); + suffixes.push("Belly", "Mommy"); + } + if (slave.belly > 150000) { + suffixes.push("Balloon"); + } + if (slave.belly > 1500) { + suffixes.push("Belly"); + } + if (slave.dick > 0) { + if (slave.dick > 4) { + names.push("Dangle", "Hung"); + suffixes.push("Cock", "Dick"); + } + if (slave.balls === 0) { + names.push("Cut", "Gelded", "Soft"); + } else { + names.push("Erect", "Hard", "Stiff"); + } + } + if (slave.dick === 1) { + names.push("Micro", "Tiny"); + suffixes.push("Bitch"); + } + if (slave.height >= 185) { + names.push("Tall", "Top"); + suffixes.push("Tower"); + } else if (slave.height < 150) { + names.push("Stumpy", "Tiny"); + suffixes.push("Shortstack", "Stumpy"); + } + if (slave.skill.whoring > 95) { + names.push("Money", "Street"); + suffixes.push("Whore"); + } + if (slave.skill.entertainment > 95) { + names.push("Easy", "Club"); + suffixes.push("Slut"); + } + if (slave.skill.oral > 95) { + names.push("Suck"); + suffixes.push("Throat"); + } + if (slave.skill.vaginal > 95) { + suffixes.push("Channel", "Kegel", "Pussy"); + } + if (slave.skill.anal > 95) { + suffixes.push("Asspussy", "Sphincter"); + } + if (slave.intelligence + slave.intelligenceImplant > 50) { + names.push("Bright", "Clever", "Smart"); + if (slave.intelligenceImplant >= 15) { + names.push("College", "Graduate", "Nerdy"); + } + } else if (slave.intelligence + slave.intelligenceImplant < -50) { + names.push("Cretin", "Dumb", "Retarded", "Stupid"); + } + if (slave.vagina === 1 && slave.skill.vaginal <= 10) { + names.push("Fresh", "New", "Tight"); + } + if (slave.devotion < -75) { + names.push("Angry", "Biter", "Caged"); + } else if (slave.devotion < -50) { + names.push("Cell", "Cuffs"); + } else if (slave.devotion < -20) { + names.push("Bag", "Box"); + } else if (slave.devotion <= 20) { + names.push("Sad", "Whiner"); + } else if (slave.devotion > 50) { + names.push("Prize"); + if (slave.visualAge > 35) { + names.push("Queen"); + } else if (slave.visualAge < 25) { + names.push("Princess"); + } + } + if (slave.trust < -50) { + names.push("Screaming"); + suffixes.push("Sobber"); + } else if (slave.trust < -20) { + names.push("Crying"); + suffixes.push("Meat", "Tears", "Thing", "Weeper"); + } else if (slave.trust < 20) { + names.push("Begging"); + } + + if (slave.dick === 0) { + if (slave.vagina === -1) { + suffixes.push("Null"); + } else { + if (slave.visualAge < 25) { + suffixes.push("Girl"); + } + } + } else { + if (slave.vagina !== -1) { + suffixes.push("Futa"); + } else { + if (slave.balls > 0) { + if (slave.boobs > 300 && slave.butt > 2) { + /* SHEMALES: cock & balls, T&A above minimum */ + suffixes.push("Shemale"); + } else { + if (slave.shoulders < 1 && slave.muscles <= 30) { + if (slave.faceShape === "masculine" || slave.faceShape === "androgynous") { + /* SISSIES: feminine shoulders or muscles, masculine faces */ + suffixes.push("Sissy"); + } else { + /* TRAPS: feminine shoulders or muscles, feminine faces */ + suffixes.push("Trap"); + } + } else { + /* BITCHES: masculine shoulders or muscles */ + suffixes.push("Bitch"); + } + } + } else { + if (slave.visualAge > 35) { + suffixes.push("DickMILF"); + } else if (slave.visualAge >= 25) { + suffixes.push("Dickslave"); + } else { + suffixes.push("Dickgirl"); + } + } + } + } + if (slave.anus > 0) { + suffixes.push("Anus", "Asshole", "Backdoor", "Butt", "Butthole"); + } + if (slave.anus === 1) { + suffixes.push("Tightass", "Tightbutt"); + } + if (slave.vagina > 0) { + suffixes.push("Cunt", "Pussy", "Vagina"); + } + if (slave.boobs < 500 && slave.butt < 3 && slave.dick > 0) { + suffixes.push("Bitch", "Bottom", "Sissy", "Trap"); + } + if (slave.energy > 95) { + suffixes.push("Fuck", "Fuckaddict", "Nympho", "Sexaddict"); + } + if (slave.fetishKnown === 1) { + if (slave.fetish === "humiliation") { + suffixes.push("Rapebait", "Showgirl"); + } + if (slave.fetish === "submissive") { + suffixes.push("Bottom", "Fuckee", "Rapebait"); + } + if (slave.fetish === "dom") { + suffixes.push("Dom", "Fucker", "Top"); + } + if (slave.fetish === "pregnancy") { + suffixes.push("Breeder", "Mommy"); + } + if (slave.fetish === "boobs") { + suffixes.push("Boob", "Boobie", "Tit", "Titty"); + } + } + if (slave.counter.births >= 2) { + suffixes.push("Breeder"); + if (slave.counter.births >= 5) { + suffixes.push("Broodmother"); + } + } + if (slave.areolae > 2) { + suffixes.push("Areolas", "Headlights"); + } + if (slave.lips > 40) { + suffixes.push("Lips", "Pillows"); + } + if (slave.labia > 1) { + suffixes.push("Curtains", "Flower", "Lips"); + } + if (slave.breedingMark === 1 && V.propOutcome === 1 && V.arcologies[0].FSRestart !== "unset") { + suffixes.push("Breeder", "Oven", "Womb"); + } + if (slave.butt > 5) { + suffixes.push("Ass", "Bottom", "Butt"); + } + if (slave.vagina === 0) { + suffixes.push("Virgin"); + } + + slave.slaveName = jsEither(names); } - const skinToMelanin = { - "pure black": 25, - "ebony": 24, - "black": 23, - "dark brown": 22, - "brown": 21, - "light brown": 20, - "dark beige": 19, - "beige": 18, - "light beige": 17, - "dark": 16, - "dark olive": 15, - "bronze": 14, - "olive": 13, - "tan": 12, - "light olive": 11, - "light": 10, - "fair": 9, - "very fair": 8, - "extremely fair": 7, - "pale": 6, - "very pale": 5, - "extremely pale": 4, - "white": 3, - "ivory": 2, - "pure white": 1 - }; - return skinToMelanin[skinTone]; + if (leadershipPosition.includes(slave.assignment)) { + switch (slave.assignment) { + case Job.ATTENDANT: + slave.slaveName = jsEither(["Bath", "Spa"]); + break; + case Job.MATRON: + slave.slaveName = jsEither(["Matron", "Nursery"]); + break; + case Job.STEWARD: + slave.slaveName = jsEither(["Maid", "Servant"]); + break; + case Job.MILKMAID: + if (V.cumSlaves > 3) { + slave.slaveName = jsEither(["Fucker", "Milker"]); + } else { + slave.slaveName = jsEither(["Dairy", "Farm"]); + } + break; + case Job.FARMER: + slave.slaveName = jsEither(["Farmer", "Farmhand"]); + break; + case Job.DJ: + slave.slaveName = jsEither(["Bass", "Booth"]); + break; + case Job.CONCUBINE: + slave.slaveName = jsEither(["Bed", "Master"]); + break; + case Job.MADAM: + slave.slaveName = jsEither(["Madam", "Pimp"]); + break; + case Job.TEACHER: + slave.slaveName = jsEither(["Classroom", "Teacher"]); + break; + case Job.WARDEN: + slave.slaveName = jsEither(["Jail", "Prison"]); + break; + case Job.NURSE: + slave.slaveName = jsEither(["Clinic", "Nurse"]); + break; + case Job.HEADGIRL: + slave.slaveName = jsEither(["Chief", "Head"]); + break; + case Job.BODYGUARD: + slave.slaveName = jsEither(["Battle", "Guard"]); + break; + case Job.RECRUITER: + slave.slaveName = jsEither(["Cam", "Recruiter"]); + break; + } + } + const surname = jsEither(suffixes); + if (typeof surname === "string" && surname.toLowerCase() === slave.slaveName.toLowerCase()) { + DegradingName(slave); + } + slave.slaveName = capFirstChar(slave.slaveName); + slave.slaveSurname = surname; }; /** - * Increase or decrease skinTone - * @param {string} skin - * @param {number} value - * @returns {string} + * @param {App.Entity.SlaveState} slave */ -globalThis.changeSkinTone = function(skin, value) { - if (!App.Medicine.Modification.naturalSkins.includes(skin)) { - return skin; - } - const skinToMelanin = { - "pure black": 25, - "ebony": 24, - "black": 23, - "dark brown": 22, - "brown": 21, - "light brown": 20, - "dark beige": 19, - "beige": 18, - "light beige": 17, - "dark": 16, - "dark olive": 15, - "bronze": 14, - "olive": 13, - "tan": 12, - "light olive": 11, - "light": 10, - "fair": 9, - "very fair": 8, - "extremely fair": 7, - "pale": 6, - "very pale": 5, - "extremely pale": 4, - "white": 3, - "ivory": 2, - "pure white": 1 - }; - let newSkin = (skinToMelanin[skin] + value); - if (newSkin > 25) { - newSkin = 25; - } else if (newSkin < 1) { - newSkin = 1; - } - let prop; - for (prop in skinToMelanin) { - if (!skinToMelanin.hasOwnProperty(prop)) { - continue; +globalThis.PaternalistName = function(slave) { + if (slave.slaveName.search("Miss") === -1) { + if (slave.slaveName.search("Ms.") === -1) { + if (slave.slaveName.search("Mrs.") === -1) { + if (slave.relationship > 4) { + slave.slaveName = ("Mrs. " + slave.slaveName); + } else if (slave.actualAge > 24) { + slave.slaveName = ("Ms. " + slave.slaveName); + } else { + slave.slaveName = ("Miss " + slave.slaveName); + } + } } - if (newSkin >= skinToMelanin[prop]) { - return prop; + } +}; + +/** + * + * @param {App.Entity.SlaveState} parent + * @param {App.Entity.SlaveState} child + */ +globalThis.parentNames = function(parent, child) { + const slaves = V.slaves; + + let currentSlaveNames = slaves.map(s => s.slaveName); + let continentNationality; + const useMaleName = (child.genes === "XY" && V.allowMaleSlaveNames === true); + + child.slaveName = generateName(parent.nationality, child.race, useMaleName, sn => !currentSlaveNames.includes(sn)); + + if (!child.slaveName) { + for (let i = 0; i < 10; i++) { + continentNationality = hashChoice(V.nationalities); + child.slaveName = generateName(continentNationality, child.race, useMaleName, sn => !currentSlaveNames.includes(sn)); // jshint ignore: line } } - return prop; + if (!child.slaveName) { + child.slaveName = generateName(parent.nationality, child.race, useMaleName); + } }; /** - * @param {string} color - * @returns {number} + * @param {App.Entity.SlaveState} slave + * @param {number} amount + * @returns {string} */ -globalThis.nippleColorLevel = function(color) { - if (!App.Medicine.Modification.naturalNippleColors.includes(color)) { - return undefined; +globalThis.faceIncrease = function(slave, amount) { + const pronouns = getPronouns(slave); + const his = pronouns.possessive; + const His = capFirstChar(his); + let r = ""; + if (slave.face <= -95) { + r += `<span class="green">${His} face is no longer horrifying,</span> and is now merely ugly.`; + } else if (slave.face <= -40 && slave.face + amount > -40) { + r += `<span class="green">${His} face is no longer ugly,</span> and is now merely unattractive.`; + } else if (slave.face <= -10 && slave.face + amount > -10) { + r += `<span class="green">${His} face is no longer unattractive,</span> and is now somewhat tolerable.`; + } else if (slave.face <= 10 && slave.face + amount > 10) { + r += `<span class="green">${His} face is now decently attractive,</span> rather than merely tolerable.`; + } else if (slave.face <= 40 && slave.face + amount > 40) { + r += `<span class="green">${His} face is now quite beautiful,</span> rather than merely pretty.`; + } else if (slave.face <= 95 && slave.face + amount > 95) { + r += `<span class="green">${His} face is now perfect.</span> It's difficult to imagine how it could be any more beautiful.`; + } + slave.face = Math.clamp(slave.face + amount, -100, 100); + if (slave.face > 95) { + slave.face = 100; } - const nippleColor = { - "ebony": 8, - "black": 7, - "dark brown": 6, - "brown": 5, - "light brown": 4, - "pink": 3, - "pale pink": 2, - "ivory": 1, - }; - return nippleColor[color]; + return r; }; /** - * Sets temporary variables named by the scheme, described below, to pronouns for the given slave * @param {App.Entity.SlaveState} slave - * @param {any} [suffix] pronounsSuffix. Anything that can be converted to string. - * @param {string[]} [pronouns] requested pronouns. Defaults to all pronoun forms. - * - * The variables naming scheme is the pronoun name (he, his, etc.) and optional suffix. If the suffix is empty, the variables - * will be set as story variables, otherwise as temporary variables. - * This way for a call App.Utils.setLocalPronouns(slave) there will be story variables "$he", "$his", for - * App.Utils.setLocalPronouns(slave, 1): _he1, _his1 and so on. + * @returns {number} */ -App.Utils.setLocalPronouns = function(slave, suffix, pronouns) { - const ps = getPronouns(slave); - /** @type {string} */ - const pSuffix = suffix !== undefined ? suffix.toString() : ''; - pronouns = pronouns || [ // Object.getOwnPropertyNames(ps) ? - 'he', 'him', 'his', 'himself', 'boy', - 'He', 'Him', 'His', 'Himself', 'Boy', - 'man', 'men', 'shota', 'son', 'brother', 'husband', 'husbands', 'father', 'fathers', - 'Man', 'Men', 'Shota', 'Son', 'Brother', 'Husband', 'Husbands', 'Father', 'Fathers', - 'she', 'her', 'hers', 'herself', 'girl', - 'She', 'Her', 'Hers', 'Herself', 'Girl', - 'woman', 'women', 'loli', 'daughter', 'sister', 'wife', 'wives', 'mother', 'mothers', - 'Woman', 'Women', 'Loli', 'Daughter', 'Sister', 'Wife', 'Wives', 'Mother', 'Mothers' - ]; // Pronouns always refer to the slave in question, never any relation of theirs. It is "mother" as in "she is a mother of many" not "you are her mother". Plural pronouns would refer to "wives like her," not "her wives." +globalThis.deadliness = function(slave) { + let deadliness = 2; - const scope = pSuffix.length === 0 ? V : State.temporary; - pronouns.forEach(p => { - scope[p + pSuffix] = ps[p]; - }); + if (slave.skill.combat > 0) { + deadliness += 2; + } + + if (App.Data.Careers.Leader.bodyguard.includes(slave.career)) { + deadliness += 1; + } else if (slave.skill.bodyguard >= V.masteredXP) { + deadliness += 1; + } + + if (V.AgePenalty !== 0) { + if (slave.physicalAge >= 100) { + deadliness -= 10; + } else if (slave.physicalAge >= 85) { + deadliness -= 3; + } else if (slave.physicalAge >= 70) { + deadliness -= 1; + } + } + + if (slave.muscles > 30 && slave.muscles <= 95) { + deadliness += 1; + } else if (slave.muscles > 95 && slave.height >= 185) { + deadliness += 2; + } else if (slave.muscles > 95) { + deadliness -= 1; + } else if (slave.muscles < -95) { + deadliness -= 20; + } else if (slave.muscles < -30) { + deadliness -= 7; + } else if (slave.muscles < -5) { + deadliness -= 3; + } + + if (slave.height >= 170) { + deadliness += 1; + } + + if (slave.health.condition > 50) { + deadliness += 1; + } else if (slave.health.condition < -50) { + deadliness -= 1; + } + + if (slave.health.illness > 3) { + deadliness -= 3; + } else if (slave.health.illness > 1) { + deadliness -= 2; + } else if (slave.health.illness > 0) { + deadliness -= 1; + } + + if (slave.boobs > 4000) { + deadliness -= 2; + } else if (slave.boobs > 2000) { + deadliness -= 1; + } + + if (slave.butt > 6) { + deadliness -= 1; + } + + if (slave.hips > 2) { + deadliness -= 1; + } + + if (slave.weight > 190) { + deadliness -= 20; + } else if (slave.weight > 160) { + deadliness -= 10; + } else if (slave.weight > 130) { + deadliness -= 3; + } else if (slave.weight > 30 || slave.weight < -10) { + deadliness -= 1; + } + + if (slave.bellyFluid >= 10000) { + deadliness -= 3; + } else if (slave.bellyFluid >= 5000) { + deadliness -= 2; + } else if (slave.bellyFluid >= 2000) { + deadliness -= 1; + } + + if (slave.pregKnown === 1 || slave.bellyPreg >= 1500 || slave.bellyImplant >= 1500) { + if (slave.belly >= 750000) { + deadliness -= 50; + } else if (slave.belly >= 600000) { + deadliness -= 25; + } else if (slave.belly >= 450000) { + deadliness -= 15; + } else if (slave.belly >= 300000) { + deadliness -= 10; + } else if (slave.belly >= 150000) { + deadliness -= 8; + } else if (slave.belly >= 100000) { + deadliness -= 7; + } else if (slave.belly >= 10000) { + deadliness -= 3; + } else if (slave.belly >= 5000) { + deadliness -= 2; + } else { + deadliness -= 1; + } + } + + if (isInLabor(slave)) { + deadliness -= 15; + } else if (slave.preg >= slave.pregData.normalBirth && slave.pregControl !== "labor suppressors") { + deadliness -= 5; + } + + if (slave.balls >= 15) { + deadliness -= 1; + } + + if (slave.dick >= 10) { + deadliness -= 1; + } + + deadliness -= getLimbCount(slave, 0) * 5; + deadliness -= getLimbCount(slave, 2) * 0.25; + deadliness -= getLimbCount(slave, 3) * 0.25; + deadliness -= getLimbCount(slave, 4) * 0.25; + deadliness += getLimbCount(slave, 5) * 1.25; + deadliness += getLimbCount(slave, 6) * 2.5; + if (hasBothLegs(slave) && !canStand(slave)) { + deadliness -= 20; + } + + if (!canSee(slave)) { + deadliness -= 8; + } else if (!canSeePerfectly(slave)) { + deadliness -= 1; + } + + if (!canHear(slave)) { + deadliness -= 4; + } else if ((slave.hears === -1 && slave.earwear !== "hearing aids") || (slave.hears === 0 && slave.earwear === "muffling ear plugs")) { + deadliness -= 1; + } + + if (slave.tail === "combat") { + deadliness += 2; + } + + if (slave.health.tired > 90) { + deadliness -= 10; + } else if (slave.health.tired > 60) { + deadliness -= 3; + } else if (slave.health.tired > 30) { + deadliness -= 1; + } + + return Math.max(deadliness, 1); }; -/** - * Fix nationalities as adjectives - * @param {string} nation - * @returns {string} +/** Is the slave ready to retire? + * @param {App.Entity.SlaveState} slave + * @returns {boolean} */ -globalThis.aNational = function(nation) { - let country; - if (nation === "a Cook Islander") { - country = "Cook Islander"; - } else if (nation === "a Liechtensteiner") { - country = "Liechtensteiner"; - } else if (nation === "a New Zealander") { - country = "New Zealander"; - } else if (nation === "a Solomon Islander") { - country = "Solomon Islander"; +globalThis.retirementReady = function(slave) { + // indentured slaves don't retire, they expire + if (slave.indenture >= 0) { + return false; + } + + // retirement by age + if (V.policies.retirement.physicalAgePolicy === 0 && slave.actualAge >= V.retirementAge) { + return true; + } else if (V.policies.retirement.physicalAgePolicy === 1 && slave.physicalAge >= V.retirementAge) { + return true; + } + + // retirement by milestone + if (V.policies.retirement.sex > 0 && (slave.counter.oral + slave.counter.anal + slave.counter.vaginal + slave.counter.penetrative + slave.counter.mammary) > V.policies.retirement.sex) { + return true; + } + if (V.policies.retirement.milk > 0 && slave.counter.milk > V.policies.retirement.milk) { + return true; + } + if (V.policies.retirement.cum > 0 && slave.counter.cum > V.policies.retirement.cum) { + return true; + } + if (V.policies.retirement.births > 0 && slave.counter.births > V.policies.retirement.births) { + return true; + } + if (V.policies.retirement.kills > 0 && slave.counter.pitKills > V.policies.retirement.kills) { + return true; + } + + // no retirement for you + return false; +}; + +/** marks some weeks of time passage for a slave, counting birthdays and invoking aging if game settings require it + * @param {App.Entity.SlaveState} slave + * @param {number} [weeks=1] + */ +globalThis.ageSlaveWeeks = function(slave, weeks = 1) { + if (V.seeAge !== 0) { // birthdays enabled + for (let i = 0; i < weeks; ++i) { + slave.birthWeek++; + if (slave.birthWeek >= 52) { + slave.birthWeek = 0; + if (V.seeAge === 1) { // actual aging enabled + ageSlave(slave); + } + } + } + } +}; + +/** advances the age of a slave by one year, incurring all aging side effects + * @param {App.Entity.SlaveState} slave + * @param {boolean} [forceDevelopment=false] + */ +globalThis.ageSlave = function(slave, forceDevelopment = false) { + slave.physicalAge++; + slave.actualAge++; + if (slave.geneMods.NCS === 1 || (slave.geneticQuirks.neoteny >= 2 && slave.geneticQuirks.progeria !== 2)) { + /* Induced NCS completely takes over visual aging. Additionally, because of the neoteny aspects of NCS, ovaries don't age quite as fast. */ + /* Unsurprisingly, actual neoteny has the same effect as long as progeria isn't in play. */ + slave.ovaryAge += either(0.5, 0.6, 0.7, 0.7, 0.8, 0.9, 1.0); } else { - country = nation; + slave.visualAge++; + slave.ovaryAge += either(0.8, 0.9, 0.9, 1.0, 1.0, 1.0, 1.1); + } + if (slave.broodmother === 1) { + slave.ovaryAge += 0.2; + } + if (slave.physicalAge <= 18 && (forceDevelopment || V.loliGrow > 0)) { + physicalDevelopment(slave); } - return country; }; /** - * Fix nationalities as plurals - * @param {string} nation + * @param {App.Entity.SlaveState} slave + * @param {number} [induce] * @returns {string} */ -globalThis.moreNational = function(nation) { - let country; - if (nation === "a Cook Islander") { - country = "Cook Islander"; - } else if (nation === "a Liechtensteiner") { - country = "Liechtensteiner"; - } else if (nation === "Mosotho") { - country = "Basotho"; - } else if (nation === "Motswana") { - country = "Batswana"; - } else if (nation === "a New Zealander") { - country = "New Zealander"; - } else if (nation === "a Solomon Islander") { - country = "Solomon Islander"; - } else { - country = nation; +globalThis.induceLactation = function(slave, induce = 0) { + const {His} = getPronouns(slave); + let r = ""; + let lactationStartChance = jsRandom(10, 100); + slave.induceLactation += induce; + if (slave.boobs < 300) { + lactationStartChance *= 1.5; + } else if (slave.boobs < 400 || slave.boobs >= 5000) { + lactationStartChance *= 1.2; } - return country; + if (slave.pubertyXX === 0) { + lactationStartChance *= 1.5; + } + if (slave.preg > (slave.pregData.normalBirth / 1.33)) { + lactationStartChance *= .5; + } + if (slave.health.condition < -20) { + lactationStartChance *= 2; + } + if (slave.weight <= -30) { + lactationStartChance *= 1.5; + } + if (slave.boobsImplant > 0) { + lactationStartChance *= (1 + (slave.boobsImplant / slave.boobs)); + } + if (slave.lactationAdaptation > 0) { + lactationStartChance = (lactationStartChance / (slave.lactationAdaptation / 10)); + } + if (slave.geneticQuirks.galactorrhea === 2) { + lactationStartChance *= .5; + } + lactationStartChance = Math.floor(lactationStartChance); + if (slave.induceLactation >= lactationStartChance) { + r += `${His} breasts have been stimulated often enough to <span class="lime">induce lactation.</span>`; + slave.induceLactation = 0; + slave.lactationDuration = 2; + slave.lactation = 1; + } + return r; }; /** - * Returns a "disobedience factor" between 0 (perfectly obedient) and 100 (completely defiant) - * @param {App.Entity.SlaveState} slave - * @returns {number} + * @param {string} targetSkill - Skill to be checked. + * @param {Object} slave - Slave to be checked. + * @param {number} [skillIncrease=1] + * @returns {string} */ -globalThis.disobedience = function(slave) { - const devotionBaseline = 20; // with devotion above this number slaves will obey completely - const trustBaseline = -20; // with trust below this number slaves will obey completely +globalThis.slaveSkillIncrease = function(targetSkill, slave, skillIncrease = 1) { + let r = "", skillDec; + const {He, his, him} = getPronouns(slave); + const isleadershipRole = function() { + if (['headGirl', 'recruiter', 'bodyguard', 'madam', 'DJ', 'nurse', 'teacher', 'attendant', 'matron', 'stewardess', 'milkmaid', 'farmer', 'wardeness'].includes(targetSkill)) { + return true; + } + return false; + }; - if (slave.devotion > devotionBaseline || slave.trust < trustBaseline) { - return 0; // no chance of disobedience - } + if (slave.skill[targetSkill] <= 10) { + switch(targetSkill) { + case 'oral': + case 'vaginal': + case 'anal': + skillDec = `knowledge about ${targetSkill} sex,`; break; + case 'whoring': + skillDec = `knowledge about how to whore,`; break; + case 'entertainment': + skillDec = `knowledge about how to be entertaining,`; break; + } + if (isleadershipRole()) { + skillDec = `${capFirstChar(targetSkill)} skills.`; + } - // factors are between 0 (right on the boundary of perfectly obedient) and 10 (completely disobedient) - let devotionFactor = 10 - ((10 * (slave.devotion + 100)) / (devotionBaseline + 100)); - let trustFactor = (10 * (slave.trust - trustBaseline)) / (100 - trustBaseline); - return Math.round(devotionFactor * trustFactor); -}; + if (slave.skill[targetSkill] + skillIncrease > 10) { + r = `<span class="green">${He} now has basic ${skillDec}</span>`; + switch(targetSkill) { + case 'oral': + r += ` and at least suck a dick without constant gagging.`; break; + case 'vaginal': + r += ` and can avoid some of the common pitfalls and turnoffs.`; break; + case 'anal': + r += ` and can accept penetration of ${his} anus without danger.`; break; + case 'whoring': + r += ` and can avoid some potentially dangerous situations.`; break; + case 'entertainment': + r += ` and can usually avoid serious faux pas.`; break; + } + } + } else if (slave.skill[targetSkill] <= 30) { + switch(targetSkill) { + case 'oral': + case 'vaginal': + case 'anal': + skillDec = `${targetSkill} skills,`; break; + case 'whoring': + skillDec = `skill as a whore,`; break; + case 'entertainment': + skillDec = `skill as an entertainer,`; break; + } + if (isleadershipRole()) { + skillDec = `skill as a ${capFirstChar(targetSkill)}.`; + } -/** - * Returns a valid rape target for a slave who is going to rape one of his peers into rivalry with him. - * @param {App.Entity.SlaveState} slave - * @param {function(App.Entity.SlaveState): boolean} predicate - * @returns {App.Entity.SlaveState | undefined} - */ -globalThis.randomRapeRivalryTarget = function(slave, predicate) { - const willIgnoreRules = disobedience(slave) > jsRandom(0, 100); + if (slave.skill.oral + skillIncrease > 30) { + r = `<span class="green">${He} now has some ${skillDec}</span>`; + switch(targetSkill) { + case 'oral': + r += ` and can reliably bring dicks and pussies to climax with ${his} mouth.`; break; + case 'vaginal': + r += ` and can do more than just lie there and take it.`; break; + case 'anal': + r += ` and needs less preparation before taking rough penetration.`; break; + case 'whoring': + r += ` and knows how to sell ${his} body at a good price.`; break; + case 'entertainment': + r += ` and can flirt, dance, and strip acceptably.`; break; + } + } + } else if (slave.skill[targetSkill] <= 60) { + switch(targetSkill) { + case 'oral': + case 'vaginal': + case 'anal': + skillDec = `${targetSkill} sex expert,`; break; + case 'whoring': + skillDec = `expert whore,`; break; + case 'entertainment': + skillDec = `expert entertainer,`; break; + } + if (isleadershipRole()) { + skillDec = `expert ${capFirstChar(targetSkill)}.`; + } - function canBeARapeRival(s) { - return (s.devotion <= 95 && s.energy <= 95 && !s.rivalry && !s.fuckdoll && s.fetish !== "mindbroken"); - } + if (slave.skill[targetSkill] + skillIncrease > 60) { + r = `<span class="green">${He} is now an ${skillDec}</span>`; + switch(targetSkill) { + case 'oral': + r += ` and has a delightfully experienced tongue.`; break; + case 'vaginal': + r += ` and has the muscular control to massage anything that's inside ${him}.`; break; + case 'anal': + r += ` and knows how to use ${his} sphincter to please.`; break; + case 'whoring': + r += ` and can often make clients forget that $he's a prostitute they're paying for.`; break; + case 'entertainment': + r += ` and can flirt engagingly, dance alluringly, and strip arousingly.`; break; + } + } + } else if (slave.skill[targetSkill] < 100) { + switch(targetSkill) { + case 'oral': + case 'vaginal': + case 'anal': + skillDec = `has mastered ${targetSkill} sex,`; break; + case 'whoring': + skillDec = `is now a masterful whore,`; break; + case 'entertainment': + skillDec = `is now a masterful entertainer,`; break; + } + if (isleadershipRole()) { + skillDec = `is now a masterful ${capFirstChar(targetSkill)}.`; + } - function canRape(rapist, rapee) { - const opportunity = (assignmentVisible(rapist) && assignmentVisible(rapee)) || rapist.assignment === rapee.assignment; - const taboo = V.seeIncest === 0 && areRelated(rapist, rapee); - const desire = !(rapist.relationship >= 3 && rapist.relationshipTarget === rapee.id) && !taboo; - const permission = willIgnoreRules || App.Utils.sexAllowed(rapist, rapee); - return opportunity && desire && permission; + if (slave.skill[targetSkill] + skillIncrease >= 100) { + r = `<span class="green">${He} ${skillDec}</span>`; + switch(targetSkill) { + case 'oral': + r += ` and can learn nothing more about sucking dick or eating pussy.`; break; + case 'vaginal': + r += ` and can learn nothing more about tribbing or taking dick.`; break; + case 'anal': + r += ` and can learn nothing more about taking it up the ass.`; break; + case 'whoring': + r += ` and can learn nothing more about prostitution.`; break; + case 'entertainment': + r += ` and can learn nothing more about flirting, dancing, or stripping.`; break; + } + } } - if (typeof predicate !== 'function') { - predicate = (() => true); + if (isleadershipRole() && slave.skill[targetSkill] + skillIncrease >= 100) { + V.tutorGraduate.push(slave.ID); + V.slaveTutor[capFirstChar(targetSkill)].delete(slave.ID); } - - const arr = V.slaves.filter((s) => { return canBeARapeRival(s) && canRape(slave, s); }).shuffle(); - return arr.find(predicate); -}; - - -/** @typedef {object} getBestSlavesParams - * @property {string|function(App.Entity.SlaveState): number} part slave object property or custom function - * @property {number} [count] number of slaves to return - * @property {boolean} [largest] should it search for the biggest or smallest value - * @property {function(App.Entity.SlaveState): boolean} [filter] filter out undesired slaves - */ - -/** - * @param {getBestSlavesParams} params - * @returns {App.Entity.SlaveState[]} sorted from best to worst - */ -globalThis.getBestSlaves = function({part, count = 3, largest = true, filter = (() => true)}) { - const partCB = _.isFunction(part) ? part : (slave) => slave[part]; - - const sortMethod = largest ? (left, right) => right.value - left.value : (left, right) => left.value - right.value; - return V.slaves.filter(slave => filter(slave)) - .map(slave => ({slave, value: partCB(slave)})) - .sort(sortMethod) - .slice(0, count) - .map(slaveInfo => slaveInfo.slave); -}; -/** - * @param {getBestSlavesParams} info - * @returns {number[]} - */ -globalThis.getBestSlavesIDs = function(info) { - return getBestSlaves(info).map(slave => slave.ID); + slave.skill[targetSkill] += skillIncrease; + return r; }; /* @@ -2864,233 +3469,53 @@ globalThis.generateSlaveID = function() { return V.IDNumber++; }; -globalThis.ASDump = function() { - if ((typeof V.activeSlave === undefined) || (V.activeSlave === 0)) { - return `<span class="red">ERROR:</span> AS Dump, activeSlave invalid, returnTo is 'V.returnTo', previous passage was '${previous()}'. Please report this. `; - } else { - let SL = V.slaves.length; - let ID = V.activeSlave.ID; - if (V.i >= 0 && V.i < SL && V.slaves[V.i].ID === ID) { /* shortcut if V.i is already pointing to this slave */ - V.slaves[V.i] = V.activeSlave; - } else { - V.i = V.slaveIndices[ID]; // find V.i if exists - if (typeof V.i === undefined) { /* not found, so new slave */ - newSlave(V.activeSlave); - } else { - V.slaves[V.i] = V.activeSlave; - } - } - } -}; - -/** Returns the revivalist nationality associated with the player's arcology, or 0 if none - * @returns {string|0} - */ -globalThis.getRevivalistNationality = function() { - if (V.arcologies[0].FSRomanRevivalist > 90) { - return "Roman Revivalist"; - } else if (V.arcologies[0].FSAztecRevivalist > 90) { - return "Aztec Revivalist"; - } else if (V.arcologies[0].FSEgyptianRevivalist > 90) { - return "Ancient Egyptian Revivalist"; - } else if (V.arcologies[0].FSEdoRevivalist > 90) { - return "Edo Revivalist"; - } else if (V.arcologies[0].FSArabianRevivalist > 90) { - return "Arabian Revivalist"; - } else if (V.arcologies[0].FSChineseRevivalist > 90) { - return "Ancient Chinese Revivalist"; - } - return 0; -}; - -globalThis.penthouseCensus = function() { - function occupiesRoom(slave) { - if (slave.rules.living !== "luxurious") { - return false; // assigned to dormitory - } else if (slave.assignment === Job.HEADGIRL && V.HGSuite > 0) { - return false; // lives in HG suite - } else if (slave.assignment === Job.BODYGUARD && V.dojo > 0) { - return false; // lives in dojo - } else if (slave.relationship >= 4) { - const partner = getSlave(slave.relationshipTarget); - if (assignmentVisible(partner) && partner.ID < slave.ID && partner.rules.living === "luxurious") { - return false; // living with partner, who is already assigned a room (always allocate a room to the partner with the lower ID) - } - } - return true; // takes her own room - } - - const penthouseSlaves = V.slaves.filter(s => assignmentVisible(s)); - V.roomsPopulation = penthouseSlaves.filter(occupiesRoom).length; - V.dormitoryPopulation = penthouseSlaves.filter(s => s.rules.living !== "luxurious").length; -}; - -/** Determine whether a given penthouse slave can move into a private room or not. - * @param {App.Entity.SlaveState} slave - * @returns {boolean} - */ -globalThis.canMoveToRoom = function(slave) { - const partner = slave.relationship >= 4 ? getSlave(slave.relationshipTarget) : null; - const partnerHasRoom = partner && assignmentVisible(partner) && partner.rules.living === "luxurious"; - return partnerHasRoom || V.rooms - V.roomsPopulation >= 1; -}; - -/** - * @param {App.Entity.Facilities.Job|App.Entity.Facilities.Facility} jobOrFacility job or facility object - * @returns {App.Entity.SlaveState[]} array of slaves employed at the job or facility, sorted in accordance to user choice - */ -App.Utils.sortedEmployees = function(jobOrFacility) { - const employees = jobOrFacility.employees(); - SlaveSort.slaves(employees); - return employees; -}; - /** - * @param {Array<string|App.Entity.Facilities.Facility>} [facilities] - * @param {Object.<string, string>} [mapping] Optional mapping for the property names in the result object. Keys - * are the standard facility names, values are the desired names. - * @returns {Object.<string, number>} + * @param {number} ID + * @returns {App.Entity.SlaveState} */ -App.Utils.countFacilityWorkers = function(facilities = null, mapping = {}) { - facilities = facilities || Object.values(App.Entity.facilities); - /** @type {App.Entity.Facilities.Facility[]} */ - const fObjects = facilities.map(f => typeof f === "string" ? App.Entity.facilities[f] : f); - return fObjects.reduce((acc, cur) => { - acc[mapping[cur.desc.baseName] || cur.desc.baseName] = cur.employeesIDs().size; return acc; - }, {}); +globalThis.slaveStateById = function(ID) { + const index = V.slaveIndices[ID]; + return index === undefined ? null : V.slaves[index]; }; /** - * @param {string[]} [assignments] array of assignment strings. The default is to count for all assignments - * @returns {Object.<string, number>} + * @param {number} ID + * @returns {App.Entity.SlaveState} */ -App.Utils.countAssignmentWorkers = function(assignments) { - assignments = assignments || Object.values(Job); - return assignments.reduce((acc, cur) => { acc[cur] = V.JobIDMap[cur].size; return acc; }, {}); +globalThis.getSlave = function(ID) { + const index = V.slaveIndices[ID]; + return index === undefined ? undefined : V.slaves[index]; }; -/** Calculate and return economic uncertainty multiplier for a given arcology - * @param {number} arcologyID - * @returns {number} - */ -App.Utils.economicUncertainty = function(arcologyID) { - let uncertainty = arcologyID === 0 ? 5 : 10; - if (assistant.power === 1) { - uncertainty -= Math.max(Math.trunc(uncertainty/2), 0); - } else if (assistant.power > 1) { - uncertainty = 0; - } - return jsRandom(100 - uncertainty, 100 + uncertainty) / 100; +globalThis.getChild = function(ID) { + return V.cribs.find(s => s.ID === ID); }; -/** Deflate a slave (reset inflation to none) +/** + * Returns a valid rape target for a slave who is going to rape one of his peers into rivalry with him. * @param {App.Entity.SlaveState} slave + * @param {function(App.Entity.SlaveState): boolean} predicate + * @returns {App.Entity.SlaveState | undefined} */ -globalThis.deflate = function(slave) { - slave.inflation = 0; - slave.inflationType = "none"; - slave.inflationMethod = 0; - slave.milkSource = 0; - slave.cumSource = 0; - SetBellySize(slave); -}; - -/** Notify the game that the sidebar needs to be refreshed as soon as possible, but do not do it immediately. - * This allows us to call this function repeatedly without impacting performance (for example, from repX() and cashX()). - * The game will redraw the sidebar exactly once, as soon as all the scripts have finished executing. - */ -App.Utils.scheduleSidebarRefresh = (function() { - let refresh = false; +globalThis.randomRapeRivalryTarget = function(slave, predicate) { + const willIgnoreRules = disobedience(slave) > jsRandom(0, 100); - function updateSidebar() { - refresh = false; - UIBar.update(); - App.UI.updateSidebarTooltips(); + function canBeARapeRival(s) { + return (s.devotion <= 95 && s.energy <= 95 && !s.rivalry && !s.fuckdoll && s.fetish !== "mindbroken"); } - function schedule() { - if (!refresh) { - refresh = true; - setTimeout(updateSidebar, 0); - } + function canRape(rapist, rapee) { + const opportunity = (assignmentVisible(rapist) && assignmentVisible(rapee)) || rapist.assignment === rapee.assignment; + const taboo = V.seeIncest === 0 && areRelated(rapist, rapee); + const desire = !(rapist.relationship >= 3 && rapist.relationshipTarget === rapee.id) && !taboo; + const permission = willIgnoreRules || App.Utils.sexAllowed(rapist, rapee); + return opportunity && desire && permission; } - return schedule; -})(); - -/** Calculate various averages for the master suite slaves - * @returns {{energy: number, milk: number, cum: number, dom: number, sadism: number, dick: number, preg: number}} - */ -App.Utils.masterSuiteAverages = (function() { - const domMap = {dom: 1, submissive: -1}; - const sadismMap = {sadism: 1, masochism: -1}; - - /** Returns either zero or the results of mapping the slave's fetish through an object containing fetish names and result values - * @param {App.Entity.SlaveState} s - * @param {Object.<string, number>} map - * @returns {number} - */ - const fetishMapOrZero = (s, map) => map.hasOwnProperty(s.fetish) ? map[s.fetish] : 0; - - return () => { - const msSlaves = App.Entity.facilities.masterSuite.employees(); - return { - energy: _.mean(msSlaves.map(s => s.energy)), - milk: _.mean(msSlaves.map(s => s.lactation*(s.boobs-s.boobsImplant))), - cum: _.mean(msSlaves.map(s => canAchieveErection(s) ? s.balls : 0)), - dick: _.mean(msSlaves.map(s => canAchieveErection(s) ? s.dick : 0)), - preg: _.mean(msSlaves.map(s => s.preg)), - sadism: _.mean(msSlaves.map(s => (s.fetishStrength * fetishMapOrZero(s, sadismMap)))), - dom: _.mean(msSlaves.map(s => (s.fetishStrength * fetishMapOrZero(s, domMap)))) - }; - }; -})(); - -App.Utils.schoolCounter = function() { - return Array.from(App.Data.misc.schools.keys()).filter(s => V[s].schoolPresent).length; -}; - -App.Utils.schoolFailure = function() { - return Array.from(App.Data.misc.schools.keys()).find(s => V[s].schoolPresent && V[s].schoolProsperity <= -10); -}; - -App.Utils.alphabetizeIterable = function(iterable) { - const compare = function(a, b) { - let aTitle = a.toLowerCase(); - let bTitle = b.toLowerCase(); - - aTitle = removeArticles(aTitle); - bTitle = removeArticles(bTitle); - - if (aTitle > bTitle) { - return 1; - } - if (aTitle < bTitle) { - return -1; - } - return 0; - }; - - function removeArticles(str) { - const words = str.split(" "); - if (words.length <= 1) { - return str; - } - if ( words[0] === 'a' || words[0] === 'the' || words[0] === 'an' ) { - return words.splice(1).join(" "); - } - return str; + if (typeof predicate !== 'function') { + predicate = (() => true); } - const clonedArray = (Array.from(iterable)); - return clonedArray.sort(compare); -}; -/** - * Returns how exposing a slave's outfit is, after taking into consideration a topless outfit is more revealing for beboobed slaves or female ones. - * @param {App.Entity.SlaveState} slave - * @returns {0|1|2|3|4} - */ -globalThis.getExposure = function(slave) { - const clothes = App.Data.clothes.get(slave.clothes); - return (clothes.topless && clothes.exposure < 3 && (slave.boobs > 299 || (slave.genes === 'XX' && slave.vagina >= 0))) ? 3 : clothes.exposure; + const arr = V.slaves.filter((s) => { return canBeARapeRival(s) && canRape(slave, s); }).shuffle(); + return arr.find(predicate); }; diff --git a/src/js/utilsSlaves.js b/src/js/utilsSlaves.js new file mode 100644 index 0000000000000000000000000000000000000000..79222bba4de3558011b209d9be326bb6d86eba17 --- /dev/null +++ b/src/js/utilsSlaves.js @@ -0,0 +1,237 @@ +globalThis.SlaveSort = function() { + const effectivePreg = (slave) => { + // slave.preg is only *mostly* usable for sorting + if (slave.preg > 0 && !slave.pregKnown) { + // don't reveal unknown pregnancies + return 0; + } + if (slave.pubertyXX === 0 && (slave.ovaries === 1 || slave.mpreg === 1)) { + // not ovulating yet - sort between barren slaves and slaves on contraceptives + return -1.2; + } else if (slave.ovaryAge >= 47 && (slave.ovaries === 1 || slave.mpreg === 1)) { + // menopausal - sort between barren slaves and slaves on contraceptives + return -1.1; + } else if (slave.pregWeek < 0) { + // postpartum - sort between slaves on contraceptives and fertile slaves + return -0.1; + } + return slave.preg; + }; + + const effectiveEnergy = (slave) => { + return slave.attrKnown === 1 ? slave.energy : -101; + }; + + const comparators = { + Aassignment: (a, b) => a.assignment < b.assignment ? -1 : 1, + Dassignment: (a, b) => a.assignment > b.assignment ? -1 : 1, + Aname: (a, b) => a.slaveName < b.slaveName ? -1 : 1, + Dname: (a, b) => a.slaveName > b.slaveName ? -1 : 1, + Aseniority: (a, b) => b.weekAcquired - a.weekAcquired, + Dseniority: (a, b) => a.weekAcquired - b.weekAcquired, + AactualAge: (a, b) => a.actualAge - b.actualAge, + DactualAge: (a, b) => b.actualAge - a.actualAge, + AvisualAge: (a, b) => a.visualAge - b.visualAge, + DvisualAge: (a, b) => b.visualAge - a.visualAge, + AphysicalAge: (a, b) => a.physicalAge - b.physicalAge, + DphysicalAge: (a, b) => b.physicalAge - a.physicalAge, + Adevotion: (a, b) => a.devotion - b.devotion, + Ddevotion: (a, b) => b.devotion - a.devotion, + AID: (a, b) => a.ID - b.ID, + DID: (a, b) => b.ID - a.ID, + AweeklyIncome: (a, b) => a.lastWeeksCashIncome - b.lastWeeksCashIncome, + DweeklyIncome: (a, b) => b.lastWeeksCashIncome - a.lastWeeksCashIncome, + Ahealth: (a, b) => a.health.health - b.health.health, + Dhealth: (a, b) => b.health.health - a.health.health, + Aweight: (a, b) => a.weight - b.weight, + Dweight: (a, b) => b.weight - a.weight, + Amuscles: (a, b) => a.muscles - b.muscles, + Dmuscles: (a, b) => b.muscles - a.muscles, + AsexDrive: (a, b) => effectiveEnergy(a) - effectiveEnergy(b), + DsexDrive: (a, b) => effectiveEnergy(b) - effectiveEnergy(a), + Apregnancy: (a, b) => effectivePreg(a) - effectivePreg(b), + Dpregnancy: (a, b) => effectivePreg(b) - effectivePreg(a), + }; + + return { + slaves: sortSlaves, + IDs: sortIDs, + indices: sortIndices + }; + + /** @param {App.Entity.SlaveState[]} [slaves] */ + function sortSlaves(slaves) { + slaves = slaves || V.slaves; + slaves.sort(_comparator()); + if (slaves === V.slaves) { + V.slaveIndices = slaves2indices(); + } + } + + /** @param {number[]} [slaveIDs] */ + function sortIDs(slaveIDs) { + const slaves = V.slaves; + const slaveIndices = V.slaveIndices; + const cmp = _comparator(); + slaveIDs = slaveIDs || slaves.map(s => s.ID); + slaveIDs.sort((IDa, IDb) => cmp(slaves[slaveIndices[IDa]], slaves[slaveIndices[IDb]])); + } + + /** @param {number[]} [slaveIdxs] */ + function sortIndices(slaveIdxs) { + const slaves = V.slaves; + const cmp = _comparator(); + slaveIdxs = slaveIdxs || [...slaves.keys()]; + slaveIdxs.sort((ia, ib) => cmp(slaves[ia], slaves[ib])); + } + + /** + * @callback slaveComparator + * @param {App.Entity.SlaveState} a + * @param {App.Entity.SlaveState} b + * @returns {number} + */ + /** @returns {slaveComparator} */ + function _comparator() { + return _makeStableComparator(comparators[(V.sortSlavesOrder === "ascending" ? 'A' : 'D') + V.sortSlavesBy]); + } + + /** secondary-sort by ascending ID if the primary comparator would return 0 (equal), so we have a guaranteed stable order regardless of input + * @param {slaveComparator} comparator + * @returns {slaveComparator} + */ + function _makeStableComparator(comparator) { + return function(a, b) { + return comparator(a, b) || comparators.AID(a, b); + }; + } +}(); + +/** + * @param {App.Entity.SlaveState[]} slaves + */ +globalThis.slaveSortMinor = function(slaves) { + slaves.sort((a, b) => a.slaveName < b.slaveName ? -1 : 1); +}; + +/** @typedef {object} getBestSlavesParams + * @property {string|function(App.Entity.SlaveState): number} part slave object property or custom function + * @property {number} [count] number of slaves to return + * @property {boolean} [largest] should it search for the biggest or smallest value + * @property {function(App.Entity.SlaveState): boolean} [filter] filter out undesired slaves + */ + +/** + * @param {getBestSlavesParams} params + * @returns {App.Entity.SlaveState[]} sorted from best to worst + */ +globalThis.getBestSlaves = function({part, count = 3, largest = true, filter = (() => true)}) { + const partCB = _.isFunction(part) ? part : (slave) => slave[part]; + + const sortMethod = largest ? (left, right) => right.value - left.value : (left, right) => left.value - right.value; + return V.slaves.filter(slave => filter(slave)) + .map(slave => ({slave, value: partCB(slave)})) + .sort(sortMethod) + .slice(0, count) + .map(slaveInfo => slaveInfo.slave); +}; + +/** + * @param {getBestSlavesParams} info + * @returns {number[]} + */ +globalThis.getBestSlavesIDs = function(info) { + return getBestSlaves(info).map(slave => slave.ID); +}; + +/** + * @param {App.Entity.SlaveState[]} [slaves] + * @returns {Object.<number, number>} + */ +globalThis.slaves2indices = function(slaves = V.slaves) { + return slaves.reduce((acc, slave, i) => { acc[slave.ID] = i; return acc; }, {}); +}; + +/** Calculate various averages for the master suite slaves + * @returns {{energy: number, milk: number, cum: number, dom: number, sadism: number, dick: number, preg: number}} + */ +App.Utils.masterSuiteAverages = (function() { + const domMap = {dom: 1, submissive: -1}; + const sadismMap = {sadism: 1, masochism: -1}; + + /** Returns either zero or the results of mapping the slave's fetish through an object containing fetish names and result values + * @param {App.Entity.SlaveState} s + * @param {Object.<string, number>} map + * @returns {number} + */ + const fetishMapOrZero = (s, map) => map.hasOwnProperty(s.fetish) ? map[s.fetish] : 0; + + return () => { + const msSlaves = App.Entity.facilities.masterSuite.employees(); + return { + energy: _.mean(msSlaves.map(s => s.energy)), + milk: _.mean(msSlaves.map(s => s.lactation*(s.boobs-s.boobsImplant))), + cum: _.mean(msSlaves.map(s => canAchieveErection(s) ? s.balls : 0)), + dick: _.mean(msSlaves.map(s => canAchieveErection(s) ? s.dick : 0)), + preg: _.mean(msSlaves.map(s => s.preg)), + sadism: _.mean(msSlaves.map(s => (s.fetishStrength * fetishMapOrZero(s, sadismMap)))), + dom: _.mean(msSlaves.map(s => (s.fetishStrength * fetishMapOrZero(s, domMap)))) + }; + }; +})(); + +globalThis.penthouseCensus = function() { + function occupiesRoom(slave) { + if (slave.rules.living !== "luxurious") { + return false; // assigned to dormitory + } else if (slave.assignment === Job.HEADGIRL && V.HGSuite > 0) { + return false; // lives in HG suite + } else if (slave.assignment === Job.BODYGUARD && V.dojo > 0) { + return false; // lives in dojo + } else if (slave.relationship >= 4) { + const partner = getSlave(slave.relationshipTarget); + if (assignmentVisible(partner) && partner.ID < slave.ID && partner.rules.living === "luxurious") { + return false; // living with partner, who is already assigned a room (always allocate a room to the partner with the lower ID) + } + } + return true; // takes her own room + } + + const penthouseSlaves = V.slaves.filter(s => assignmentVisible(s)); + V.roomsPopulation = penthouseSlaves.filter(occupiesRoom).length; + V.dormitoryPopulation = penthouseSlaves.filter(s => s.rules.living !== "luxurious").length; +}; + +/** + * @param {App.Entity.Facilities.Job|App.Entity.Facilities.Facility} jobOrFacility job or facility object + * @returns {App.Entity.SlaveState[]} array of slaves employed at the job or facility, sorted in accordance to user choice + */ +App.Utils.sortedEmployees = function(jobOrFacility) { + const employees = jobOrFacility.employees(); + SlaveSort.slaves(employees); + return employees; +}; + +/** + * @param {Array<string|App.Entity.Facilities.Facility>} [facilities] + * @param {Object.<string, string>} [mapping] Optional mapping for the property names in the result object. Keys + * are the standard facility names, values are the desired names. + * @returns {Object.<string, number>} + */ +App.Utils.countFacilityWorkers = function(facilities = null, mapping = {}) { + facilities = facilities || Object.values(App.Entity.facilities); + /** @type {App.Entity.Facilities.Facility[]} */ + const fObjects = facilities.map(f => typeof f === "string" ? App.Entity.facilities[f] : f); + return fObjects.reduce((acc, cur) => { + acc[mapping[cur.desc.baseName] || cur.desc.baseName] = cur.employeesIDs().size; return acc; + }, {}); +}; + +/** + * @param {string[]} [assignments] array of assignment strings. The default is to count for all assignments + * @returns {Object.<string, number>} + */ +App.Utils.countAssignmentWorkers = function(assignments) { + assignments = assignments || Object.values(Job); + return assignments.reduce((acc, cur) => { acc[cur] = V.JobIDMap[cur].size; return acc; }, {}); +}; diff --git a/src/js/utilsUnits.js b/src/js/utilsUnits.js new file mode 100644 index 0000000000000000000000000000000000000000..bb2ca3278f4b2654efcd1842b3420e60e24feec9 --- /dev/null +++ b/src/js/utilsUnits.js @@ -0,0 +1,505 @@ +/** + * Returns numbers as text, e.g. 10 as "ten", according to the player's settings + * @param {number} x + * @param {boolean} [printText=false] (optional) + * @returns {string} + */ +globalThis.num = function(x, printText = false) { + const max = V.showNumbersMax; + + const ONE_TO_NINETEEN = [ + "one", "two", "three", "four", "five", + "six", "seven", "eight", "nine", "ten", + "eleven", "twelve", "thirteen", "fourteen", "fifteen", + "sixteen", "seventeen", "eighteen", "nineteen", + ]; + + const TENS = [ + "ten", "twenty", "thirty", "forty", "fifty", + "sixty", "seventy", "eighty", "ninety", + ]; + + const SCALES = ["thousand", "million", "billion", "trillion", "quadrillion", "quintillion", "sextillion", "septillion", "octillion", "nonillion", "decillion"]; + + /** + * helper function for use with Array.filter + * @param {any} item + * @returns {boolean} + */ + function isTruthy(item) { + return !!item; + } + + /** + * convert a number into "chunks" of 0-999 + * @param {number} number + * @returns {number[]} + */ + function chunk(number) { + const thousands = []; + + while (number > 0) { + thousands.push(number % 1000); + number = Math.floor(number / 1000); + } + + return thousands; + } + + /** + * translate a number from 1-999 into English + * @param {number} number + * @returns {string} + */ + function inEnglish(number) { + let hundreds; + let tens; + let ones; + const words = []; + + if (number === 0) { + return "zero"; + } + + if (number < 20) { + return ONE_TO_NINETEEN[number - 1]; // may be undefined + } + + if (number < 100) { + ones = number % 10; + tens = number / 10 | 0; // equivalent to Math.floor(number / 10) + + words.push(TENS[tens - 1]); + words.push(inEnglish(ones)); + + return words.filter(isTruthy).join("-"); + } + + hundreds = number / 100 | 0; + words.push(inEnglish(hundreds)); + words.push("hundred"); + words.push(inEnglish(number % 100)); + + return words.filter(isTruthy).join(" "); + } + + if (printText) { + return inEnglish(x); + } + + /** + * append the word for a scale. Made for use with Array.map + * @param {string} chunk + * @param {number} exp + * @returns {string} + */ + function appendScale(chunk, exp) { + let scale; + if (!chunk) { + return null; + } + scale = SCALES[exp - 1]; + return [chunk, scale].filter(isTruthy).join(" "); + } + + if (V.showNumbers === 2) { + return commaNum(x); + } else { + if (x === 0) { + return "zero"; + } + + if (V.showNumbers === 1 && Math.abs(x) > max) { + return commaNum(x); + } + + let numberAsString = chunk(Math.abs(x)) + .map(inEnglish) + .map(appendScale) + .filter(isTruthy) + .reverse() + .join(" "); + + if (x > 0) { + return numberAsString; + } else { + return `negative ${numberAsString}`; + } + } +}; + +globalThis.asPlural = function(single, plural) { + if (typeof single !== 'string') { + let asObj = single; + single = asObj.single; + plural = asObj.plural; + } + if (plural == null) { + plural = single + "s"; + } + return plural; +}; +globalThis.asSingular = function(single) { + if (typeof single !== 'string') { + let asObj = single; + single = asObj.single; + } + return single; +}; +// When 1, shows "a (slave)" +globalThis.numberWithPlural = function(number, single, plural) { + if (number === 0) { + return "no " + asPlural(single, plural); + } else if (number === 1) { + return addA(asSingular(single)); + } else if (number > 0 && number < 1) { + return "less than one " + asSingular(single); + } else { + return number + " " + asPlural(single, plural); + } +}; + +// when 1, shows "one (slave)" +globalThis.numberWithPluralOne = function(number, single, plural) { + if (number === 0) { + return "no " + asPlural(single, plural); + } else if (number === 1) { + return "one " + asSingular(single); + } else if (number > 0 && number < 1) { + return "less than one " + asSingular(single); + } else { + return number + " " + asPlural(single, plural); + } +}; +// shows "less than one (slave)" instead of "no (slaves)" when number is 0. +globalThis.numberWithPluralNonZero = function(number, single, plural) { + if (number === 0) { + number = 0.1; + } + return numberWithPlural(number, single, plural); +}; +globalThis.onlyPlural = function(number, single, plural) { + if (number > 0 && number <= 1) { + return asSingular(single); + } + return asPlural(single, plural); +}; +globalThis.Separator = function(SeparatorObject) { + if (SeparatorObject.need) { + return SeparatorObject.text; + } + SeparatorObject.need = true; + return ""; +}; +/** + * Returns numbers with comma, e.g. 10000 as "10,000", according to the player's settings + * @param {number} s + * @returns {string} + */ +globalThis.commaNum = function(s) { + // Separated from num because some places in code (like long lists, tables) should never have numbers spelled out, but still benefit from commas + if (!s) { + return "0"; + } + if (V.formatNumbers !== 1) { + return s.toString(); + } else { + return s.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); + } +}; + +/** + * Returns the number of weeks in a years / months / weeks format + * @param {number} weeks + * @returns {string} + */ +globalThis.years = function(weeks) { + let years = 0; + let quarters = 0; // needed for calc, not user facing + let months = 0; + let array = []; + + // A year is always 52 weeks + // that could be 13 months, but lets say 4 quarters each getting an extra week (13 weeks) + + // Find years + years = Math.trunc(weeks / 52); + + if (years >= 1) { // Is there at least 1 year + weeks = weeks - (years * 52); // Find leftover weeks + } + if (weeks && weeks / 13 >= 1) { // Is there at least 1 quarter + quarters = Math.trunc(weeks / 13); // How many quarters? + weeks = weeks - (quarters * 13); // A quarter contains 13 weeks, how many extra weeks do we have? + } + if (weeks && weeks / 4 >= 1) { // Is there at least 1 month + months = Math.trunc(weeks / 4); // How many months? + if (months === 3) { // Almost a quarter of a year + months--; // Quarters have 13 weeks though, so let's be sure the extra is in weeks. Otherwise 51 will return "12 months" instead of "11 months and 4 weeks." + } + weeks = weeks - (months * 4); // A month contains 4 weeks, how many extra weeks do we have? + } + + // So we have years, quarters, months, and weeks. + + // Quarters are useless so: + + months += quarters * 3; // Each quarter has three months. + + if (years) { + array.push(`${num(years)} year${years !== 1 ? `s` : ``}`); + } + + if (months) { + array.push(`${num(months)} month${months !== 1 ? `s` : ``}`); + } + + if (weeks) { + array.push(`${num(weeks)} week${weeks !== 1 ? `s` : ``}`); + } + + return array.toStringExt(); +}; +/** + * @param {number} [weeks] + * @param {number} [bonusDay] + * @returns {Date} + */ +globalThis.asDate = function(weeks = null, bonusDay = 0) { + if (weeks == null) { + weeks = V.week; + } + let d = new Date(2037, 0, 12); + d.setDate(d.getDate() + weeks * 7 + bonusDay); + return d; +}; +/** + * @param {number} [weeks] + * @param {number} [bonusDay] + * @returns {string} + */ +globalThis.asDateString = function(weeks = null, bonusDay = 0) { + return asDate(weeks, bonusDay).toLocaleString(undefined, {year: 'numeric', month: 'long', day: 'numeric'}); +}; + +/** + * @param {number} s + * @returns {string} + */ +globalThis.cashFormat = function(s) { + if (s < 0) { + return `-¤${commaNum(Math.abs(s))}`; + } + return `¤${commaNum(s)}`; +}; +globalThis.cashFormatColor = function(s, invert = false) { + if (invert) { + s = -1 * s; + } + // Display red if the value is negative, unless invert is true + if (s < 0) { + return `<span class='red'>${cashFormat(s)}</span>`; + // White for exactly zero + } else if (s === 0) { + return `<span>${cashFormat(s)}</span>`; + // Yellow for positive + } else { + return `<span class='yellowgreen'>${cashFormat(s)}</span>`; + } +}; + +/** + * @param {number} s + * @returns {string} + */ +globalThis.repFormat = function(s) { + /* if (!s) { s = 0; }*/ + if (V.cheatMode === 1 || V.debugMode === 1) { + if (s > 0) { + return `<span class="green">${commaNum(Math.round(s * 100) / 100)} rep</span>`; + } else if (s < 0) { + return `<span class="red">${commaNum(Math.round(s * 100) / 100)} rep</span>`; + } else { + return `${commaNum(Math.round(s * 100) / 100)} rep`; + } + } else { + /* In order to calculate just how much any one category matters so we can show a "fuzzy" symbolic value to the player, we need to know how "busy" reputation was this week. To calculate this, I ADD income to expenses. Why? 100 - 100 and 10000 - 10000 BOTH are 0, but a +50 event matters a lot more in the first case than the second. I exclude overflow from the calculation because it's not a "real" expense for our purposes, and divide by half just to make percentages a bit easier. */ + let weight = s / (((V.lastWeeksRepIncome.Total - V.lastWeeksRepExpenses.Total) + V.lastWeeksRepExpenses.overflow) / 2); + if (weight > 0.60) { + return `<span class="green">+++++ rep</span>`; + } else if (weight > 0.45) { + return `<span class="green">++++ rep</span>`; + } else if (weight > 0.30) { + return `<span class="green">+++ rep</span>`; + } else if (weight > 0.15) { + return `<span class="green">++ rep</span>`; + } else if (weight > 0.0) { + return `<span class="green">+ rep</span>`; + } else if (weight === 0) { + return "0 rep"; + } else if (weight < -0.60) { + return `<span class="red">−−−−− rep</span>`; + } else if (weight < -0.45) { + return `<span class="red">−−−− rep</span>`; + } else if (weight < -0.30) { + return `<span class="red">−−− rep</span>`; + } else if (weight < -0.15) { + return `<span class="red">−− rep</span>`; + } else if (weight < 0) { + return `<span class="red">− rep</span>`; + } + /* return weight;*/ + } +}; + +/** + * @param {number} s + * @returns {string} + */ +globalThis.massFormat = function(s) { + if (!s) { + s = 0; + } + if (Math.abs(s) >= 1000) { + s = Math.trunc(s / 1000); + if (s !== 1) { + return `${num(s)} tons`; + } else { + return `${num(s)} ton`; + } + } else { + return `${num(s)} kg`; + } +}; + +/** + * Takes an integer e.g. slave.hLength, returns a string in the format 10 inches + * @param {number} cm + * @returns {string} + */ +globalThis.cmToInchString = function(cm) { + let inches = cm / 2.54; + if (inches > 0 && inches < 1) { + return "less than an inch"; + } + inches = Math.round(inches); + if (inches === 1) { + return "1 inch"; + } + return `${inches} inches`; +}; + +/** + * takes an integer e.g. slave.height, returns a string in the format 6'5" + * @param {number} cm + * @returns {string} + */ +globalThis.cmToFootInchString = function(cm) { + if (Math.round(cm / 2.54) < 12) { + return cmToInchString(cm); + } + return `${Math.trunc(Math.round(cm / 2.54) / 12)}'${Math.round(cm / 2.54) % 12}"`; +}; + +/** + * takes a dick value e.g. slave.dick, returns a string in the format 6 inches + * @param {number} dick + * @returns {string} + */ +globalThis.dickToInchString = function(dick) { + return cmToInchString(dickToCM(dick)); +}; + +/** + * takes a dick value e.g. slave.dick, returns an int of the dick length in cm + * @param {number} dick + * @returns {number} + */ +globalThis.dickToCM = function(dick) { + if (dick < 9) { + return dick * 5; + } else if (dick === 9) { + return 50; + } + return dick * 6; +}; +/** + * takes a ball value e.g. slave.balls, returns a string in the format 3 inches + * @param {number} balls + * @returns {string} + */ +globalThis.ballsToInchString = function(balls) { + return cmToInchString(ballsToCM(balls)); +}; + +/** + * takes a ball value e.g. slave.balls, returns an int of the ball size in cm + * @param {number} balls + * @returns {number} + */ +globalThis.ballsToCM = function(balls) { + if (balls < 2) { + return 0; + } + return (balls < 10 ? (balls - 1) * 2 : balls * 2); +}; + +/** + * takes a dick value e.g. slave.dick, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm` + * @param {number} dick + * @returns {string} + */ +globalThis.dickToEitherUnit = function(dick) { + if (V.showInches === 1) { + return `${dickToCM(dick)}cm (${dickToInchString(dick)})`; + } + if (V.showInches === 2) { + return dickToInchString(dick); + } + return `${dickToCM(dick)}cm`; +}; + +/** + * takes a ball value e.g. slave.balls, returns a string in the format of either `20cm (8 inches)`, `8 inches`, or `20cm` + * @param {number} balls + * @returns {string} + */ +globalThis.ballsToEitherUnit = function(balls) { + if (V.showInches === 1) { + return `${ballsToCM(balls)}cm (${ballsToInchString(balls)})`; + } + if (V.showInches === 2) { + return ballsToInchString(balls); + } + return `${ballsToCM(balls)}cm`; +}; + +/** + * takes an int in centimeters e.g. slave.height, returns a string in the format of either `200cm (6'7")`, `6'7"`, or `200cm` + * @param {number} height + * @returns {string} + */ +globalThis.heightToEitherUnit = function(height) { + if (V.showInches === 1) { + return `${height}cm (${cmToFootInchString(height)})`; + } + if (V.showInches === 2) { + return cmToFootInchString(height); + } + return `${height}cm`; +}; + +/** + * takes an int in centimeters e.g. slave.hLength, returns a string in the format of either `30cm (12 inches)`, `12 inches`, or `30cm` + * @param {number} length + * @returns {string} + */ +globalThis.lengthToEitherUnit = function(length) { + if (V.showInches === 1) { + return `${length}cm (${cmToInchString(length)})`; + } + if (V.showInches === 2) { + return cmToInchString(length); + } + return `${length}cm`; +}; diff --git a/src/npc/surgery/bodySwap/bodySwap.js b/src/npc/surgery/bodySwap/bodySwap.js new file mode 100644 index 0000000000000000000000000000000000000000..6ddecdcff2200e9f4c5b2754ed3eb345745e7b97 --- /dev/null +++ b/src/npc/surgery/bodySwap/bodySwap.js @@ -0,0 +1,361 @@ +/** + * + * @param {App.Entity.SlaveState} soul + * @param {App.Entity.SlaveState} body + * @param {boolean} fromGenepool is slave from the genepool? + */ +globalThis.bodySwap = function(soul, body, fromGenepool) { + WombInit(body); // Just to be sure. + soul.genes = body.genes; + soul.physicalAge = body.physicalAge; + soul.visualAge = body.visualAge; + soul.ageImplant = body.ageImplant; + soul.health = body.health; + soul.weight = body.weight; + soul.muscles = body.muscles; + soul.height = body.height; + soul.heightImplant = body.heightImplant; + soul.race = body.race; + soul.origRace = body.origRace; + soul.pubicHColor = body.pubicHColor; + soul.skin = body.skin; + soul.origSkin = body.origSkin; + soul.markings = body.markings; + soul.eye = body.eye; + soul.hears = body.hears; + soul.earImplant = body.earImplant; + soul.earShape = body.earShape; + soul.earT = body.earT; + soul.earTColor = body.earTColor; + soul.smells = body.smells; + soul.tastes = body.tastes; + soul.horn = body.horn; + soul.hornColor = body.hornColor; + soul.PTail = body.PTail; + soul.tail = body.tail; + soul.tailShape = body.tailShape; + soul.tailColor = body.tailColor; + soul.origHColor = body.origHColor; + soul.hColor = body.hColor; + soul.hLength = body.hLength; + soul.hStyle = body.hStyle; + soul.pubicHStyle = body.pubicHStyle; + soul.waist = body.waist; + soul.corsetPiercing = body.corsetPiercing; + soul.arm = body.arm; + soul.leg = body.leg; + soul.PLimb = body.PLimb; + soul.readyProsthetics = body.readyProsthetics; + soul.heels = body.heels; + soul.voice = body.voice; + soul.voiceImplant = body.voiceImplant; + soul.shoulders = body.shoulders; + soul.shouldersImplant = body.shouldersImplant; + soul.boobs = body.boobs; + soul.boobsImplant = body.boobsImplant; + soul.boobsImplantType = body.boobsImplantType; + soul.boobShape = body.boobShape; + soul.nipples = body.nipples; + soul.nipplesPiercing = body.nipplesPiercing; + soul.nipplesAccessory = body.nipplesAccessory; + soul.areolae = body.areolae; + soul.areolaePiercing = body.areolaePiercing; + soul.areolaeShape = body.areolaeShape; + soul.boobsTat = body.boobsTat; + soul.lactation = body.lactation; + soul.lactationDuration = body.lactationDuration; + soul.induceLactation = body.induceLactation; + soul.boobsMilk = body.boobsMilk; + soul.lactationAdaptation = body.lactationAdaptation; + soul.hips = body.hips; + soul.hipsImplant = body.hipsImplant; + soul.butt = body.butt; + soul.buttImplant = body.buttImplant; + soul.buttImplantType = body.buttImplantType; + soul.buttTat = body.buttTat; + soul.face = body.face; + soul.faceImplant = body.faceImplant; + soul.faceShape = body.faceShape; + soul.lips = body.lips; + soul.lipsImplant = body.lipsImplant; + soul.lipsPiercing = body.lipsPiercing; + soul.lipsTat = body.lipsTat; + soul.teeth = body.teeth; + soul.tonguePiercing = body.tonguePiercing; + soul.vagina = body.vagina; + soul.vaginaLube = body.vaginaLube; + soul.vaginaPiercing = body.vaginaPiercing; + soul.vaginaTat = body.vaginaTat; + soul.fertKnown = body.fertKnown; + soul.fertPeak = body.fertPeak; + soul.broodmother = body.broodmother; + soul.broodmotherFetuses = body.broodmotherFetuses; + soul.broodmotherOnHold = body.broodmotherOnHold; + soul.broodmotherCountDown = body.broodmotherCountDown; + soul.labia = body.labia; + soul.clit = body.clit; + soul.clitPiercing = body.clitPiercing; + soul.dick = body.dick; + soul.foreskin = body.foreskin; + soul.anus = body.anus; + soul.analArea = body.analArea; + soul.dickPiercing = body.dickPiercing; + soul.dickTat = body.dickTat; + soul.prostate = body.prostate; + soul.balls = body.balls; + soul.scrotum = body.scrotum; + soul.ovaries = body.ovaries; + soul.anusPiercing = body.anusPiercing; + soul.anusTat = body.anusTat; + soul.brand = body.brand; + soul.nosePiercing = body.nosePiercing; + soul.eyebrowPiercing = body.eyebrowPiercing; + soul.navelPiercing = body.navelPiercing; + soul.shouldersTat = body.shouldersTat; + soul.armsTat = body.armsTat; + soul.legsTat = body.legsTat; + soul.backTat = body.backTat; + soul.stampTat = body.stampTat; + soul.hormones = body.hormones; + soul.chem = body.chem; + soul.vaginalAttachment = body.vaginalAttachment; + soul.chastityVagina = body.chastityVagina; + soul.chastityPenis = body.chastityPenis; + soul.chastityAnus = body.chastityAnus; + if (soul.custom && body.custom) { + soul.custom.tattoo = body.custom.tattoo; + } + soul.bellyTat = body.bellyTat; + soul.abortionTat = body.abortionTat; + soul.birthsTat = body.birthsTat; + soul.pubertyAgeXX = body.pubertyAgeXX; + soul.pubertyXX = body.pubertyXX; + soul.pubertyAgeXY = body.pubertyAgeXY; + soul.pubertyXY = body.pubertyXY; + soul.breedingMark = body.breedingMark; + soul.underArmHColor = body.underArmHColor; + soul.underArmHStyle = body.underArmHStyle; + soul.ballType = body.ballType; + soul.eggType = body.eggType; + soul.bald = body.bald; + soul.hormoneBalance = body.hormoneBalance; + soul.breastMesh = body.breastMesh; + soul.vasectomy = body.vasectomy; + soul.haircuts = body.haircuts; + soul.ovaryAge = body.ovaryAge; + soul.readyOva = body.readyOva; + soul.womb = body.womb; // this is array assigned by reference, if slave body that is ${body} will be still used anywhere in code (not discarded) — it's WRONG (they now technically share one womb object). Please tell me about it then. But if old body body just discarded — it's no problem then. + soul.pregAdaptation = body.pregAdaptation; + soul.geneMods = body.geneMods; + soul.NCSyouthening = body.NCSyouthening; + soul.eyebrowHColor = body.eyebrowHColor; + soul.eyebrowHStyle = body.eyebrowHStyle; + soul.eyebrowFullness = body.eyebrowFullness; + soul.wombImplant = body.wombImplant; + soul.ovaImplant = body.ovaImplant; + soul.geneticQuirks = body.geneticQuirks; + soul.albinismOverride = body.albinismOverride; + soul.clone = body.clone; + soul.cloneID = body.cloneID; + soul.inbreedingCoeff = body.inbreedingCoeff; + + soul.canRecruit = 0; + + if (!fromGenepool) { // swapping NOT gene pool records + soul.counter.publicUse = body.counter.publicUse; + soul.counter.laborCount = body.counter.laborCount; + soul.porn = body.porn; + soul.aphrodisiacs = body.aphrodisiacs; + soul.curatives = body.curatives; + soul.drugs = body.drugs; + soul.prestige = body.prestige; + soul.prestigeDesc = body.prestigeDesc; + soul.minorInjury = body.minorInjury; + soul.eyewear = body.eyewear; + soul.earwear = body.earwear; + soul.earPiercing = body.earPiercing; + soul.armAccessory = body.armAccessory; + soul.legAccessory = body.legAccessory; + soul.bellyAccessory = body.bellyAccessory; + soul.preg = body.preg; + soul.pregSource = body.pregSource; + soul.pregType = body.pregType; + soul.labor = body.labor; + soul.clitSetting = body.clitSetting; + soul.diet = body.diet; + soul.dietCum = body.dietCum; + soul.dietMilk = body.dietMilk; + soul.clothes = body.clothes; + soul.collar = body.collar; + soul.faceAccessory = body.faceAccessory; + soul.mouthAccessory = body.mouthAccessory; + soul.shoes = body.shoes; + soul.makeup = body.makeup; + soul.nails = body.nails; + soul.vaginalAccessory = body.vaginalAccessory; + soul.dickAccessory = body.dickAccessory; + soul.buttplug = body.buttplug; + soul.buttplugAttachment = body.buttplugAttachment; + soul.induce = body.induce; + soul.mpreg = body.mpreg; + soul.pregKnown = body.pregKnown; + soul.pregWeek = body.pregWeek; + soul.belly = body.belly; + soul.bellyPreg = body.bellyPreg; + soul.bellyFluid = body.bellyFluid; + soul.bellyImplant = body.bellyImplant; + soul.bellySag = body.bellySag; + soul.bellySagPreg = body.bellySagPreg; + soul.bellyPain = body.bellyPain; + soul.cervixImplant = body.cervixImplant; + soul.scar = body.scar; + soul.pregControl = body.pregControl; + deflate(soul); + } +}; +/** + * + * @param {App.Entity.SlaveState} soul + * @param {App.Entity.SlaveState} body + */ +globalThis.bodySwapName = function(soul, body) { + if (body.bodySwap === 0) { + if (body.birthSurname) { + if (body.birthName !== "") { + soul.origBodyOwner = SlaveFullBirthName(body); + } else { + soul.origBodyOwner = body.slaveName + " " + body.birthSurname; + } + } else if (body.birthName) { + if (body.slaveSurname) { + if ( + (V.surnameOrder !== 1 && ["Cambodian", "Chinese", "Hungarian", "Japanese", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes(body.nationality)) || + V.surnameOrder === 2 + ) { + soul.origBodyOwner = body.slaveSurname + " " + body.birthName; + } else { + soul.origBodyOwner = body.birthName + " " + body.slaveSurname; + } + } else { + soul.origBodyOwner = body.birthName; + } + } else if (body.slaveSurname) { + soul.origBodyOwner = SlaveFullName(body); + } else { + soul.origBodyOwner = body.slaveName; + } + } else { + soul.origBodyOwner = body.origBodyOwner; + } +}; + +/** + * + * @param {App.Entity.SlaveState} soul + * @returns {DocumentFragment} + */ +globalThis.bodySwapSelection = function(soul) { + const {him} = getPronouns(soul); + const el = new DocumentFragment(); + const cost = 10000; + App.UI.DOM.appendNewElement("div", el, `The surgeon awaits the pair of slaves to be strapped into the surgery. So far only ${soul.slaveName} is prepped:`, "scene-intro"); + App.UI.DOM.appendNewElement("div", el, `Select ${(V.seeExtreme) ? `an eligible slave (any slave who is not a fuckdoll)` : `a slave`} who will be trading bodies with ${him}. This operation will cost ${cashFormat(cost)}.`); + + for (const body of V.slaves) { + const slaveDiv = document.createElement("div"); + slaveDiv.append( + App.UI.DOM.link( + body.slaveName, + () => { + V.swappingSlave = body; + cashX(forceNeg(cost), "slaveSurgery", body); + }, + [], + "Slave Slave Swap" + ) + ); + const relTerm = relativeTerm(soul, body); + if (relTerm) { + slaveDiv.append(` ${relTerm}`); + } + if (body.relationshipTarget === soul.ID) { + const {wife} = getPronouns(body); + switch (body.relationship) { + case 1: + slaveDiv.append(` friends`); + break; + case 2: + slaveDiv.append(` best friends`); + break; + case 3: + slaveDiv.append(` friends with benefits`); + break; + case 4: + slaveDiv.append(` lover`); + break; + case 5: + slaveDiv.append(` slave${wife}`); + break; + } + } + if (body.rivalryTarget === soul.ID) { + switch (body.relationship) { + case 1: + slaveDiv.append(`dislikes`); + break; + case 2: + slaveDiv.append(`rival`); + break; + case 3: + slaveDiv.append(`bitterly hates`); + break; + } + } + el.append(slaveDiv); + } + + return el; +}; + +/** + * + * @param {App.Entity.SlaveState} body + * @returns {DocumentFragment} + */ +globalThis.huskSwapSelection = function(body) { + const el = new DocumentFragment(); + const cost = 10000; + App.UI.DOM.appendNewElement("div", el, `"This operation is neither simple nor is it perfected. There are extreme health risks involved and no guarantee of success. Strap a slave into your remote surgery to consent to the operation. Indentured servants${(V.incubator > 0) || (V.nurseryChildren) ? `and slaves with reserved children`:``} are not eligible." + `, "scene-intro"); + App.UI.DOM.appendNewElement("div", el, `Select the slave whose mind will be transferred into the waiting husk. Amputated slaves must not be wearing prosthetics. This operation will cost ${cashFormat(cost)}.`); + + for (const soul of V.slaves) { + if (isSlaveAvailable(soul)) { + if (soul.fuckdoll === 0) { + if (!hasAnyProstheticLimbs(soul)) { + if (soul.indenture === -1) { + if (soul.breedingMark === 0 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || V.arcologies[0].FSRestart === "unset") { + if (WombReserveCount(soul) === 0) { + if (soul.ID !== body.ID) { + App.UI.DOM.appendNewElement("div", el, + App.UI.DOM.link( + soul.slaveName, + () => { + V.swappingSlave = soul; + cashX(forceNeg(cost), "slaveSurgery", soul); + }, + [], + "Husk Slave Swap" + ) + ); + } + } + } + } + } + } + } + } + + return el; +}; diff --git a/src/pregmod/widgets/bodySwapReaction.tw b/src/npc/surgery/bodySwap/bodySwapReaction.tw similarity index 100% rename from src/pregmod/widgets/bodySwapReaction.tw rename to src/npc/surgery/bodySwap/bodySwapReaction.tw diff --git a/src/pregmod/huskSlaveSwap.tw b/src/npc/surgery/bodySwap/huskSlaveSwap.tw similarity index 98% rename from src/pregmod/huskSlaveSwap.tw rename to src/npc/surgery/bodySwap/huskSlaveSwap.tw index d945e7618334678a1f51cee0f76cd75ccfdc01cf..2c3fd583c71c2a1088d8f5e470758842ae28e4f6 100644 --- a/src/pregmod/huskSlaveSwap.tw +++ b/src/npc/surgery/bodySwap/huskSlaveSwap.tw @@ -5,7 +5,7 @@ <<set _m = $slaveIndices[$swappingSlave.ID]>> You strap $slaves[_m].slaveName, and the body to which $he will be transferred, into the remote surgery and stand back as it goes to work. -<<BodySwap $slaves[_m] $activeSlave>> +<<run bodySwap($slaves[_m], $activeSlave)>> <<set _gps = $genePool.findIndex(function(s) { return s.ID == $slaves[_m].ID; })>> <<set $genePool[_gps].race = $slaves[_m].race, $genePool[_gps].origRace = $slaves[_m].origRace, $genePool[_gps].skin = $slaves[_m].skin, $genePool[_gps].markings = $slaves[_m].markings, $genePool[_gps].eye.origColor = $slaves[_m].eye.origColor, $genePool[_gps].origHColor = $slaves[_m].origHColor, $genePool[_gps].origSkin = $slaves[_m].origSkin, $genePool[_gps].face = $slaves[_m].face, $genePool[_gps].pubicHStyle = $slaves[_m].pubicHStyle, $genePool[_gps].underArmHStyle = $slaves[_m].underArmHStyle, $genePool[_gps].eyebrowHStyle = $slaves[_m].eyebrowHStyle>> /* special exception to swap genePool since the temporary body lacks an entry. Otherwise we could just call the widget using the genePool entries */ diff --git a/src/pregmod/slaveSlaveSwap.tw b/src/npc/surgery/bodySwap/slaveSlaveSwap.tw similarity index 84% rename from src/pregmod/slaveSlaveSwap.tw rename to src/npc/surgery/bodySwap/slaveSlaveSwap.tw index 9ef4e1554ac05b70182a6b3ebeac1b2f7d65ee27..877285090c0dde7186286ae467153c850b8a9a7f 100644 --- a/src/pregmod/slaveSlaveSwap.tw +++ b/src/npc/surgery/bodySwap/slaveSlaveSwap.tw @@ -11,10 +11,10 @@ <<set _gps2Clone = clone($genePool[_gps2])>> You strap <<= getSlave($AS).slaveName>> and $swappingSlave.slaveName into the remote surgery and stand back as it goes to work. -<<BodySwap $slaves[_ss1] _ss2Clone>> -<<BodySwap $genePool[_gps1] _gps2Clone 1>> /* passing a third argument just to detect if it's a slave from the genepool */ -<<BodySwap $slaves[_ss2] _ss1Clone>> -<<BodySwap $genePool[_gps2] _gps1Clone 1>> /* passing a third argument just to detect if it's a slave from the genepool */ +<<run bodySwap($slaves[_ss1], _ss2Clone)>> +<<run bodySwap($genePool[_gps1], _gps2Clone, true)>> /* passing a third argument just to detect if it's a slave from the genepool */ +<<run bodySwap($slaves[_ss2], _ss1Clone)>> +<<run bodySwap($genePool[_gps2], _gps1Clone, true)>> /* passing a third argument just to detect if it's a slave from the genepool */ <br><br> After an honestly impressive procedure, $slaves[_ss1].slaveName is recovering nicely. @@ -49,8 +49,8 @@ In the neighboring bed, $slaves[_ss2].slaveName rests peacefully. /* now to handle whose body it is, name-wise */ -<<BodySwapName $slaves[_ss1] $slaves[_ss2]>> -<<BodySwapName $slaves[_ss2] $slaves[_ss1]>> +<<run bodySwapName($slaves[_ss1], $slaves[_ss2])>> +<<run bodySwapName($slaves[_ss2], $slaves[_ss1])>> <<if _ss1Clone.bodySwap > 0>> <<if $slaves[_ss1].origBodyOwnerID == $slaves[_ss1].ID>> diff --git a/src/pregmod/huskSlaveSwapWorkaround.tw b/src/pregmod/huskSlaveSwapWorkaround.tw deleted file mode 100644 index 112c4ccb092363baabaf74ad3793c93874941b34..0000000000000000000000000000000000000000 --- a/src/pregmod/huskSlaveSwapWorkaround.tw +++ /dev/null @@ -1,34 +0,0 @@ -:: husk Slave Swap Workaround [nobr] - -<<if $activeSlave.tankBaby != 3>> - <<set $nextButton = "Abort Operation", $nextLink = "Scheduled Event", $returnTo = "Scheduled Event">> -<<else>> - <<set $nextButton = "Abort Operation", $nextLink = "Main", $returnTo = "Incubator">> -<</if>> - -"This operation is neither simple nor is it perfected. There are extreme health risks involved and no guarantee of success. Strap a slave into your remote surgery to consent to the operation. Indentured servants<<if ($incubator > 0) || ($nurseryChildren)>> and slaves with reserved children<</if>> are not eligible." -<br> -//Select the slave whose mind will be transferred into the waiting husk. Amputated slaves must not be wearing prosthetics. This operation will cost <<print cashFormat(10000)>>.// - -<br><br> -__Select an eligible slave:__ -<<for _i = 0; _i < $slaves.length; _i++>> - <<capture _i>> - <<if isSlaveAvailable($slaves[_i])>> - <<if $slaves[_i].fuckdoll == 0>> - <<if !hasAnyProstheticLimbs($slaves[_i])>> - <<if $slaves[_i].indenture == -1>> - <<if $slaves[_i].breedingMark == 0 || $propOutcome == 0 || $eugenicsFullControl == 1 || $arcologies[0].FSRestart == "unset">> - <<if WombReserveCount($slaves[_i]) == 0>> - <<if $slaves[_i].ID != $AS>> - <<set _name = SlaveFullName($slaves[_i])>> - <br>[[_name|Husk Slave Swap][$swappingSlave = $slaves[_i], cashX(-10000, "slaveSurgery", $slaves[_i])]] - <</if>> - <</if>> - <</if>> - <</if>> - <</if>> - <</if>> - <</if>> - <</capture>> -<</for>> diff --git a/src/pregmod/seHuskSlaveDelivery.tw b/src/pregmod/seHuskSlaveDelivery.tw index 59a79704dd47a927e67d14874f6b0b11d46f4411..2d8d2c360284f90552626549d426556b457db320 100644 --- a/src/pregmod/seHuskSlaveDelivery.tw +++ b/src/pregmod/seHuskSlaveDelivery.tw @@ -73,7 +73,7 @@ A slave came in fitting the description you provided. <<if $cash >= $surgeryCost>> <<link "Accept the offered slave and contact the bodyswap surgeon.">> <<set $activeSlave = _husk>> - <<goto "husk Slave Swap Workaround">> + <<goto "Husk Slave Swap Workaround">> <</link>> //Will significantly increase the selected slave's upkeep.// <<else>> diff --git a/src/pregmod/slaveSlaveSwapWorkaround.tw b/src/pregmod/slaveSlaveSwapWorkaround.tw deleted file mode 100644 index 72ee529f98f13facdc558e6afab2ce7ee8578f93..0000000000000000000000000000000000000000 --- a/src/pregmod/slaveSlaveSwapWorkaround.tw +++ /dev/null @@ -1,22 +0,0 @@ -:: Slave Slave Swap Workaround [nobr] - -<<set $nextButton = "Abort Operation", $nextLink = "Main">> - -The surgeon awaits the pair of slaves to be strapped into the surgery. So far only <<= getSlave($AS).slaveName>> is prepped: -<br> -//Select the slave who will be trading bodies with $him. This operation will cost <<print cashFormat(10000)>>.// - -<br><br> -__Select an eligible slave:__ -<<for _i = 0; _i < $slaves.length; _i++>> - <<capture _i>> - <<if isSlaveAvailable($slaves[_i])>> - <<if $slaves[_i].fuckdoll == 0>> - <<if ($slaves[_i].ID != $AS)>> - <<set _name = SlaveFullName($slaves[_i])>> - <br>[[_name|Slave Slave Swap][$swappingSlave = $slaves[_i], cashX(-10000, "slaveSurgery", $slaves[_i])]] - <</if>> - <</if>> - <</if>> - <</capture>> -<</for>> \ No newline at end of file diff --git a/src/pregmod/widgets/bodyswapWidgets.tw b/src/pregmod/widgets/bodyswapWidgets.tw deleted file mode 100644 index bd5cd97a0e398f7caa1c0675d81c48b203c3fd86..0000000000000000000000000000000000000000 --- a/src/pregmod/widgets/bodyswapWidgets.tw +++ /dev/null @@ -1,259 +0,0 @@ -:: bodyswap widgets [nobr widget] - -/* It's too fucking big jesus christ */ - -/* first arg is slave getting swapped, second is body she is being swapped to. The second body's physical traits overwrite the originals */ -<<widget "BodySwap">> - -<<set WombInit($args[1])>> /*Just to be sure.*/ -<<set $args[0].genes = $args[1].genes>> -<<set $args[0].physicalAge = $args[1].physicalAge>> -<<set $args[0].visualAge = $args[1].visualAge>> -<<set $args[0].ageImplant = $args[1].ageImplant>> -<<set $args[0].health.condition = $args[1].health.condition>> -<<set $args[0].health.shortDamage = $args[1].health.shortDamage>> -<<set $args[0].health.longDamage = $args[1].health.longDamage>> -<<set $args[0].health.illness = $args[1].health.illness>> -<<set $args[0].health.tired = $args[1].health.tired>> -<<set $args[0].health.health = $args[1].health.health>> -<<set $args[0].weight = $args[1].weight>> -<<set $args[0].muscles = $args[1].muscles>> -<<set $args[0].height = $args[1].height>> -<<set $args[0].heightImplant = $args[1].heightImplant>> -<<set $args[0].race = $args[1].race>> -<<set $args[0].origRace = $args[1].origRace>> -<<set $args[0].pubicHColor = $args[1].pubicHColor>> -<<set $args[0].skin = $args[1].skin>> -<<set $args[0].origSkin = $args[1].origSkin>> -<<set $args[0].markings = $args[1].markings>> -<<set $args[0].eye = $args[1].eye>> -<<set $args[0].hears = $args[1].hears>> -<<set $args[0].earImplant = $args[1].earImplant>> -<<set $args[0].earShape = $args[1].earShape>> -<<set $args[0].earT = $args[1].earT>> -<<set $args[0].earTColor = $args[1].earTColor>> -<<set $args[0].smells = $args[1].smells>> -<<set $args[0].tastes = $args[1].tastes>> -<<set $args[0].horn = $args[1].horn>> -<<set $args[0].hornColor = $args[1].hornColor>> -<<set $args[0].PTail = $args[1].PTail>> -<<set $args[0].tail = $args[1].tail>> -<<set $args[0].tailShape = $args[1].tailShape>> -<<set $args[0].tailColor = $args[1].tailColor>> -<<set $args[0].origHColor = $args[1].origHColor>> -<<set $args[0].hColor = $args[1].hColor>> -<<set $args[0].hLength = $args[1].hLength>> -<<set $args[0].hStyle = $args[1].hStyle>> -<<set $args[0].pubicHStyle = $args[1].pubicHStyle>> -<<set $args[0].waist = $args[1].waist>> -<<set $args[0].corsetPiercing = $args[1].corsetPiercing>> -<<set $args[0].arm = $args[1].arm>> -<<set $args[0].leg = $args[1].leg>> -<<set $args[0].PLimb = $args[1].PLimb>> -<<set $args[0].readyProsthetics = $args[1].readyProsthetics>> -<<set $args[0].heels = $args[1].heels>> -<<set $args[0].voice = $args[1].voice>> -<<set $args[0].voiceImplant = $args[1].voiceImplant>> -<<set $args[0].shoulders = $args[1].shoulders>> -<<set $args[0].shouldersImplant = $args[1].shouldersImplant>> -<<set $args[0].boobs = $args[1].boobs>> -<<set $args[0].boobsImplant = $args[1].boobsImplant>> -<<set $args[0].boobsImplantType = $args[1].boobsImplantType>> -<<set $args[0].boobShape = $args[1].boobShape>> -<<set $args[0].nipples = $args[1].nipples>> -<<set $args[0].nipplesPiercing = $args[1].nipplesPiercing>> -<<set $args[0].nipplesAccessory = $args[1].nipplesAccessory>> -<<set $args[0].areolae = $args[1].areolae>> -<<set $args[0].areolaePiercing = $args[1].areolaePiercing>> -<<set $args[0].areolaeShape = $args[1].areolaeShape>> -<<set $args[0].boobsTat = $args[1].boobsTat>> -<<set $args[0].lactation = $args[1].lactation>> -<<set $args[0].lactationDuration = $args[1].lactationDuration>> -<<set $args[0].induceLactation = $args[1].induceLactation>> -<<set $args[0].boobsMilk = $args[1].boobsMilk>> -<<set $args[0].lactationAdaptation = $args[1].lactationAdaptation>> -<<set $args[0].hips = $args[1].hips>> -<<set $args[0].hipsImplant = $args[1].hipsImplant>> -<<set $args[0].butt = $args[1].butt>> -<<set $args[0].buttImplant = $args[1].buttImplant>> -<<set $args[0].buttImplantType = $args[1].buttImplantType>> -<<set $args[0].buttTat = $args[1].buttTat>> -<<set $args[0].face = $args[1].face>> -<<set $args[0].faceImplant = $args[1].faceImplant>> -<<set $args[0].faceShape = $args[1].faceShape>> -<<set $args[0].lips = $args[1].lips>> -<<set $args[0].lipsImplant = $args[1].lipsImplant>> -<<set $args[0].lipsPiercing = $args[1].lipsPiercing>> -<<set $args[0].lipsTat = $args[1].lipsTat>> -<<set $args[0].teeth = $args[1].teeth>> -<<set $args[0].tonguePiercing = $args[1].tonguePiercing>> -<<set $args[0].vagina = $args[1].vagina>> -<<set $args[0].vaginaLube = $args[1].vaginaLube>> -<<set $args[0].vaginaPiercing = $args[1].vaginaPiercing>> -<<set $args[0].vaginaTat = $args[1].vaginaTat>> -<<set $args[0].fertKnown = $args[1].fertKnown>> -<<set $args[0].fertPeak = $args[1].fertPeak>> -<<set $args[0].broodmother = $args[1].broodmother>> -<<set $args[0].broodmotherFetuses = $args[1].broodmotherFetuses>> -<<set $args[0].broodmotherOnHold = $args[1].broodmotherOnHold>> -<<set $args[0].broodmotherCountDown = $args[1].broodmotherCountDown>> -<<set $args[0].labia = $args[1].labia>> -<<set $args[0].clit = $args[1].clit>> -<<set $args[0].clitPiercing = $args[1].clitPiercing>> -<<set $args[0].dick = $args[1].dick>> -<<set $args[0].foreskin = $args[1].foreskin>> -<<set $args[0].anus = $args[1].anus>> -<<set $args[0].analArea = $args[1].analArea>> -<<set $args[0].dickPiercing = $args[1].dickPiercing>> -<<set $args[0].dickTat = $args[1].dickTat>> -<<set $args[0].prostate = $args[1].prostate>> -<<set $args[0].balls = $args[1].balls>> -<<set $args[0].scrotum = $args[1].scrotum>> -<<set $args[0].ovaries = $args[1].ovaries>> -<<set $args[0].anusPiercing = $args[1].anusPiercing>> -<<set $args[0].anusTat = $args[1].anusTat>> -<<set $args[0].brand = $args[1].brand>> -<<set $args[0].nosePiercing = $args[1].nosePiercing>> -<<set $args[0].eyebrowPiercing = $args[1].eyebrowPiercing>> -<<set $args[0].navelPiercing = $args[1].navelPiercing>> -<<set $args[0].shouldersTat = $args[1].shouldersTat>> -<<set $args[0].armsTat = $args[1].armsTat>> -<<set $args[0].legsTat = $args[1].legsTat>> -<<set $args[0].backTat = $args[1].backTat>> -<<set $args[0].stampTat = $args[1].stampTat>> -<<set $args[0].hormones = $args[1].hormones>> -<<set $args[0].chem = $args[1].chem>> -<<set $args[0].vaginalAttachment = $args[1].vaginalAttachment>> -<<set $args[0].chastityVagina = $args[1].chastityVagina>> -<<set $args[0].chastityPenis = $args[1].chastityPenis>> -<<set $args[0].chastityAnus = $args[1].chastityAnus>> -<<if (def $args[0].custom) && (def $args[1].custom)>> - <<set $args[0].custom.tattoo = $args[1].custom.tattoo>> -<</if>> -<<set $args[0].bellyTat = $args[1].bellyTat>> -<<set $args[0].abortionTat = $args[1].abortionTat>> -<<set $args[0].birthsTat = $args[1].birthsTat>> -<<set $args[0].pubertyAgeXX = $args[1].pubertyAgeXX>> -<<set $args[0].pubertyXX = $args[1].pubertyXX>> -<<set $args[0].pubertyAgeXY = $args[1].pubertyAgeXY>> -<<set $args[0].pubertyXY = $args[1].pubertyXY>> -<<set $args[0].breedingMark = $args[1].breedingMark>> -<<set $args[0].underArmHColor = $args[1].underArmHColor>> -<<set $args[0].underArmHStyle = $args[1].underArmHStyle>> -<<set $args[0].ballType = $args[1].ballType>> -<<set $args[0].eggType = $args[1].eggType>> -<<set $args[0].bald = $args[1].bald>> -<<set $args[0].hormoneBalance = $args[1].hormoneBalance>> -<<set $args[0].breastMesh = $args[1].breastMesh>> -<<set $args[0].vasectomy = $args[1].vasectomy>> -<<set $args[0].haircuts = $args[1].haircuts>> -<<set $args[0].ovaryAge = $args[1].ovaryAge>> -<<set $args[0].readyOva = $args[1].readyOva>> -<<set $args[0].womb = $args[1].womb>> /* this is array assigned by reference, if slave body that is $args[1] will be still used anywhere in code (not discarded) — it's WRONG (they now technically share one womb object). Please tell me about it then. But if old body $args[1] just discarded — it's no problem then.*/ -<<set $args[0].pregAdaptation = $args[1].pregAdaptation>> -<<set $args[0].geneMods = $args[1].geneMods>> -<<set $args[0].NCSyouthening = $args[1].NCSyouthening>> -<<set $args[0].eyebrowHColor = $args[1].eyebrowHColor>> -<<set $args[0].eyebrowHStyle = $args[1].eyebrowHStyle>> -<<set $args[0].eyebrowFullness = $args[1].eyebrowFullness>> -<<set $args[0].wombImplant = $args[1].wombImplant>> -<<set $args[0].ovaImplant = $args[1].ovaImplant>> -<<set $args[0].geneticQuirks = $args[1].geneticQuirks>> -<<set $args[0].albinismOverride = $args[1].albinismOverride>> -<<set $args[0].clone = $args[1].clone>> -<<set $args[0].cloneID = $args[1].cloneID>> -<<set $args[0].inbreedingCoeff = $args[1].inbreedingCoeff>> - -<<set $args[0].canRecruit = 0>> - -<<if $args[2] != 1>> /* swapping NOT gene pool records */ - <<set $args[0].counter.publicUse = $args[1].counter.publicUse>> - <<set $args[0].counter.laborCount = $args[1].counter.laborCount>> - <<set $args[0].porn = $args[1].porn>> - <<set $args[0].aphrodisiacs = $args[1].aphrodisiacs>> - <<set $args[0].curatives = $args[1].curatives>> - <<set $args[0].drugs = $args[1].drugs>> - <<set $args[0].prestige = $args[1].prestige>> - <<set $args[0].porn.viewerCount = $args[1].porn.viewerCount>> - <<set $args[0].porn.prestige = $args[1].porn.prestige>> - <<set $args[0].porn.prestigeDesc = $args[1].porn.prestigeDesc>> - <<set $args[0].prestigeDesc = $args[1].prestigeDesc>> - <<set $args[0].minorInjury = $args[1].minorInjury>> - <<set $args[0].eyewear = $args[1].eyewear>> - <<set $args[0].earwear = $args[1].earwear>> - <<set $args[0].earsPiercing = $args[1].earsPiercing>> - <<set $args[0].armAccessory = $args[1].armAccessory>> - <<set $args[0].legAccessory = $args[1].legAccessory>> - <<set $args[0].bellyAccessory = $args[1].bellyAccessory>> - <<set $args[0].preg = $args[1].preg>> - <<set $args[0].pregSource = $args[1].pregSource>> - <<set $args[0].pregType = $args[1].pregType>> - <<set $args[0].labor = $args[1].labor>> - <<set $args[0].clitSetting = $args[1].clitSetting>> - <<set $args[0].diet = $args[1].diet>> - <<set $args[0].dietCum = $args[1].dietCum>> - <<set $args[0].dietMilk = $args[1].dietMilk>> - <<set $args[0].clothes = $args[1].clothes>> - <<set $args[0].collar = $args[1].collar>> - <<set $args[0].faceAccessory = $args[1].faceAccessory>> - <<set $args[0].mouthAccessory = $args[1].mouthAccessory>> - <<set $args[0].shoes = $args[1].shoes>> - <<set $args[0].makeup = $args[1].makeup>> - <<set $args[0].nails = $args[1].nails>> - <<set $args[0].vaginalAccessory = $args[1].vaginalAccessory>> - <<set $args[0].dickAccessory = $args[1].dickAccessory>> - <<set $args[0].buttplug = $args[1].buttplug>> - <<set $args[0].buttplugAttachment = $args[1].buttplugAttachment>> - <<set $args[0].induce = $args[1].induce>> - <<set $args[0].mpreg = $args[1].mpreg>> - <<set $args[0].pregKnown = $args[1].pregKnown>> - <<set $args[0].pregWeek = $args[1].pregWeek>> - <<set $args[0].belly = $args[1].belly>> - <<set $args[0].bellyPreg = $args[1].bellyPreg>> - <<set $args[0].bellyFluid = $args[1].bellyFluid>> - <<set $args[0].bellyImplant = $args[1].bellyImplant>> - <<set $args[0].bellySag = $args[1].bellySag>> - <<set $args[0].bellySagPreg = $args[1].bellySagPreg>> - <<set $args[0].bellyPain = $args[1].bellyPain>> - <<set $args[0].cervixImplant = $args[1].cervixImplant>> - <<set $args[0].scar = $args[1].scar>> - <<set $args[0].pregControl = $args[1].pregControl>> - <<run deflate($args[0])>> -<</if>> -/* -<<if def $args[2]>> - <<= assignJob($args[0], "rest")>> -<</if>> -*/ - -<</widget>> - -<<widget "BodySwapName">> - -<<if $args[1].bodySwap == 0>> - <<if $args[1].birthSurname>> - <<if $args[1].birthName !== "">> - <<set $args[0].origBodyOwner = SlaveFullBirthName($args[1])>> - <<else>> - <<set $args[0].origBodyOwner = $args[1].slaveName + " " + $args[1].birthSurname>> - <</if>> - <<elseif $args[1].birthName>> - <<if $args[1].slaveSurname>> - <<if (($surnameOrder != 1 && ["Cambodian", "Chinese", "Hungarian", "Japanese", "Korean", "Mongolian", "Taiwanese", "Vietnamese"].includes($args[1].nationality)) || ($surnameOrder == 2)>> - <<set $args[0].origBodyOwner = $args[1].slaveSurname + " " + $args[1].birthName>> - <<else>> - <<set $args[0].origBodyOwner = $args[1].birthName + " " + $args[1].slaveSurname>> - <</if>> - <<else>> - <<set $args[0].origBodyOwner = $args[1].birthName>> - <</if>> - <<elseif $args[1].slaveSurname>> - <<set $args[0].origBodyOwner = SlaveFullName($args[1])>> - <<else>> - <<set $args[0].origBodyOwner = $args[1].slaveName>> - <</if>> -<<else>> - <<set $args[0].origBodyOwner = $args[1].origBodyOwner>> -<</if>> - -<</widget>> diff --git a/src/uncategorized/PESS.tw b/src/uncategorized/PESS.tw index 70a4da847f3a58574da59cb997c2c9f259681bba..b9dc856bf60e79a654c143613ec1a05110b16ed7 100644 --- a/src/uncategorized/PESS.tw +++ b/src/uncategorized/PESS.tw @@ -39,7 +39,7 @@ <</if>> <<else>> -<<set $nextButton = "Continue", $nextLink = "AS Dump", $returnTo = "RIE Eligibility Check">> +<<set $nextButton = "Continue", $nextLink = "AS Dump", $returnTo = "RIE Eligibility Check", _SL = $slaves.length>> /* special leadership events can be rolled on any eventSlave */ <<switch $PESSevent>> @@ -558,6 +558,9 @@ $He sees you examining at $him, and looks back at you submissively, too tired to $He giggles happily as you seize $his $activeSlave.skin wrist and pull $him towards where your other slaves are mostly already asleep. $He jokingly points out $slaves[$j].slaveName, fast asleep. You nod, and $activeSlave.slaveName pounces. $slaves[$j].slaveName wakes in terror and confusion to find _his2 head and neck pinned in a leg-lock that holds _his2 mouth hard against $activeSlave.slaveName's <<if canDoAnal($activeSlave)>>anus<<elseif canDoVaginal($activeSlave) && canPenetrate($activeSlave)>>cock and cunt<<elseif ($activeSlave.dick > 0) && !canAchieveErection($activeSlave)>>limp cock<<elseif ($activeSlave.dick > 0) && ($activeSlave.balls > 0) && ($activeSlave.scrotum > 0)>>cock and balls<<elseif ($activeSlave.clit > 0)>>huge, hard clit<<elseif !canDoVaginal($activeSlave)>>soft perineum<<else>>soaking-wet cunt<</if>>. $slaves[$j].slaveName only manages one kick of _his2 legs before you pin them and ram yourself up _his2 butt. _His2 howl of protest, directed against $activeSlave.slaveName's privates, sends a shiver through your Head Girl. <<if ($activeSlave.lips > 70)>>"Oh pleathe make _him2 moan, <<Master>>," $he lisps through $his huge lips.<<elseif ($activeSlave.lipsPiercing+$activeSlave.tonguePiercing > 2)>>"Oh pleathe make _him2 moan, <<Master>>," $he lisps through $his face full of piercings.<<else>>"I love it when you make _him2 moan, <<Master>>," $he groans.<</if>> Poor $slaves[$j].slaveName's asshole takes quite a beating before $activeSlave.slaveName finally convulses with @@.hotpink;naughty pleasure.@@ Meanwhile, all around the nighttime rape, slaves have been woken by $slaves[$j].slaveName's struggles. They realize how completely your Head Girl has @@.hotpink;involved $himself in your sexual pursuits@@ and @@.gold;lie as still as they can.@@ <<run seX($slaves[$j], "oral", $activeSlave, "penetrative")>> <<run seX($slaves[$j], "anal", $PC, "penetrative")>> + <<if $slaves[$j].anus == 0>> + <<set $slaves[$j].anus = 1>> + <</if>> <<for $i = 0; $i < _SL; $i++>> <<set $slaves[$i].devotion += 4, $slaves[$i].trust -= 4>> <</for>> diff --git a/src/uncategorized/repBudget.js b/src/uncategorized/repBudget.js new file mode 100644 index 0000000000000000000000000000000000000000..dbdc715633d06773cb7ba87f766e9fd105f1c354 --- /dev/null +++ b/src/uncategorized/repBudget.js @@ -0,0 +1,36 @@ +/** + * @param {string} category + * @param {string} title + * @returns {string} + */ +globalThis.budgetLine = function(category, title) { + let income; + let expenses; + + if (passage() === "Rep Budget") { + income = "lastWeeksRepIncome"; + expenses = "lastWeeksRepExpenses"; + + if (V[income][category] || V[expenses][category] || V.showAllEntries.repBudget) { + return `<tr>\ + <td>${title}</td>\ + <td>${repFormat(V[income][category])}</td>\ + <td>${repFormat(V[expenses][category])}</td>\ + <td>${repFormat(V[income][category] + V[expenses][category])}</td>\ + </tr>`; + } + } else if (passage() === "Costs Budget") { + income = "lastWeeksCashIncome"; + expenses = "lastWeeksCashExpenses"; + + if (V[income][category] || V[expenses][category] || V.showAllEntries.costsBudget) { + return `<tr>\ + <td>${title}</td>\ + <td>${cashFormatColor(V[income][category])}</td>\ + <td>${cashFormatColor(-Math.abs(V[expenses][category]))}</td>\ + <td>${cashFormatColor(V[income][category] + V[expenses][category])}</td>\ + </tr>`; + } + } + return ``; +};