@dicelette/core 1.22.2 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,47 @@ This README documents the public API exported by the `core` package. It lists ty
8
8
 
9
9
  The `core` module provides small utilities to parse and evaluate dice notation, to generate and replace statistical values in dice expressions, and to validate statistical templates. The API is intended to be consumed by higher-level modules (bot, CLI, etc.).
10
10
 
11
+ ## Pity System
12
+
13
+ The pity system is an optional feature that automatically re-rolls dice when a comparison fails, ensuring successful outcomes. This is useful in role-playing games where certain rolls must succeed (e.g., a critical save action).
14
+
15
+ ### How It Works
16
+
17
+ When the `pity` parameter is set to `true` in the `roll()` function and the dice expression includes a comparison operator (e.g., `1d6>=5`), the system:
18
+
19
+ 1. Evaluates the initial roll against the comparison condition
20
+ 2. If the condition is **not met**, automatically re-rolls and repeats until success
21
+ 3. Tracks the number of re-rolls in the `pityLogs` field of the result
22
+
23
+ ### Limitations
24
+
25
+ The pity system only activates when a **comparison is theoretically possible**. For example:
26
+
27
+ - `1d6>=7` — **Ignored** (impossible: maximum roll is 6)
28
+ - `1d6>=5` — **Active** (possible: can roll 5 or 6)
29
+ - `2d6>=12` — **Active** (possible: can roll 12)
30
+ - `1d20>20` — **Ignored** (impossible: maximum roll is 20, not greater than 20)
31
+
32
+ ### Example
33
+
34
+ ```javascript
35
+ const result = roll("1d6>=5", null, true);
36
+ // If first roll is 3, pity system re-rolls
37
+ // If second roll is 2, pity system re-rolls again
38
+ // If third roll is 6, comparison succeeds and stops
39
+ // result.total = 6
40
+ // result.pityLogs = 2 (number of re-rolls)
41
+ ```
42
+
43
+ Without pity (`pity = false` or `undefined`), a roll of `1d6>=5` would return the result regardless of comparison outcome, with no re-rolls.
44
+
45
+ ### Usage Notes
46
+
47
+ - The pity system only applies when the roll includes a comparison operator
48
+ - The system has a safety limit of 100 re-rolls to prevent infinite loops
49
+ - The `pityLogs` field is only present in the result if at least one re-roll occurred
50
+ - Impossible comparisons are silently ignored (no error thrown)
51
+
11
52
  ## Public API
12
53
 
13
54
  Note: when a parameter `engine` is shown it usually defaults to the `NumberGenerator.engines.nodeCrypto` engine (from `@dice-roller/rpg-dice-roller`) unless otherwise specified.
@@ -21,6 +62,7 @@ Note: when a parameter `engine` is shown it usually defaults to the `NumberGener
21
62
  - compare?: ComparedValue — Optional comparison attached to the roll
22
63
  - modifier?: Modifier — Optional modifier applied to the roll
23
64
  - total?: number — Optional numeric total of the roll
65
+ - pityLogs?: number — Optional count of re-rolls triggered by the pity system (see Pity System below)
24
66
 
25
67
  #### Interface: Compare
26
68
  - sign: "<" | ">" | ">=" | "<=" | "=" | "!=" | "=="
@@ -112,9 +154,10 @@ Note: when a parameter `engine` is shown it usually defaults to the `NumberGener
112
154
 
113
155
  ### Dice functions (`src/dice.ts`)
114
156
 
115
- #### Function: roll(dice: string, engine?: Engine | null): Resultat | undefined
157
+ #### Function: roll(dice: string, engine?: Engine | null, pity?: boolean): Resultat | undefined
116
158
  - Parse a dice notation string and perform the roll(s) using `rpg-dice-roller`.
117
159
  - Supports comments, grouped/shared rolls, comparisons, modifiers and custom notation (see module docs).
160
+ - If `pity` is `true` and the roll includes a comparison (e.g., `1d6>=5`), automatically re-rolls until the comparison succeeds (see Pity System below).
118
161
  - Returns `Resultat` when a dice expression is recognized, otherwise `undefined`.
