diff --git a/src/js/utilsAssaySlave.js b/src/js/utilsAssaySlave.js new file mode 100644 index 0000000000000000000000000000000000000000..d9a4ff083bacaefda9c947af7ed9600396bcd902 --- /dev/null +++ b/src/js/utilsAssaySlave.js @@ -0,0 +1,410 @@ +/** + * @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"; + } +}; + +/** + * @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; +}; + +/** + * @param {string} targetSkill - Skill to be checked. + * @param {Object} slave - Slave to be checked. + * @param {number} [skillIncrease=1] + * @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; + }; + + 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.`; + } + + 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)}.`; + } + + 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)}.`; + } + + 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)}.`; + } + + 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 (isleadershipRole() && slave.skill[targetSkill] + skillIncrease >= 100) { + V.tutorGraduate.push(slave.ID); + V.slaveTutor[capFirstChar(targetSkill)].delete(slave.ID); + } + slave.skill[targetSkill] += skillIncrease; + return r; +}; + +/** + * 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; +}; diff --git a/src/js/utilsFC.js b/src/js/utilsFC.js index 59ed3ac912bb4d544253d6a78bf53cbed248abdb..9196be050320aac0c5aa41d3fa6c4a1b576d1b26 100644 --- a/src/js/utilsFC.js +++ b/src/js/utilsFC.js @@ -512,3 +512,112 @@ globalThis.initRules = function() { V.defaultRules = [rule]; V.rulesToApplyOnce = {}; }; + +/** @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); +}; + +/** + * 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); + + function canBeARapeRival(s) { + return (s.devotion <= 95 && s.energy <= 95 && !s.rivalry && !s.fuckdoll && s.fetish !== "mindbroken"); + } + + 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 (typeof predicate !== 'function') { + predicate = (() => true); + } + + const arr = V.slaves.filter((s) => { return canBeARapeRival(s) && canRape(slave, s); }).shuffle(); + return arr.find(predicate); +}; + +/** + * @param {getBestSlavesParams} info + * @returns {number[]} + */ +globalThis.getBestSlavesIDs = function(info) { + return getBestSlaves(info).map(slave => slave.ID); +}; + +/* +//Example +getBestSlaves({part:"butt", count: 5}); +getBestSlaves({part:"boobs"});//defaults to top 3 +getBestSlaves({part:"dick", smallest:true, filter:(slave)=>slave.dick > 0});//defaults to top 3 +getBestSlaves({part:slave=>slave.intelligence+slave.intelligenceImplant}); +*/ + +/** + * Generates a new slave ID that is guaranteed to be unused + * @returns {number} slave ID + */ +globalThis.generateSlaveID = function() { + // household liquidators and recETS generate slaves at an offset of 1000 (and many such slaves already exist) + // if you go through enough slaves you WILL generate collisions, so make sure we haven't just done that. + let allSlaveIDs = [...V.slaves.map((s) => s.ID), ...V.tanks.map((s) => s.ID), ...V.cribs.map((s) => s.ID)]; + while (allSlaveIDs.includes(V.IDNumber)) { + V.IDNumber++; + } + 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; + } + } + } +}; + +/** + * @param {App.Entity.SlaveState} A + * @param {App.Entity.SlaveState} B + * @returns {boolean} + */ +globalThis.sameAssignmentP = function(A, B) { + return A.assignment === B.assignment; +}; diff --git a/src/js/utilsSlave.js b/src/js/utilsSlave.js index 34fbfd2d6b5a576ee2ed668724c51dc9305fb653..02f8e60df6fb86c4ca77de3a71bab6278a839d50 100644 --- a/src/js/utilsSlave.js +++ b/src/js/utilsSlave.js @@ -909,119 +909,6 @@ As a categorizer <</if>> <<print $cats.muscleCat.cat(_Slave.muscles)>> */ -/** - * @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"; - } -}; - -/** - * @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.pronounReplacer = function(slavetext) { switch (slavetext) { @@ -1484,6 +1371,7 @@ globalThis.pronounReplacer = function(slavetext) { } return slavetext; }; + globalThis.convertCareer = function(slave) { let job = slave.career; if ((V.diversePronouns === 1) && (slave.pronoun === App.Data.Pronouns.Kind.male)) { @@ -1587,151 +1475,10 @@ globalThis.convertCareer = function(slave) { }; /** - * @param {string} targetSkill - Skill to be checked. - * @param {Object} slave - Slave to be checked. - * @param {number} [skillIncrease=1] - * @returns {string} + * + * @param {App.Entity.SlaveState} slave + * @returns {string|null} */ -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.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.`; - } - - 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)}.`; - } - - 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)}.`; - } - - 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)}.`; - } - - 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 (isleadershipRole() && slave.skill[targetSkill] + skillIncrease >= 100) { - V.tutorGraduate.push(slave.ID); - V.slaveTutor[capFirstChar(targetSkill)].delete(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]; @@ -1742,6 +1489,11 @@ globalThis.tutorForSlave = function(slave) { 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)) { @@ -2136,125 +1888,6 @@ globalThis.moreNational = function(nation) { return country; }; -/** - * 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 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); - - function canBeARapeRival(s) { - return (s.devotion <= 95 && s.energy <= 95 && !s.rivalry && !s.fuckdoll && s.fetish !== "mindbroken"); - } - - 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 (typeof predicate !== 'function') { - predicate = (() => true); - } - - 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); -}; - -/* -//Example -getBestSlaves({part:"butt", count: 5}); -getBestSlaves({part:"boobs"});//defaults to top 3 -getBestSlaves({part:"dick", smallest:true, filter:(slave)=>slave.dick > 0});//defaults to top 3 -getBestSlaves({part:slave=>slave.intelligence+slave.intelligenceImplant}); -*/ - -/** - * Generates a new slave ID that is guaranteed to be unused - * @returns {number} slave ID - */ -globalThis.generateSlaveID = function() { - // household liquidators and recETS generate slaves at an offset of 1000 (and many such slaves already exist) - // if you go through enough slaves you WILL generate collisions, so make sure we haven't just done that. - let allSlaveIDs = [...V.slaves.map((s) => s.ID), ...V.tanks.map((s) => s.ID), ...V.cribs.map((s) => s.ID)]; - while (allSlaveIDs.includes(V.IDNumber)) { - V.IDNumber++; - } - 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; - } - } - } -}; - /** Deflate a slave (reset inflation to none) * @param {App.Entity.SlaveState} slave */ @@ -2267,100 +1900,6 @@ globalThis.deflate = function(slave) { SetBellySize(slave); }; -/** - * 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} 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. @@ -2546,23 +2085,6 @@ globalThis.newSlave = function(slave) { } }; -/** 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} @@ -2595,18 +2117,6 @@ globalThis.fetishChangeChance = function(slave) { 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} @@ -3428,6 +2938,9 @@ globalThis.DegradingName = function(slave) { slave.slaveSurname = surname; }; +/** + * @param {App.Entity.SlaveState} slave + */ globalThis.PaternalistName = function(slave) { if (slave.slaveName.search("Miss") === -1) { if (slave.slaveName.search("Ms.") === -1) { @@ -3444,6 +2957,11 @@ globalThis.PaternalistName = function(slave) { } }; +/** + * + * @param {App.Entity.SlaveState} parent + * @param {App.Entity.SlaveState} child + */ globalThis.parentNames = function(parent, child) { const slaves = V.slaves; @@ -3736,21 +3254,3 @@ globalThis.ageSlave = function(slave, forceDevelopment = false) { 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; -};