diff --git a/devTools/types/FC/RA.d.ts b/devTools/types/FC/RA.d.ts index d7fbd09d62bf3e47f32332b909a959b9b1b1d2b1..37ee9b6b548fe1ca808df724b1d2ca3c97402208 100644 --- a/devTools/types/FC/RA.d.ts +++ b/devTools/types/FC/RA.d.ts @@ -5,6 +5,11 @@ declare namespace FC { val: number; } + interface ExpressiveNumericTarget { + cond: string; + val: string | number; + } + interface NumericRange { min: number; max: number; @@ -47,11 +52,11 @@ declare namespace FC { } interface RuleGrowthSetters { - boobs: NumericTarget; - butt: NumericTarget; - lips: NumericTarget; - dick: NumericTarget; - balls: NumericTarget; + boobs: ExpressiveNumericTarget; + butt: ExpressiveNumericTarget; + lips: ExpressiveNumericTarget; + dick: ExpressiveNumericTarget; + balls: ExpressiveNumericTarget; intensity: number; } diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js index 1523f20d3a9b7eeb740d5d086e719b16ce08289a..76b215d039868c3e5f79d982b7c32668b8dd90d8 100644 --- a/js/003-data/gameVariableData.js +++ b/js/003-data/gameVariableData.js @@ -1188,7 +1188,8 @@ App.Data.resetOnNGPlus = { food: 0, animalOvaries: 0, dinnerParty: 0, - reportMissingClothing: 0 + reportMissingClothing: 0, + raGrowthExpr: 0 }, NaNArray: [], diff --git a/src/gui/options/options.js b/src/gui/options/options.js index 037fc71801063b67c1f78870b5f0f2af9bc0372f..489a617626f2c9b3e5610caff3bd340970f5f4b7 100644 --- a/src/gui/options/options.js +++ b/src/gui/options/options.js @@ -1000,6 +1000,10 @@ App.UI.optionsPassage = function() { .addValue("Enabled", 1).on().addValue("Disabled", 0).off(); } + options.addOption("Rules Assistant target growth expressions", "raGrowthExpr", V.experimental) + .addValue("Enabled", 1).on().addValue("Disabled", 0).off() + .addComment("This will allow javascript expressions to be used in the Rules Assistant growth target setters."); + options.addOption("New event", "tempEventToggle") .addValue("Enabled", 1).on().addValue("Disabled", 0).off(); diff --git a/src/js/DefaultRules.js b/src/js/DefaultRules.js index 4ec6ae7de1b2090b828ef9584a62f941b7b22814..4d128718a86385122d4a74b6111cb7de0958de04 100644 --- a/src/js/DefaultRules.js +++ b/src/js/DefaultRules.js @@ -1173,7 +1173,15 @@ globalThis.DefaultRules = (function() { r += `<br>${slave.slaveName} is on ${slave.drugs} and will not be considered for drug enhancement until that regime is complete.`; ProcessOtherDrugs(slave, rule); return; - } else if (rule.growth.boobs === null && rule.growth.butt === null && rule.growth.lips === null && rule.growth.dick === null && rule.growth.balls === null) { + } else if ( + [ + rule.growth.boobs, + rule.growth.butt, + rule.growth.lips, + rule.growth.dick, + rule.growth.balls + ].every(r => r === null) // Check if all objects in list equal null + ){ ProcessOtherDrugs(slave, rule); return; } @@ -1264,7 +1272,7 @@ globalThis.DefaultRules = (function() { /** * @param {App.Entity.SlaveState} slave * @param {DrugTarget} asset - * @param {FC.RA.NumericTarget} target + * @param {FC.RA.NumericTarget | FC.RA.ExpressiveNumericTarget} target * @param {{drug: FC.Drug, weight: number}[]} priorities * @param {number} step */ @@ -1273,6 +1281,14 @@ globalThis.DefaultRules = (function() { return; } + if (V.experimental.raGrowthExpr === 1 && typeof target.val === 'string') { + const interpreter = new Function("slave", "return (" + target.val + ");"); + target.val = runWithReadonlyProxy(() => interpreter(createReadonlyProxy(slave))); + if (V.debugMode) { + console.log(asset + " expression for '" + slave.slaveName + "' resolves to " + target.val.toString()); + } + } + const flesh = fleshFunc[asset](slave); if (growDrugs[asset] !== null && App.RA.shallGrow(flesh, target, step) && maxAssetSize[asset] > slave[asset]) { priorities.push({ diff --git a/src/js/rulesAssistantOptions.js b/src/js/rulesAssistantOptions.js index 1404d02b08e6d834c27505be5df6c77e063d3e54..c71f8ae59cc0be5ee1d33b969eede4b14304546a 100644 --- a/src/js/rulesAssistantOptions.js +++ b/src/js/rulesAssistantOptions.js @@ -887,6 +887,73 @@ App.RA.options = (function() { } } + // Basically just a copy of NumericTargetEditor modified to handle strings as well + class ExpressiveNumericTargetEditor extends EditorWithShortcuts { + /** + * @param {string} prefix + * @param {Array} [data=[]] + * @param {boolean} [allowNullValue=true] + * @param {number} [min=0] + * @param {number} [max=100] + * @param {boolean} [spinBox=false] + */ + constructor(prefix, data = [], allowNullValue = true, min = 0, max = 100, spinBox = false) { + super(prefix, data, allowNullValue, spinBox, true, min, max); + } + + createEditor(min, max) { + function makeOp(op, ui) { + return {op: op, ui: ui}; + } + this.opSelector = document.createElement("select"); + for (const o of [makeOp('==', '='), makeOp('>=', "⩾"), makeOp('<=', '⩽'), makeOp('>', '>'), makeOp('<', '<')]) { + let opt = document.createElement("option"); + opt.textContent = o.ui; + opt.value = o.op; + this.opSelector.appendChild(opt); + } + this.opSelector.classList.add("rajs-list"); + this.opSelector.onchange = () => { + this.inputEdited(); + }; + + this.numEditor = document.createElement("input"); + this.numEditor.type = "text"; + this.numEditor.classList.add("rajs-value"); + this.numEditor.onblur = () => { + this.inputEdited(); + }; + this.numEditor.onkeypress = (e) => { + if (returnP(e)) { this.inputEdited(); } + }; + + const res = document.createElement("span"); + res.appendChild(this.opSelector); + res.appendChild(this.numEditor); + return res; + } + + setTextValue(what) { + if (typeof what === 'number') { + this.numEditor.value = what.toString(); + } else if (typeof what === 'string') { + this.numEditor.value = what; + } else if (what === null) { + this.numEditor.value = null; + this.opSelector.value = '=='; + } else if (typeof what === 'object') { + this.opSelector.value = what.cond; + this.numEditor.value = what.val; + } + } + + getTextData() { + const n = this.numEditor.value !== "" ? Number(this.numEditor.value) : Number.NaN; // Attempt to convert numEditor.value to number, + const v = isNaN(n) ? this.numEditor.value : Math.floor(n); // return numEditor.value as number if !NaN (should result in realValue being of number) + return v === null || v === "" ? null : { cond: this.opSelector.value, val: v }; + } + } + // a way to organize lists with too many elements in subsections // children are bound to the master list class ListSubSection extends ElementWithLabel { @@ -2072,14 +2139,25 @@ App.RA.options = (function() { ]; pairs.forEach(pair => this.appendChild(new OptionsItem(...pair))); - this.breasts = new BreastGrowthList(); - this.butts = new ButtGrowthList(); - this.lips = new LipGrowthList(); + if (!V.experimental.raGrowthExpr) { + this.breasts = new BreastGrowthList(); + this.butts = new ButtGrowthList(); + this.lips = new LipGrowthList(); + } else { + this.breasts = new ExprBreastGrowthList(); + this.butts = new ExprButtGrowthList(); + this.lips = new ExprLipGrowthList(); + } this.sublists.push(this.breasts, this.butts, this.lips); if (V.seeDicks > 0 || V.makeDicks > 0) { - this.dicks = new DickGrowthList(); - this.balls = new BallGrowthList(); + if (!V.experimental.raGrowthExpr) { + this.dicks = new DickGrowthList(); + this.balls = new BallGrowthList(); + } else { + this.dicks = new ExprDickGrowthList(); + this.balls = new ExprBallGrowthList(); + } this.sublists.push(this.dicks, this.balls); } } @@ -2212,6 +2290,78 @@ App.RA.options = (function() { } } + class ExprBreastGrowthList extends ExpressiveNumericTargetEditor { + constructor() { + const pairs = [ + ["B-Cup", 350], + ["D-Cup", 1000], + ["monstrous", 9000], + ["unlimited", 48000], + ["none", 0] + ]; + super("Breasts", pairs, true, 0, 48000, true); + this.setValue(current_rule.set.growth.boobs); + this.onchange = (value) => current_rule.set.growth.boobs = value; + } + } + + class ExprButtGrowthList extends ExpressiveNumericTargetEditor { + constructor() { + const pairs = [ + ["cute", 2], + ["big", 4], + ["huge", 6], + ["unlimited", 20], + ["none", 0] + ]; + super("Butts", pairs, true, 0, 20, true); + this.setValue(current_rule.set.growth.butt); + this.onchange = (value) => current_rule.set.growth.butt = value; + } + } + + class ExprLipGrowthList extends ExpressiveNumericTargetEditor { + constructor() { + const pairs = [ + ["plump", 25], + ["beestung", 45], + ["facepussy", 100], + ["none", 0] + ]; + super("Lips", pairs, true, 0, 100, true); + this.setValue(current_rule.set.growth.lips); + this.onchange = (value) => current_rule.set.growth.lips = value; + } + } + + class ExprDickGrowthList extends ExpressiveNumericTargetEditor { + constructor() { + const pairs = [ + ["above average", 4], + ["pornstar", 6], + ["unlimited", 30], + ["none", 0] + ]; + super("Dicks, if present", pairs, true, 0, 30, true); + this.setValue(current_rule.set.growth.dick); + this.onchange = (value) => current_rule.set.growth.dick = value; + } + } + + class ExprBallGrowthList extends ExpressiveNumericTargetEditor { + constructor() { + const pairs = [ + ["sizable", 4], + ["cumslave", 6], + ["unlimited", 125], + ["none", 0] + ]; + super("Balls, if present", pairs, true, 0, 125, true); + this.setValue(current_rule.set.growth.balls); + this.onchange = (value) => current_rule.set.growth.balls = value; + } + } + class CurativesList extends ListSelector { constructor() { const pairs = [