119
162
  - Throws `DiceTypeError` when the expression cannot be parsed or rolled.
120
163
 
package/dist/index.d.mts CHANGED
@@ -13,8 +13,10 @@ declare function createCriticalCustom(dice: string, customCritical: CustomCritic
13
13
  /**
14
14
  * Parse the string provided and turn it as a readable dice for dice parser
15
15
  * @param dice {string}
16
+ * @param engine
17
+ * @param pity
16
18
  */
17
- declare function roll(dice: string, engine?: Engine | null): Resultat | undefined;
19
+ declare function roll(dice: string, engine?: Engine | null, pity?: boolean): Resultat | undefined;
18
20
  /**
19
21
  * Evaluate a formula and replace "^" by "**" if any
20
22
  * @param {Sign} sign
@@ -84,6 +86,7 @@ interface Resultat {
84
86
  * Total of the roll
85
87
  */
86
88
  total?: number;
89
+ pityLogs?: number;
87
90
  }
88
91
  interface Compare {
89
92
  /**
@@ -94,6 +97,11 @@ interface Compare {
94
97
  * Value of the comparison
95
98
  */
96
99
  value: number;
100
+ /**
101
+ * Indicate if the comparison is "trivial"
102
+ * aka if the comparaison is always true or always false
103
+ */
104
+ trivial?: boolean;
97
105
  }
98
106
  /**
99
107
  * Sign format for calculation of modifier
@@ -355,13 +363,16 @@ declare function getEngine(engine: "nativeMath" | "browserCrypto" | "nodeCrypto"
355
363
  * Verify if the provided dice work with random value
356
364
  * @param testDice {string}
357
365
  * @param allStats {Record<string,number>}
366
+ * @param engine
367
+ * @param pity
358
368
  */
359
- declare function evalStatsDice(testDice: string, allStats?: Record<string, number>, engine?: Engine | null): string;
369
+ declare function evalStatsDice(testDice: string, allStats?: Record<string, number>, engine?: Engine | null, pity?: boolean): string;
360
370
  /**
361
371
  * Generate a random dice and remove the formula (+ evaluate it)
362
372
  * Used for diceDamage only
363
373
  * @param value {string}
364
374
  * @param template {StatisticalTemplate}
375
+ * @param engine
365
376
  * @returns
366
377
  */
367
378
  declare function diceRandomParse(value: string, template: StatisticalTemplate, engine?: Engine | null): string;
