@dicelette/core 1.22.3 → 1.23.1

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,7 +61,37 @@ 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) {
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) {
65
95
  if (dice.match(/((\{.*,(.*)+\}|([><=!]+\d+f))([><=]|!=)+\d+\}?)|\{(.*)(([><=]|!=)+).*\}/))
66
96
  return { dice, compare: void 0 };
67
97
  dice = dice.replace(SIGN_REGEX_SPACE, "");
@@ -71,7 +101,7 @@ function getCompare(dice, compareRegex, engine = import_rpg_dice_roller.NumberGe
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,24 +331,27 @@ 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 = "";
342
+ let trivial = false;
241
343
  const compareResult = getCompare(toRoll, compareRegex, engine);
242
344
  const toCompare = `${compareResult.dice}${compareResult.compare?.sign}${compareResult.compare?.value}`;
243
345
  let res;
244
346
  try {
245
347
  res = (0, import_mathjs.evaluate)(toCompare);
246
348
  } catch (error) {
247
- res = roll(toCompare, engine);
349
+ res = roll(toCompare, engine, pity);
248
350
  }
249
351
  if (typeof res === "boolean") {
250
- results = replaceInFormula(element, diceResult, compareResult, res, engine);
352
+ trivial = true;
353
+ if (compareResult.compare) compareResult.compare.trivial = true;
354
+ results = replaceInFormula(element, diceResult, compareResult, res, engine, pity);
251
355
  } else if (res instanceof Object) {
252
356
  const diceResult2 = res;
253
357
  if (diceResult2.compare) {
@@ -258,9 +362,10 @@ function compareSignFormule(toRoll, compareRegex, element, diceResult, engine =
258
362
  const invertedSign = toEvaluate ? diceResult2.compare.sign : inverseSign(diceResult2.compare.sign);
259
363
  const dice = replaceText(element, 0, diceResult2.dice).diceAll;
260
364
  results = `${sign} ${dice}: ${diceResult2.result.split(":").splice(1).join(":").trim()}${invertedSign}${diceResult2.compare.value}`;
365
+ if (diceResult2.compare.trivial) trivial = true;
261
366
  }
262
367
  }
263
- return { dice: compareResult.dice, results };
368
+ return { dice: compareResult.dice, results, compare: compareResult.compare, trivial };
264
369
  }
265
370
  function replaceText(element, total, dice) {
266
371
  return {
@@ -277,13 +382,12 @@ function formatComment(dice) {
277
382
  const optionalComments = optionalCommentsRegex.exec(diceWithoutBrackets);
278
383
  const optional = optionalComments?.groups?.comment ? `${optionalComments.groups.comment.trim()}` : "";
279
384
  let finalComment = "";
280
- if (comments && optional)
281
- finalComment = `__${comments} ${optional}__ \u2014 `;
385
+ if (comments && optional) finalComment = `__${comments} ${optional}__ \u2014 `;
282
386
  else if (comments) finalComment = `__${comments}__ \u2014 `;
283
387
  else if (optional) finalComment = `__${optional}__ \u2014 `;
284
388
  return finalComment;
285
389
  }
286
- function sharedRolls(dice, engine = import_rpg_dice_roller.NumberGenerator.engines.nodeCrypto) {
390
+ function sharedRolls(dice, engine = import_rpg_dice_roller.NumberGenerator.engines.nodeCrypto, pity) {
287
391
  if (dice.match(/\d+?#(.*?)/))
288
392
  throw new DiceTypeError(
289
393
  dice,
@@ -309,14 +413,16 @@ function sharedRolls(dice, engine = import_rpg_dice_roller.NumberGenerator.engin
309
413
  } else {
310
414
  diceMain = diceMainWithoutComments;
311
415
  }
312
- let diceResult = roll(diceMain, engine);
416
+ let diceResult = roll(diceMain, engine, pity);
313
417
  if (!diceResult || !diceResult.total) {
314
418
  if (hidden) {
315
- diceResult = roll(fixParenthesis(split[0]));
419
+ diceResult = roll(fixParenthesis(split[0]), engine, pity);
316
420
  hidden = false;
317
421
  } else return void 0;
318
422
  }
319
423
  if (!diceResult || !diceResult.total) return void 0;
424
+ let aggregatedCompare = diceResult.compare;
425
+ let hasTrivialComparison = diceResult.compare?.trivial === true;
320
426
  results.push(`\u203B ${comments}${diceResult.result}`);
321
427
  let total = diceResult.total;
322
428
  diceResult.comment = mainComment;
@@ -336,6 +442,8 @@ function sharedRolls(dice, engine = import_rpg_dice_roller.NumberGenerator.engin
336
442
  );
337
443
  toRoll = compareResult.dice;
338
444
  results.push(compareResult.results);
445
+ if (!aggregatedCompare && compareResult.compare) aggregatedCompare = compareResult.compare;
446
+ if (compareResult.trivial) hasTrivialComparison = true;
339
447
  } else {
340
448
  const { formule, diceAll } = replaceText(
341
449
  element,
@@ -347,12 +455,15 @@ function sharedRolls(dice, engine = import_rpg_dice_roller.NumberGenerator.engin
347
455
  results.push(`\u25C8 ${comment}${diceAll}: ${formule} = ${evaluated}`);
348
456
  total += Number.parseInt(evaluated, 10);
349
457
  } catch (error) {
350
- const evaluated = roll(toRoll, engine);
351
- if (evaluated)
458
+ const evaluated = roll(toRoll, engine, pity);
459
+ if (evaluated) {
352
460
  results.push(
353
461
  `\u25C8 ${comment}${diceAll}: ${evaluated.result.split(":").slice(1).join(":")}`
354
462
  );
355
- else results.push(`\u25C8 ${comment}${diceAll}: ${formule} = ${evaluated}`);
463
+ if (!aggregatedCompare && evaluated.compare)
464
+ aggregatedCompare = evaluated.compare;
465
+ if (evaluated.compare?.trivial) hasTrivialComparison = true;
466
+ } else results.push(`\u25C8 ${comment}${diceAll}: ${formule} = ${evaluated}`);
356
467
  total += evaluated?.total ?? 0;
357
468
  }
358
469
  }
@@ -363,7 +474,7 @@ function sharedRolls(dice, engine = import_rpg_dice_roller.NumberGenerator.engin
363
474
  dice: diceMain,
364
475
  result: results.join(";"),
365
476
  comment: mainComment,
366
- compare: diceResult.compare,
477
+ compare: hasTrivialComparison && aggregatedCompare ? { ...aggregatedCompare, trivial: true } : aggregatedCompare,
367
478
  modifier: diceResult.modifier,
368
479
  total
369
480
  };
@@ -436,8 +547,8 @@ var NoStatisticsError = class extends Error {
436
547
 
437
548
  // src/interfaces/constant.ts
438
549
  var COMMENT_REGEX = /\s+(#|\/{2}|\[|\/\*)(?<comment>.*)/gi;
439
- var SIGN_REGEX = /!=|(?<![!<>])>=|(?<![!<>])<=|(?<!!)(?<![<>])>|(?<!!)(?<![<>])<|(?<!!)(?<![<>])=/;
440
- var SIGN_REGEX_SPACE = /(!=|(?<![!<>])>=|(?<![!<>])<=|(?<!!)(?<![<>])>|(?<!!)(?<![<>])<|(?<!!)(?<![<>])=)(\S+)/;
550
+ var SIGN_REGEX = /==|!=|(?<![!<>])>=|(?<![!<>])<=|(?<!!)(?<![<>])>|(?<!!)(?<![<>])<|(?<!!)(?<![<>])=/;
551
+ var SIGN_REGEX_SPACE = /(==|!=|(?<![!<>])>=|(?<![!<>])<=|(?<!!)(?<![<>])>|(?<!!)(?<![<>])<|(?<!!)(?<![<>])=)(\S+)/;
441
552
  var SYMBOL_DICE = "&";
442
553
  var DETECT_CRITICAL = /\{\*?c[fs]:([<>=]|!=)+(.+?)}/gim;
443
554
  var OPTIONAL_COMMENT = /\s+(#|\/{2}|\[|\/\*)?(?<comment>.*)/gi;
@@ -659,7 +770,7 @@ var import_mathjs3 = require("mathjs");
659
770
  var import_random_js2 = require("random-js");
660
771
  var import_uniformize2 = require("uniformize");
661
772
  var import_rpg_dice_roller3 = require("@dice-roller/rpg-dice-roller");
662
- function evalStatsDice(testDice, allStats, engine = import_rpg_dice_roller3.NumberGenerator.engines.nodeCrypto) {
773
+ function evalStatsDice(testDice, allStats, engine = import_rpg_dice_roller3.NumberGenerator.engines.nodeCrypto, pity) {
663
774
  let dice = testDice.trimEnd();
664
775
  if (allStats && Object.keys(allStats).length > 0) {
665
776
  const names = Object.keys(allStats);
@@ -672,7 +783,7 @@ function evalStatsDice(testDice, allStats, engine = import_rpg_dice_roller3.Numb
672
783
  }
673
784
  }
674
785
  try {
675
- if (!roll(replaceFormulaInDice(replaceExpByRandom(dice)), engine))
786
+ if (!roll(replaceFormulaInDice(replaceExpByRandom(dice)), engine, pity))
676
787
  throw new DiceTypeError(dice, "evalStatsDice", "no roll result");
677
788
  return testDice;
678
789
  } catch (error) {