diff --git a/js/rulesAssistant/conditionEditor.js b/js/rulesAssistant/conditionEditor.js index 88a6ac2ba08643609311ea0a8be4656789d9a5ac..e6f4ec6be0004e6857e6223bd5ec849d95752fb4 100644 --- a/js/rulesAssistant/conditionEditor.js +++ b/js/rulesAssistant/conditionEditor.js @@ -533,9 +533,9 @@ App.RA.Activation.Editor = (function() { } /** - * @typedef {"sub" | "div" | "eq" | "neq" | "gt" | "gte" | "lt" | "lte"| "substr"} RulePairComparators + * @typedef {"sub" | "div" | "eq" | "neq" | "gt" | "gte" | "lt" | "lte"| "substr" | "match"} RulePairComparators * @typedef {object} RulePairComparatorDisplay - * @property {string} key + * @property {RulePairComparators} key * @property {string} name * @type {RulePairComparatorDisplay[]} */ @@ -549,6 +549,7 @@ App.RA.Activation.Editor = (function() { {key: "sub", name: "-"}, {key: "div", name: "/"}, {key: "substr", name: "Contains"}, + {key: "match", name: "Matches"}, ]; class RulePair extends RuleContainer { @@ -604,7 +605,7 @@ App.RA.Activation.Editor = (function() { errorList.push("Both sides need to return the same type."); return "error"; } - } else if (this.mode === "substr") { + } else if (this.mode === "substr" || this.mode === "match") { if (this._child1.validate(errorList) === "string" && this._child2.validate(errorList) === "string") { return "number"; } else { @@ -1123,7 +1124,7 @@ App.RA.Activation.Editor = (function() { } /** - * @param {"sub" | "div" | "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "substr"} mode + * @param {"sub" | "div" | "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "substr" | "match"} mode */ function makePair(mode) { const pair = new RulePair(mode); @@ -1162,6 +1163,7 @@ App.RA.Activation.Editor = (function() { ["lt", () => makePair("lt")], ["lte", () => makePair("lte")], ["substr", () => makePair("substr")], + ["match", () => makePair("match")], ["not", () => { const negate = new RuleNegate(); negate.child = stack.popRulePart(); diff --git a/js/rulesAssistant/conditionEvaluation.js b/js/rulesAssistant/conditionEvaluation.js index c2c2afca4d6ece627ba74ed9480857c2667875c4..e3c50573c7529e77a4f7cab7d70c471bd788ca74 100644 --- a/js/rulesAssistant/conditionEvaluation.js +++ b/js/rulesAssistant/conditionEvaluation.js @@ -656,15 +656,28 @@ App.RA.Activation.populateGetters = function() { // Strings gm.addString("label", {name: "Label", description: "Assigned Label", val: c => c.slave.custom.label}); gm.addString("genes", { - name: "Sex", description: "Genetic sex: Male: XX, Female: XY", + name: "Genetic sex", description: "Genetic sex: Female: XX, Male: XY", val: c => c.slave.genes }); + gm.addString("phenotypesex", { + name: "Phenotype sex", + description: "A three-character string that encodes state of sexual organs" + + " as female('X'), male('Y'), both ('H'), or absent ('-'): vagina/dick, ovaries/balls, tits", + val: c => phenotypeSex(c.slave) + }); gm.addString("fetish", { name: "Fetish", description: "One of 'buttslut', 'cumslut', 'masochist', 'sadist', 'dom', 'submissive', 'boobs', " + "'pregnancy', 'none' (AKA vanilla)", val: c => c.slave.fetish }); + gm.addString("title", { + name: "Title", + description: "Slave title (class) without adjectives (slavegirl, MILF, bimbo, shemale, herm, etc.). ", + requirements: "The alternate titles game option is enabled", + enabled: () => V.newDescriptions === 1, + val: c => SlaveTitle(c.slave, false, false) + }); }; /** @@ -750,6 +763,10 @@ App.RA.Activation.evaluate = function(slave, rule) { const value = stack.popString(); stack.pushBoolean(stack.popString().includes(value)); }], + ["match", () => { + const value = stack.popString(); + stack.pushBoolean(stack.popString().match(value) !== null); + }], ["not", () => stack.pushBoolean(stack.popNumber() === 0)], ["ternarystr", () => { const ifFalse = stack.popString(); diff --git a/src/futureSocieties/futureSociety.js b/src/futureSocieties/futureSociety.js index ef28cb60a6c758131e22069205cd3437a83f2d57..a6245aa4d737ae9bc05f36cefe8b1f83065ea8fa 100644 --- a/src/futureSocieties/futureSociety.js +++ b/src/futureSocieties/futureSociety.js @@ -28,6 +28,7 @@ globalThis.FutureSocieties = (function() { HighestDecoration: FSHighestDecoration, arcSupport: arcSupport, researchAvailable, + isActive, }; /** get the list of FSes active for a particular arcology @@ -810,4 +811,14 @@ globalThis.FutureSocieties = (function() { function researchAvailable(fs, arcology) { return (arcology || V.arcologies[0])[`FS${fs}Research`] === 1; } + + /** + * Checks if the given FS active (i.e. not "unset") + * @param {FC.FutureSociety} fs + * @param {FC.ArcologyState} [arcology] Arcology to test, defaults to the PC's arcology + * @returns {boolean} + */ + function isActive(fs, arcology) { + return (arcology || V.arcologies[0])[fs] !== "unset"; + } })(); diff --git a/src/gui/Encyclopedia/encyclopediaRAActivationEditor.js b/src/gui/Encyclopedia/encyclopediaRAActivationEditor.js index 5fa62bc7cac8f4e6b6381f63d5ccd45a69fa8c02..6347e8031d5b2a1d93336bb8f52cdd6c80913738 100644 --- a/src/gui/Encyclopedia/encyclopediaRAActivationEditor.js +++ b/src/gui/Encyclopedia/encyclopediaRAActivationEditor.js @@ -68,6 +68,8 @@ App.Encyclopedia.addArticle("RA Condition Editor", function() { transformerRow(el, "/", "Divides the second value by the first value", "Number"); transformerRow(el, "Contains", "True, if the second value is somewhere in the first value", "String"); + transformerRow(el, "Matches", "True, if the first value matches the regular expression in the second value", + "String"); transformerRow(el, "Not …", "Negates the input value.", "Boolean"); transformerRow(el, "If … Then … Else …", "If the first value is true, returns the second value, otherwise the third value. The second " + diff --git a/src/js/statsChecker/statsChecker.js b/src/js/statsChecker/statsChecker.js index 781a6287e8d5ec70199e231f7d5e571115768236..c1c4133cd75ee5f024dcb0424c8d5a1576470d7c 100644 --- a/src/js/statsChecker/statsChecker.js +++ b/src/js/statsChecker/statsChecker.js @@ -628,7 +628,7 @@ globalThis.isFertile = function(slave) { }; /** - * @param {App.Entity.SlaveState | App.Entity.PlayerState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.canAchieveErection = function(slave) { @@ -653,7 +653,7 @@ globalThis.canAchieveErection = function(slave) { }; /** - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.canPenetrate = function(slave) { @@ -674,7 +674,7 @@ globalThis.canPenetrate = function(slave) { }; /** - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.canSee = function(slave) { @@ -686,7 +686,7 @@ globalThis.canSee = function(slave) { /** * - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.canSeePerfectly = function(slave) { @@ -705,7 +705,7 @@ globalThis.canSeePerfectly = function(slave) { }; /** - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.canHear = function(slave) { @@ -716,7 +716,7 @@ globalThis.canHear = function(slave) { }; /** - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.canSmell = function(slave) { @@ -727,7 +727,7 @@ globalThis.canSmell = function(slave) { }; /** - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.canTaste = function(slave) { @@ -738,7 +738,7 @@ globalThis.canTaste = function(slave) { }; /** - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.canHold = function(slave) { @@ -751,7 +751,7 @@ globalThis.canHold = function(slave) { }; /** If a slave can walk, she can move and stand. - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.canWalk = function(slave) { @@ -780,7 +780,7 @@ globalThis.canWalk = function(slave) { }; /** If a slave can stand, she can move, but not necessarily walk. - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.canStand = function(slave) { @@ -968,7 +968,7 @@ globalThis.canDoVaginal = function(slave) { }; /** - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.tooFatSlave = function(slave) { @@ -987,7 +987,7 @@ globalThis.tooFatSlave = function(slave) { }; /** - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.tooBigBreasts = function(slave) { @@ -1006,7 +1006,7 @@ globalThis.tooBigBreasts = function(slave) { }; /** - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.tooBigBelly = function(slave) { @@ -1025,7 +1025,7 @@ globalThis.tooBigBelly = function(slave) { }; /** - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.tooBigBalls = function(slave) { @@ -1042,7 +1042,7 @@ globalThis.tooBigBalls = function(slave) { }; /** - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.tooBigDick = function(slave) { @@ -1059,7 +1059,7 @@ globalThis.tooBigDick = function(slave) { }; /** - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {boolean} */ globalThis.tooBigButt = function(slave) { @@ -1131,6 +1131,10 @@ globalThis.canBeReceptrix = function(slave) { ); }; +/** + * @param {FC.HumanState} slave + * @returns {string} + */ globalThis.milkFlavor = function(slave) { if (slave.milkFlavor === "none") { return ``; diff --git a/src/js/utilsAssessSlave.js b/src/js/utilsAssessSlave.js index 39bf59bcf3e96d9e135c1cacd80d7d6ca0f88c9f..d3233de8c6d292b48cd90f55c44e926894e034e3 100644 --- a/src/js/utilsAssessSlave.js +++ b/src/js/utilsAssessSlave.js @@ -120,7 +120,7 @@ globalThis.maxHeight = function(slave) { if (slave.geneticQuirks.neoteny === 2 && slave.physicalAge > 12) { /* Limit neoteny slaves to 12 year old max height */ max = Math.clamp(((Height.mean(slave.nationality, slave.race, slave.genes, 12) * 1.25) + slave.heightImplant * 10), 0, 274); } - + if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism !== 2) { max = Math.min(max, 160); } @@ -249,7 +249,7 @@ globalThis.canMoveToRoom = function(slave) { }; /** - * @param {App.Entity.SlaveState} slave + * @param {FC.HumanState} slave * @returns {0|1|2|3} 0: No heel boost at all. 1: Pumps, slight boost. 2: High heels. 3: Painfully/extreme high heels */ globalThis.shoeHeelCategory = function(slave) { diff --git a/src/js/utilsSlave.js b/src/js/utilsSlave.js index 1b13711a4f918af6d3f017b5fee947edacc6b559..e99c9c32cbdf351467137e6631fae0a86ce00f7b 100644 --- a/src/js/utilsSlave.js +++ b/src/js/utilsSlave.js @@ -2169,13 +2169,15 @@ globalThis.PoliteRudeTitle = function(slave) { /** * @param {App.Entity.SlaveState} slave + * @param {boolean} [adjective=true] Add adjectives + * @param {boolean} [variability=true] Use alternative titles * @returns {string} */ -globalThis.SlaveTitle = function(slave) { +globalThis.SlaveTitle = function(slave, adjective = true, variability = true) { 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) { + if (variability && jsRandom(1, 100) > 50) { r = "futanari"; } else { r = "herm"; @@ -2236,6 +2238,10 @@ globalThis.SlaveTitle = function(slave) { r = "slave"; } + if (!adjective) { + return r; + } + if (slave.visualAge < 13) { if (slave.actualAge < 3) { if (slave.actualAge < 1) { @@ -2548,6 +2554,20 @@ globalThis.SlaveTitle = function(slave) { return r; }; +/** + * Phenotype sex: a three-character string that encodes state of sexual organs + * as female('X'), male('Y'), both ('H'), or absent ('-'): vagina/dick, ovaries/balls, tits + * @param {FC.HumanState} human + * @returns {string} + */ +globalThis.phenotypeSex = function(human) { + const encode = (haveFemale, haveMale) => + '-XYH'[haveFemale + haveMale * 2]; + return encode(human.vagina > -1, human.dick > 0) + + encode(human.ovaries > 0, human.balls > 0) + + encode(human.boobs > 0, false); +}; + /** * @param {App.Entity.SlaveState} slave */