@@ -369,6 +380,7 @@ declare function diceRandomParse(value: string, template: StatisticalTemplate, e
369
380
  * Same as damageDice but for DiceType
370
381
  * @param dice {string}
371
382
  * @param template {StatisticalTemplate}
383
+ * @param engine
372
384
  */
373
385
  declare function diceTypeRandomParse(dice: string, template: StatisticalTemplate, engine?: Engine | null): string;
374
386
  /**
@@ -387,17 +399,20 @@ declare function evalOneCombinaison(combinaison: string, stats: Record<string, n
387
399
  * Parse the provided JSON and verify each field to check if everything could work when rolling
388
400
  * @param {unknown} template
389
401
  * @param {boolean} verify - If true, will roll the dices to check if everything is valid
402
+ * @param engine
390
403
  * @returns {StatisticalTemplate}
391
404
  */
392
405
  declare function verifyTemplateValue(template: unknown, verify?: boolean, engine?: Engine | null): StatisticalTemplate;
393
406
  /**
394
407
  * Test each damage roll from the template.damage
395
408
  * @param {StatisticalTemplate} template
409
+ * @param engine
396
410
  */
397
411
  declare function testDiceRegistered(template: StatisticalTemplate, engine?: Engine | null): void;
398
412
  /**
399
413
  * Test all combinaison with generated random value
400
414
  * @param {StatisticalTemplate} template
415
+ * @param engine
401
416
  */
402
417
  declare function testStatCombinaison(template: StatisticalTemplate, engine?: Engine | null): void;
403
418
  /**
@@ -405,6 +420,7 @@ declare function testStatCombinaison(template: StatisticalTemplate, engine?: Eng
405
420
  * @param {number|undefined} total
406
421
  * @param {number | undefined} max
407
422
  * @param {number | undefined} min
423
+ * @param engine
408
424
  * @returns
409
425
  */
410
426
  declare function generateRandomStat(total?: number | undefined, max?: number, min?: number, engine?: Engine | null): number;
package/dist/index.d.ts CHANGED
@@ -13,8 +13,10 @@ declare function createCriticalCustom(dice: string, customCritical: CustomCritic
13
13
  /**
14
14
  * Parse the string provided and turn it as a readable dice for dice parser
15
15
  * @param dice {string}
16
+ * @param engine
17
+ * @param pity
16
18
  */
17
- declare function roll(dice: string, engine?: Engine | null): Resultat | undefined;
19
+ declare function roll(dice: string, engine?: Engine | null, pity?: boolean): Resultat | undefined;
18
20
  /**
19
21
  * Evaluate a formula and replace "^" by "**" if any
20
22
  * @param {Sign} sign
@@ -84,6 +86,7 @@ interface Resultat {
84
86
  * Total of the roll
85
87
  */
86
88
  total?: number;
89
+ pityLogs?: number;
87
90
  }
88
91
  interface Compare {
89
92
  /**
@@ -94,6 +97,11 @@ interface Compare {
94
97
  * Value of the comparison
95
98
  */
96
99
  value: number;
100
+ /**
101
+ * Indicate if the comparison is "trivial"
102
+ * aka if the comparaison is always true or always false
103
+ */
104
+ trivial?: boolean;
97
105
  }
98
106
  /**
99
107
  * Sign format for calculation of modifier
@@ -355,13 +363,16 @@ declare function getEngine(engine: "nativeMath" | "browserCrypto" | "nodeCrypto"
355
363
  * Verify if the provided dice work with random value
356
364
  * @param testDice {string}
357
365
  * @param allStats {Record<string,number>}
366
+ * @param engine
367
+ * @param pity
358
368
  */
359
- declare function evalStatsDice(testDice: string, allStats?: Record<string, number>, engine?: Engine | null): string;
369
+ declare function evalStatsDice(testDice: string, allStats?: Record<string, number>, engine?: Engine | null, pity?: boolean): string;
360
370
  /**
361
371
  * Generate a random dice and remove the formula (+ evaluate it)
362
372
  * Used for diceDamage only
363
373
  * @param value {string}
364
374
  * @param template {StatisticalTemplate}
375
+ * @param engine
365
376
  * @returns
366
377
  */
367
378
  declare function diceRandomParse(value: string, template: StatisticalTemplate, engine?: Engine | null): string;
@@ -369,6 +380,7 @@ declare function diceRandomParse(value: string, template: StatisticalTemplate, e
369
380
  * Same as damageDice but for DiceType
370
381
  * @param dice {string}
371
382
  * @param template {StatisticalTemplate}
383
+ * @param engine
372
384
  */
373
385
  declare function diceTypeRandomParse(dice: string, template: StatisticalTemplate, engine?: Engine | null): string;
374
386
  /**
@@ -387,17 +399,20 @@ declare function evalOneCombinaison(combinaison: string, stats: Record<string, n
387
399
  * Parse the provided JSON and verify each field to check if everything could work when rolling
388
400
  * @param {unknown} template
389
401
  * @param {boolean} verify - If true, will roll the dices to check if everything is valid
402
+ * @param engine
390
403
  * @returns {StatisticalTemplate}
391
404
  */
392
405
  declare function verifyTemplateValue(template: unknown, verify?: boolean, engine?: Engine | null): StatisticalTemplate;
393
406
  /**
394
407
  * Test each damage roll from the template.damage
395
408
  * @param {StatisticalTemplate} template
409
+ * @param engine
396
410
  */
397
411
  declare function testDiceRegistered(template: StatisticalTemplate, engine?: Engine | null): void;
398
412
  /**
399
413
  * Test all combinaison with generated random value
400
414
  * @param {StatisticalTemplate} template
415
+ * @param engine
401
416
  */
402
417
  declare function testStatCombinaison(template: StatisticalTemplate, engine?: Engine | null): void;
403
418
  /**
@@ -405,6 +420,7 @@ declare function testStatCombinaison(template: StatisticalTemplate, engine?: Eng
405
420
  * @param {number|undefined} total
406
421
  * @param {number | undefined} max
407
422
  * @param {number | undefined} min
423
+ * @param engine
408
424
  * @returns
409
425
  */
410
426
  declare function generateRandomStat(total?: number | undefined, max?: number, min?: number, engine?: Engine | null): number;
package/dist/index.js CHANGED
@@ -61,17 +61,47 @@ module.exports = __toCommonJS(index_exports);
61
61
  // src/dice.ts
62
62
  var import_rpg_dice_roller = require("@dice-roller/rpg-dice-roller");
63
63
  var import_mathjs = require("mathjs");
64
- function getCompare(dice, compareRegex, engine = import_rpg_dice_roller.NumberGenerator.engines.nodeCrypto) {
65
- if (dice.match(/((\{.*,(.*)+\}|([><=!]+\d+f))[><=!]+\d+\}?)|\{(.*)([><=!]+).*\}/))
64
+ function isTrivialComparison(maxValue, minValue, compare) {
65
+ const canSucceed = canComparisonSucceed(maxValue, compare, minValue);
66
+ const canFail = canComparisonFail(maxValue, compare, minValue);
67
+ return !canSucceed || !canFail;
68
+ }
69
+ function canComparisonFail(maxRollValue, compare, minRollValue = 1) {
70
+ switch (compare.sign) {
71
+ case ">":
72
+ return minRollValue <= compare.value;
73
+ // failure if roll <= value
74
+ case ">=":
75
+ return minRollValue < compare.value;
76
+ // failure if roll < value
77
+ case "<":
78
+ return maxRollValue >= compare.value;
79
+ // failure if roll >= value
80
+ case "<=":
81
+ return maxRollValue > compare.value;
82
+ // failure if roll > value
83
+ case "=":
84
+ case "==":
85
+ return minRollValue !== compare.value || maxRollValue !== compare.value;
86
+ // can differ
87
+ case "!=":
88
+ return minRollValue <= compare.value && compare.value <= maxRollValue;
89
+ // equality possible
90
+ default:
91
+ return true;
92
+ }
93
+ }
94
+ function getCompare(dice, compareRegex, engine = import_rpg_dice_roller.NumberGenerator.engines.nodeCrypto, pity) {
95
+ if (dice.match(/((\{.*,(.*)+\}|([><=!]+\d+f))([><=]|!=)+\d+\}?)|\{(.*)(([><=]|!=)+).*\}/))
66
96
  return { dice, compare: void 0 };
67
97
  dice = dice.replace(SIGN_REGEX_SPACE, "");
68
98
  let compare;
69
- const calc = compareRegex[1];
99
+ const calc = compareRegex[2];
70
100
  const sign = calc.match(/[+-/*^]/)?.[0];
71
101
  const compareSign = compareRegex[0].match(SIGN_REGEX)?.[0];
72
102
  if (sign) {
73
103
  const toCalc = calc.replace(SIGN_REGEX, "").replace(/\s/g, "").replace(/;(.*)/, "");
74
- const rCompare = rollCompare(toCalc, engine);
104
+ const rCompare = rollCompare(toCalc, engine, pity);
75
105
  const total = (0, import_mathjs.evaluate)(rCompare.value.toString());
76
106
  dice = dice.replace(SIGN_REGEX_SPACE, `${compareSign}${total}`);
77
107
  compare = {
@@ -81,7 +111,7 @@ function getCompare(dice, compareRegex, engine = import_rpg_dice_roller.NumberGe
81
111
  rollValue: rCompare.diceResult
82
112
  };
83
113
  } else {
84
- const rcompare = rollCompare(calc, engine);
114
+ const rcompare = rollCompare(calc, engine, pity);
85
115
  compare = {
86
116
  sign: compareSign,
87
117
  value: rcompare.value,
@@ -91,11 +121,19 @@ function getCompare(dice, compareRegex, engine = import_rpg_dice_roller.NumberGe
91
121
  }
92
122
  return { dice, compare };
93
123
  }
94
- function rollCompare(value, engine = import_rpg_dice_roller.NumberGenerator.engines.nodeCrypto) {
124
+ function rollCompare(value, engine = import_rpg_dice_roller.NumberGenerator.engines.nodeCrypto, pity) {
95
125
  if (isNumber(value)) return { value: Number.parseInt(value, 10) };
96
- const rollComp = roll(value, engine);
97
- if (!rollComp?.total)
98
- return { value: (0, import_mathjs.evaluate)(value), diceResult: value };
126
+ if (!value || typeof value === "string" && value.trim() === "") {
127
+ return { value: 0, diceResult: value };
128
+ }
129
+ const rollComp = roll(value, engine, pity);
130
+ if (!rollComp?.total) {
131
+ try {
132
+ return { value: (0, import_mathjs.evaluate)(value), diceResult: value };
133
+ } catch (error) {
134
+ return { value: 0, diceResult: value };
135
+ }
136
+ }
99
137
  return {
100
138
  dice: value,
101
139
  value: rollComp.total,
@@ -134,20 +172,20 @@ function getModifier(dice) {
134
172
  }
135
173
  return modificator;
136
174
  }
137
- function roll(dice, engine = import_rpg_dice_roller.NumberGenerator.engines.nodeCrypto) {
175
+ function roll(dice, engine = import_rpg_dice_roller.NumberGenerator.engines.nodeCrypto, pity) {
138
176
  dice = standardizeDice(replaceFormulaInDice(dice)).replace(/^\+/, "").replaceAll("=>", ">=").replaceAll("=<", "<=").trimStart();
139
177
  if (!dice.includes("d")) return void 0;
140
178
  dice = dice.replaceAll(DETECT_CRITICAL, "").trimEnd();
141
179
  const compareRegex = dice.match(SIGN_REGEX_SPACE);
142
180
  let compare;
143
181
  if (dice.includes(";")) return sharedRolls(dice, engine);
182
+ dice = fixParenthesis(dice);
183
+ const modificator = getModifier(dice);
144
184
  if (compareRegex) {
145
- const compareResult = getCompare(dice, compareRegex, engine);
185
+ const compareResult = getCompare(dice, compareRegex, engine, pity);
146
186
  dice = compareResult.dice;
147
187
  compare = compareResult.compare;
148
188
  }
149
- dice = fixParenthesis(dice);
150
- const modificator = getModifier(dice);
151
189
  if (dice.match(/\d+?#(.*)/)) {
152
190
  const diceArray = dice.split("#");
153
191
  const numberOfDice = Number.parseInt(diceArray[0], 10);
@@ -175,22 +213,85 @@ function roll(dice, engine = import_rpg_dice_roller.NumberGenerator.engines.node
175
213
  const roller = new import_rpg_dice_roller.DiceRoller();
176
214
  import_rpg_dice_roller.NumberGenerator.generator.engine = engine;
177
215
  const diceWithoutComment = dice.replace(COMMENT_REGEX, "").trimEnd();
216
+ let diceRoll;
178
217
  try {
179
- roller.roll(diceWithoutComment);
218
+ diceRoll = roller.roll(diceWithoutComment);
180
219
  } catch (error) {
181
220
  throw new DiceTypeError(diceWithoutComment, "roll", error);
182
221
  }
222
+ if (compare && diceRoll) {
223
+ const currentRoll = Array.isArray(diceRoll) ? diceRoll[0] : diceRoll;
224
+ const maxDiceValue = currentRoll.maxTotal;
225
+ const minDiceValue = currentRoll.minTotal;
226
+ const trivial = isTrivialComparison(maxDiceValue, minDiceValue, compare);
227
+ compare.trivial = trivial ? true : void 0;
228
+ }
183
229
  const commentMatch = dice.match(COMMENT_REGEX);
184
230
  const comment = commentMatch ? commentMatch[2] : void 0;
231
+ let rerollCount = 0;
232
+ let res;
233
+ if (pity && compare) {
234
+ const currentRoll = Array.isArray(diceRoll) ? diceRoll[0] : diceRoll;
235
+ const maxPossible = currentRoll ? currentRoll.maxTotal : null;
236
+ const isComparisonPossible = maxPossible === null || canComparisonSucceed(maxPossible, compare);
237
+ if (isComparisonPossible) {
238
+ let isFail = (0, import_mathjs.evaluate)(`${roller.total}${compare.sign}${compare.value}`);
239
+ if (!isFail) {
240
+ const maxReroll = 100;
241
+ while (!isFail && rerollCount < maxReroll) {
242
+ try {
243
+ res = roll(diceWithoutComment, engine, false);
244
+ } catch (error) {
245
+ throw new DiceTypeError(diceWithoutComment, "roll", error);
246
+ }
247
+ rerollCount++;
248
+ if (res && res.total !== void 0)
249
+ isFail = (0, import_mathjs.evaluate)(`${res.total}${compare.sign}${compare.value}`);
250
+ }
251
+ if (res) {
252
+ return {
253
+ ...res,
254
+ dice,
255
+ comment,
256
+ compare,
257
+ modifier: modificator,
258
+ pityLogs: rerollCount
259
+ };
260
+ }
261
+ }
262
+ } else ;
263
+ }
185
264
  return {
186
265
  dice,
187
266
  result: roller.output,
188
267
  comment,
189
268
  compare: compare ? compare : void 0,
190
269
  modifier: modificator,
191
- total: roller.total
270
+ total: roller.total,
271
+ pityLogs: rerollCount > 0 ? rerollCount : void 0
192
272
  };
193
273
  }
274
+ function canComparisonSucceed(maxRollValue, compare, minRollValue) {
275
+ switch (compare.sign) {
276
+ case ">":
277
+ return maxRollValue > compare.value;
278
+ case ">=":
279
+ return maxRollValue >= compare.value;
280
+ case "<":
281
+ return compare.value > (minRollValue ?? 1);
282
+ // Au moins minRollValue possible
283
+ case "<=":
284
+ return compare.value >= (minRollValue ?? 1);
285
+ // Au moins minRollValue possible
286
+ case "=":
287
+ case "==":
288
+ return maxRollValue >= compare.value && compare.value >= (minRollValue ?? 1);
289
+ case "!=":
290
+ return maxRollValue !== compare.value || (minRollValue ?? 1) !== compare.value;
291
+ default:
292
+ return true;
293
+ }
294
+ }
194
295
  function fixParenthesis(dice) {
195
296
  const parenthesisRegex = /d\((\d+)\)/g;
196
297
  return dice.replaceAll(parenthesisRegex, (_match, p1) => `d${p1}`);
@@ -217,7 +318,7 @@ function inverseSign(sign) {
217
318
  return "==";
218
319
  }
219
320
  }
220
- function replaceInFormula(element, diceResult, compareResult, res, engine = import_rpg_dice_roller.NumberGenerator.engines.nodeCrypto) {
321
+ function replaceInFormula(element, diceResult, compareResult, res, engine = import_rpg_dice_roller.NumberGenerator.engines.nodeCrypto, pity) {
221
322
  const { formule, diceAll } = replaceText(
222
323
  element,
223
324
  diceResult.total ?? 0,
@@ -230,13 +331,13 @@ function replaceInFormula(element, diceResult, compareResult, res, engine = impo
230
331
  evaluateRoll = (0, import_mathjs.evaluate)(compareResult.dice);
231
332
  return `${validSign} ${diceAll}: ${formule} = ${evaluateRoll}${invertedSign}${compareResult.compare?.value}`;
232
333
  } catch (error) {
233
- const evaluateRoll2 = roll(compareResult.dice, engine);
334
+ const evaluateRoll2 = roll(compareResult.dice, engine, pity);
234
335
  if (evaluateRoll2)
235
336
  return `${validSign} ${diceAll}: ${evaluateRoll2.result.split(":").splice(1).join(":")}`;
236
337
  return `${validSign} ${diceAll}: ${formule} = ${evaluateRoll2}${invertedSign}${compareResult.compare?.value}`;
237
338
  }
238
339
  }
239
- function compareSignFormule(toRoll, compareRegex, element, diceResult, engine = import_rpg_dice_roller.NumberGenerator.engines.nodeCrypto) {
340
+ function compareSignFormule(toRoll, compareRegex, element, diceResult, engine = import_rpg_dice_roller.NumberGenerator.engines.nodeCrypto, pity) {
240
341
  let results = "";
241
342
  const compareResult = getCompare(toRoll, compareRegex, engine);
242
343
  const toCompare = `${compareResult.dice}${compareResult.compare?.sign}${compareResult.compare?.value}`;
@@ -244,10 +345,10 @@ function compareSignFormule(toRoll, compareRegex, element, diceResult, engine =
244
345
  try {
245
346
  res = (0, import_mathjs.evaluate)(toCompare);
246
347
  } catch (error) {
247
- res = roll(toCompare, engine);
348
+ res = roll(toCompare, engine, pity);
248
349
  }
249
350
  if (typeof res === "boolean") {
250
- results = replaceInFormula(element, diceResult, compareResult, res, engine);
351
+ results = replaceInFormula(element, diceResult, compareResult, res, engine, pity);
251
352
  } else if (res instanceof Object) {
252
353
  const diceResult2 = res;
253
354
  if (diceResult2.compare) {
@@ -271,17 +372,18 @@ function replaceText(element, total, dice) {
271
372
  function formatComment(dice) {
272
373
  const commentsRegex = /\[(?<comments>.*?)\]/;
273
374
  const commentsMatch = commentsRegex.exec(dice);
274
- const optionalComments = OPTIONAL_COMMENT.exec(dice);
275
375
  const comments = commentsMatch?.groups?.comments ? `${commentsMatch.groups.comments}` : "";
376
+ const diceWithoutBrackets = dice.replace(commentsRegex, "");
377
+ const optionalCommentsRegex = /\s+(#|\/\/)(?<comment>.*)/;
378
+ const optionalComments = optionalCommentsRegex.exec(diceWithoutBrackets);
276
379
  const optional = optionalComments?.groups?.comment ? `${optionalComments.groups.comment.trim()}` : "";
277
380
  let finalComment = "";
278
- if (comments && optional)
279
- finalComment = `__${commentsMatch?.groups?.comments} ${optionalComments?.groups?.comment.trim()}__ \u2014 `;
381
+ if (comments && optional) finalComment = `__${comments} ${optional}__ \u2014 `;
280
382
  else if (comments) finalComment = `__${comments}__ \u2014 `;
281
383
  else if (optional) finalComment = `__${optional}__ \u2014 `;
282
384
  return finalComment;
283
385
  }
284
- function sharedRolls(dice, engine = import_rpg_dice_roller.NumberGenerator.engines.nodeCrypto) {
386
+ function sharedRolls(dice, engine = import_rpg_dice_roller.NumberGenerator.engines.nodeCrypto, pity) {
285
387
  if (dice.match(/\d+?#(.*?)/))
286
388
  throw new DiceTypeError(
287
389
  dice,
@@ -292,8 +394,11 @@ function sharedRolls(dice, engine = import_rpg_dice_roller.NumberGenerator.engin
292
394
  const mainComment = /\s+#(?<comment>.*)/.exec(dice)?.groups?.comment?.trimEnd() ?? void 0;
293
395
  const split = dice.split(";");
294
396
  let diceMain = fixParenthesis(split[0]);
295
- const toHideRegex = /(?<!\[[^\]]*)\((?<dice>[^)]+)\)/;
296
- const toHide = toHideRegex.exec(diceMain)?.groups;
397
+ const commentsRegex = /\[(?<comments>.*?)\]/gi;
398
+ const comments = formatComment(diceMain);
399
+ const diceMainWithoutComments = diceMain.replace(commentsRegex, "").trim();
400
+ const toHideRegex = /\((?<dice>[^)]+)\)/;
401
+ const toHide = toHideRegex.exec(diceMainWithoutComments)?.groups;
297
402
  let hidden = false;
298
403
  if (toHide?.dice) {
299
404
  diceMain = toHide.dice;
@@ -301,14 +406,13 @@ function sharedRolls(dice, engine = import_rpg_dice_roller.NumberGenerator.engin
301
406
  } else if (toHide) {
302
407
  diceMain = "1d1";
303
408
  hidden = true;
409
+ } else {
410
+ diceMain = diceMainWithoutComments;
304
411
  }
305
- const commentsRegex = /\[(?<comments>.*?)\]/gi;
306
- const comments = formatComment(diceMain);
307
- diceMain = diceMain.replace(commentsRegex, "").trim();
308
- let diceResult = roll(diceMain, engine);
412
+ let diceResult = roll(diceMain, engine, pity);
309
413
  if (!diceResult || !diceResult.total) {
310
414
  if (hidden) {
311
- diceResult = roll(fixParenthesis(split[0]));
415
+ diceResult = roll(fixParenthesis(split[0]), engine, pity);
312
416
  hidden = false;
313
417
  } else return void 0;
314
418
  }
@@ -343,7 +447,7 @@ function sharedRolls(dice, engine = import_rpg_dice_roller.NumberGenerator.engin
343
447
  results.push(`\u25C8 ${comment}${diceAll}: ${formule} = ${evaluated}`);
344
448
  total += Number.parseInt(evaluated, 10);
345
449
  } catch (error) {
346
- const evaluated = roll(toRoll, engine);
450
+ const evaluated = roll(toRoll, engine, pity);
347
451
  if (evaluated)
348
452
  results.push(
349
453
  `\u25C8 ${comment}${diceAll}: ${evaluated.result.split(":").slice(1).join(":")}`
@@ -432,10 +536,10 @@ var NoStatisticsError = class extends Error {
432
536
 
433
537
  // src/interfaces/constant.ts
434
538
  var COMMENT_REGEX = /\s+(#|\/{2}|\[|\/\*)(?<comment>.*)/gi;
435
- var SIGN_REGEX = /[><=!]+/;
436
- var SIGN_REGEX_SPACE = /[><=!]+(\S+)/;
539
+ var SIGN_REGEX = /==|!=|(?<![!<>])>=|(?<![!<>])<=|(?<!!)(?<![<>])>|(?<!!)(?<![<>])<|(?<!!)(?<![<>])=/;
540
+ var SIGN_REGEX_SPACE = /(==|!=|(?<![!<>])>=|(?<![!<>])<=|(?<!!)(?<![<>])>|(?<!!)(?<![<>])<|(?<!!)(?<![<>])=)(\S+)/;
437
541
  var SYMBOL_DICE = "&";
438
- var DETECT_CRITICAL = /\{\*?c[fs]:[<>=!]+(.+?)}/gim;
542
+ var DETECT_CRITICAL = /\{\*?c[fs]:([<>=]|!=)+(.+?)}/gim;
439
543
  var OPTIONAL_COMMENT = /\s+(#|\/{2}|\[|\/\*)?(?<comment>.*)/gi;
440
544
 
441
545
  // src/interfaces/zod.ts
@@ -655,7 +759,7 @@ var import_mathjs3 = require("mathjs");
655
759
  var import_random_js2 = require("random-js");
656
760
  var import_uniformize2 = require("uniformize");
657
761
  var import_rpg_dice_roller3 = require("@dice-roller/rpg-dice-roller");
658
- function evalStatsDice(testDice, allStats, engine = import_rpg_dice_roller3.NumberGenerator.engines.nodeCrypto) {
762
+ function evalStatsDice(testDice, allStats, engine = import_rpg_dice_roller3.NumberGenerator.engines.nodeCrypto, pity) {
659
763
  let dice = testDice.trimEnd();
660
764
  if (allStats && Object.keys(allStats).length > 0) {
661
765
  const names = Object.keys(allStats);
@@ -668,7 +772,7 @@ function evalStatsDice(testDice, allStats, engine = import_rpg_dice_roller3.Numb
668
772
  }
669
773
  }
670
774
  try {
671
- if (!roll(replaceFormulaInDice(replaceExpByRandom(dice)), engine))
775
+ if (!roll(replaceFormulaInDice(replaceExpByRandom(dice)), engine, pity))
672
776
  throw new DiceTypeError(dice, "evalStatsDice", "no roll result");
673
777
  return testDice;
674
778
  } catch (error) {