diff --git a/devTools/types/FC/RA.d.ts b/devTools/types/FC/RA.d.ts index 4d3c721f97802bd107db6e0269d05abb7a500bf2..17c7e6cf48ed8b4698cff3d982dec85682ed5e15 100644 --- a/devTools/types/FC/RA.d.ts +++ b/devTools/types/FC/RA.d.ts @@ -237,5 +237,13 @@ declare namespace FC { } type PostFixRule = Array<string | number | boolean> + + interface SlaveActions { + sizingDrug?: Drug; + } + + interface Actions { + slaves: Record<number, SlaveActions>; + } } } diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js index fc390fb52f12b4640bdc172a143a4a927d6b5f15..5f3b16b062ca900630adea8bc754990bbf58a939 100644 --- a/js/003-data/gameVariableData.js +++ b/js/003-data/gameVariableData.js @@ -1512,6 +1512,12 @@ App.Data.resetOnNGPlus = { * @type {FC.Loan[]} */ loans: [], + /** + * @type {FC.RA.Actions} + */ + RAActions: { + slaves:{} + }, }; // The keys of this object are a whitelist of options that are set by the player at game start. diff --git a/src/004-base/000-proxies.js b/src/004-base/000-proxies.js index 9738e68bf063ed42b792f6cf905465fcf22ba7f9..066191496ab5841082c8d5f463c4b31c3303c3a3 100644 --- a/src/004-base/000-proxies.js +++ b/src/004-base/000-proxies.js @@ -106,12 +106,17 @@ })(); Object.defineProperty(window, "V", { get: function() { - if (window.storyProxy != null) { return window.storyProxy; } - return State.variables; + return window.storyProxy ?? State.variables; } }); -// This should be used if the user might use V under normal, non-cheating circumstances but shouldn't be punished for accidentally setting the value. The attempt to make the change will simply be disregarded. + +/** + * This should be used if the user might use V under normal, non-cheating circumstances but shouldn't be punished for accidentally setting the value. The attempt to make the change will simply be disregarded. + * @template {Function} F + * @param {F} callback + * @returns {ReturnType<F>} + */ globalThis.runWithReadonlyProxy = function(callback) { globalThis.storyProxy = createReadonlyProxy(State.variables); try { @@ -120,7 +125,13 @@ globalThis.runWithReadonlyProxy = function(callback) { globalThis.storyProxy = null; } }; -// This should be used if setting values would constitute cheating. For example, a debug view that shows all variables in an editable form; showing isn't cheating, but making a change would be. + +/** + * This should be used if setting values would constitute cheating. For example, a debug view that shows all variables in an editable form; showing isn't cheating, but making a change would be. + * @template {Function} F + * @param {F} callback + * @returns {ReturnType<F>} + */ globalThis.runWithCheatProxy = function(callback) { globalThis.storyProxy = createCheatProxy(State.variables); try { diff --git a/src/js/DefaultRules.js b/src/js/DefaultRules.js index 39b5000482de2333ce4bc9d8808fe0e53bca53fe..ebce434bf6a4dcc3f24de854eb8b489969aba0ca 100644 --- a/src/js/DefaultRules.js +++ b/src/js/DefaultRules.js @@ -1229,10 +1229,10 @@ globalThis.DefaultRules = function(slave, options) { return; } - // Asset Growth - const growthDrugs = new Set(["breast injections", "breast redistributors", "butt injections", "butt redistributors", "hyper breast injections", "hyper butt injections", "hyper penis enhancement", "hyper testicle enhancement", "intensive breast injections", "intensive butt injections", "intensive penis enhancement", "intensive testicle enhancement", "lip atrophiers", "lip injections", "penis atrophiers", "penis enhancement", "testicle atrophiers", "testicle enhancement", "clitoris enhancement", "intensive clitoris enhancement"]); + // Asset growth/shrink + const sizingDrugs = new Set(["breast injections", "breast redistributors", "butt injections", "butt redistributors", "hyper breast injections", "hyper butt injections", "hyper penis enhancement", "hyper testicle enhancement", "intensive breast injections", "intensive butt injections", "intensive penis enhancement", "intensive testicle enhancement", "lip atrophiers", "lip injections", "penis atrophiers", "penis enhancement", "testicle atrophiers", "testicle enhancement", "clitoris enhancement", "intensive clitoris enhancement"]); - // WARNING: property names in growDrugs, and shrinkDrugs must be identical and this fact is used by the drugs() below + // NOTE: property names in growDrugs, and shrinkDrugs must be identical and this fact is used by the drugs() below /** @type {Record<FC.SizableBodyPart, FC.Drug>} */ const growDrugs = { lips: "lip injections", @@ -1359,6 +1359,7 @@ globalThis.DefaultRules = function(slave, options) { const action = priorities.reduce((acc, cur) => (acc.weight > cur.weight) ? acc : cur); if (slave.drugs !== action.drug) { slave.drugs = action.drug; + actionsObjectForSlave(slave, true).sizingDrug = action.drug; let m = `${slave.slaveName} has been put on ${slave.drugs}, since `; if (action.drug.startsWith("intensive")) { m += `${he}'s healthy enough to take them, and `; @@ -1368,23 +1369,26 @@ globalThis.DefaultRules = function(slave, options) { if (!isNaN(action.weight)) { m += `${Math.trunc(action.weight * 100)}% `; } - if (action.weight < 1) { - m += "below "; - } else { - m += "above "; - } + m += action.weight < 1 ? "below " : "above "; m += "the targeted size."; } else { m += `that is the only part of ${his} body that does not meet the targeted size.`; } message(m, action.source); } - } else if (slave.drugs !== rule.drug) { - if (growthDrugs.has(slave.drugs)) { - message(`${slave.slaveName}'s body has met all relevant growth targets, so ${his} pharmaceutical regime has been ended.`, sourceRecord.drug); - if (rule.drug === null) { - slave.drugs = "no drugs"; - message(`${slave.slaveName} has been defaulted to ${slave.drugs}`, sourceRecord.drug); + } else { + const actionsObj = actionsObjectForSlave(slave, false); + if (slave.drugs !== rule.drug && actionsObj?.sizingDrug === slave.drugs) { + if (sizingDrugs.has(slave.drugs)) { + message(`${slave.slaveName}'s body has met all relevant growth targets, so ${his} pharmaceutical regime has been ended.`, sourceRecord.drug); + if (rule.drug === null) { + slave.drugs = Drug.NONE; + message(`${slave.slaveName} has been defaulted to ${slave.drugs}`, sourceRecord.drug); + } + } + if (actionsObj) { + delete actionsObj.sizingDrug; + removeActionsObjectIfEmpty(slave); } } ProcessOtherDrugs(slave, rule); @@ -1400,19 +1404,19 @@ globalThis.DefaultRules = function(slave, options) { if (rule.drug !== undefined && rule.drug !== null && slave.drugs !== rule.drug) { let flag = true; switch (rule.drug) { - case "anti-aging cream": + case Drug.ANTIAGE: if (V.arcologies[0].FSYouthPreferentialistResearch !== 1 || slave.visualAge < 18) { flag = false; } break; - case "growth stimulants": + case Drug.GROWTHSTIM: if (V.growthStim !== 1 || !canImproveHeight(slave)) { flag = false; } break; - case "sag-B-gone": + case Drug.SAGBGONE: if (V.purchasedSagBGone !== 1 || (!(slave.boobs > 250 && slave.boobShape !== "saggy"))) { flag = false; } @@ -1430,150 +1434,150 @@ globalThis.DefaultRules = function(slave, options) { } break; - case "psychosuppressants": + case Drug.PSYCHOSUPP: if (!(slave.intelligence > -100 && slave.indentureRestrictions < 1)) { flag = false; } break; - case "psychostimulants": + case Drug.PSYCHOSTIM: if (V.arcologies[0].FSSlaveProfessionalismResearch !== 1 || !canImproveIntelligence(slave)) { flag = false; } break; - case "breast injections": + case Drug.GROWBREAST: if (slave.boobs >= 50000) { flag = false; } break; - case "hyper breast injections": + case Drug.HYPERBREAST: if (V.arcologies[0].FSAssetExpansionistResearch !== 1 || slave.boobs >= 50000) { flag = false; } break; - case "nipple enhancers": + case Drug.GROWNIPPLE: if (!(["inverted", "partially inverted", "cute", "tiny", "puffy", "flat"].includes(slave.nipples))) { flag = false; } break; - case "breast redistributors": + case Drug.REDISTBREAST: if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || (slave.boobs - slave.boobsImplant <= 100)) { flag = false; } break; - case "butt injections": + case Drug.GROWBUTT: if (slave.butt >= 9) { flag = false; } break; - case "hyper butt injections": + case Drug.HYPERBUTT: if (V.arcologies[0].FSAssetExpansionistResearch !== 1 || slave.butt >= 20) { flag = false; } break; - case "nipple atrophiers": + case Drug.ATROPHYNIPPLE: if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || !(["cute", "huge", "puffy"].includes(slave.nipples))) { flag = false; } break; - case "butt redistributors": + case Drug.REDISTBUTT: if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.butt - slave.buttImplant <= 0) { flag = false; } break; - case "lip injections": + case Drug.GROWLIP: if (!(slave.lips <= 95 || (slave.lips <= 85 && V.seeExtreme !== 1))) { flag = false; } break; - case "lip atrophiers": + case Drug.ATROPHYLIP: if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.lips - slave.lipsImplant <= 0) { flag = false; } break; - case "super fertility drugs": + case Drug.SUPERFERTILITY: if ((V.seeHyperPreg !== 1 || V.superFertilityDrugs !== 1) || !(slave.indentureRestrictions < 1 && (slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || !FutureSocieties.isActive('FSRestart')))) { flag = false; } break; - case "penis enhancement": + case Drug.GROWPENIS: if (!slave.dick.isBetween(0, 10)) { flag = false; } break; - case "clitoris enhancement": + case Drug.GROWCLIT: if (slave.clit >= 5) { flag = false; } break; - case "hyper penis enhancement": + case Drug.HYPERPENIS: if (V.arcologies[0].FSAssetExpansionistResearch !== 1 || !((slave.dick.isBetween(0, 31)) || slave.clit < 5)) { flag = false; } - break; + break; - case "intensive clitoris enhancement": + case Drug.INTENSIVECLIT: if (slave.clit >= 5) { flag = false; } break; - case "penis atrophiers": + case Drug.ATROPHYPENIS: if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.dick <= 1) { flag = false; } break; - case "testicle enhancement": + case Drug.GROWTESTICLE: if (slave.balls <= 0) { flag = false; } break; - case "hyper testicle enhancement": + case Drug.HYPERTESTICLE: if (V.arcologies[0].FSAssetExpansionistResearch !== 1 || slave.balls <= 0) { flag = false; } break; - case "testicle atrophiers": + case Drug.ATROPHYTESTICLE: if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.balls <= 1) { flag = false; } break; - case "clitoris atrophiers": + case Drug.ATROPHYCLIT: if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.clit <= 0) { flag = false; } break; - case "labia atrophiers": + case Drug.ATROPHYLABIA: if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.labia <= 0) { flag = false; } break; - case "appetite suppressors": + case Drug.APPETITESUPP: if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.weight <= -95) { flag = false; } break; - case "priapism agents": + case Drug.PRIAPISM: if (slave.dick === 0 || slave.dick > 10 || slave.chastityPenis === 1 || (canAchieveErection(slave))) { flag = false; } @@ -3454,6 +3458,28 @@ globalThis.DefaultRules = function(slave, options) { } r += text; } + + /** + * @param {FC.SlaveState} slave + * @param {boolean} create + * @returns {FC.RA.SlaveActions} + */ + function actionsObjectForSlave(slave, create) { + const res = V.RAActions.slaves[slave.ID]; + if (res || !create) { + return res; + } + V.RAActions.slaves[slave.ID] = {}; + return V.RAActions.slaves[slave.ID]; + } + + function removeActionsObjectIfEmpty(slave) { + const obj = V.RAActions.slaves[slave.ID]; + if (!obj) {return;} + if (Object.keys(obj).length === 0) { + delete V.RAActions.slaves[slave.ID]; + } + } }; diff --git a/src/js/removeSlave.js b/src/js/removeSlave.js index 7d641d1c156749a3876a8ddcce902013b4e4d832..9d4572dc1d1425a89d1da114fc4b58adf60665f8 100644 --- a/src/js/removeSlave.js +++ b/src/js/removeSlave.js @@ -273,5 +273,7 @@ globalThis.removeSlave = function(slave) { V.slaveIndices = slaves2indices(); LENGTH--; V.JobIDMap = makeJobIdMap(); /* need to call this once more to update count of resting slaves*/ + + delete V.RAActions.slaves[slave.ID]; } };