From 831772ae5e85ba1ca18481f61fe0dd68c24080c6 Mon Sep 17 00:00:00 2001 From: lowercasedonkey <lowercasedonkey@gmail.com> Date: Wed, 23 Dec 2020 17:06:28 -0500 Subject: [PATCH] move more functions to various files --- src/js/utilsArcology.js | 48 ++++++ src/js/utilsFC.js | 328 ---------------------------------------- src/js/utilsSlave.js | 53 ++++++- src/js/utilsSlaves.js | 228 ++++++++++++++++++++++++++++ 4 files changed, 328 insertions(+), 329 deletions(-) create mode 100644 src/js/utilsSlaves.js diff --git a/src/js/utilsArcology.js b/src/js/utilsArcology.js index 4e75e1ee3ed..65f41139081 100644 --- a/src/js/utilsArcology.js +++ b/src/js/utilsArcology.js @@ -45,3 +45,51 @@ App.Utils.schoolCounter = function() { App.Utils.schoolFailure = function() { return Array.from(App.Data.misc.schools.keys()).find(s => V[s].schoolPresent && V[s].schoolProsperity <= -10); }; + +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/utilsFC.js b/src/js/utilsFC.js index 0592144c545..ca3efee3bb2 100644 --- a/src/js/utilsFC.js +++ b/src/js/utilsFC.js @@ -117,53 +117,6 @@ globalThis.arrayToSentence = function(array) { return array.reduce((res, ch, i, arr) => res + (i === arr.length - 1 ? ' and ' : ', ') + ch); }; -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>} @@ -196,34 +149,6 @@ App.Utils.scheduleSidebarRefresh = (function() { 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.alphabetizeIterable = function(iterable) { const compare = function(a, b) { let aTitle = a.toLowerCase(); @@ -255,200 +180,6 @@ App.Utils.alphabetizeIterable = function(iterable) { return clonedArray.sort(compare); }; -/** - * @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); -}; - -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}; -}; - globalThis.initRules = function() { const rule = emptyDefaultRule(); rule.name = "Obedient Slaves"; @@ -460,62 +191,3 @@ 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); -}; diff --git a/src/js/utilsSlave.js b/src/js/utilsSlave.js index 51c864ea895..9d055f0f8b2 100644 --- a/src/js/utilsSlave.js +++ b/src/js/utilsSlave.js @@ -3467,4 +3467,55 @@ globalThis.generateSlaveID = function() { V.IDNumber++; } return V.IDNumber++; -}; \ No newline at end of file +}; + +/** + * @param {number} ID + * @returns {App.Entity.SlaveState} + */ +globalThis.slaveStateById = function(ID) { + const index = V.slaveIndices[ID]; + return index === undefined ? null : V.slaves[index]; +}; + +/** + * @param {number} ID + * @returns {App.Entity.SlaveState} + */ +globalThis.getSlave = function(ID) { + const index = V.slaveIndices[ID]; + return index === undefined ? undefined : V.slaves[index]; +}; + +globalThis.getChild = function(ID) { + return V.cribs.find(s => s.ID === ID); +}; + +/** + * 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); +}; diff --git a/src/js/utilsSlaves.js b/src/js/utilsSlaves.js new file mode 100644 index 00000000000..ff68e668055 --- /dev/null +++ b/src/js/utilsSlaves.js @@ -0,0 +1,228 @@ +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; + }, {}); +}; -- GitLab