@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/dist/index.mjs CHANGED
@@ -1,17 +1,47 @@
1
1
  // src/dice.ts
2
2
  import { DiceRoller, NumberGenerator } from "@dice-roller/rpg-dice-roller";
3
3
  import { evaluate } from "mathjs";
4
- function getCompare(dice, compareRegex, engine = NumberGenerator.engines.nodeCrypto) {
5
- if (dice.match(/((\{.*,(.*)+\}|([><=!]+\d+f))[><=!]+\d+\}?)|\{(.*)([><=!]+).*\}/))
4
+ function isTrivialComparison(maxValue, minValue, compare) {
5
+ const canSucceed = canComparisonSucceed(maxValue, compare, minValue);
6
+ const canFail = canComparisonFail(maxValue, compare, minValue);
7
+ return !canSucceed || !canFail;
8
+ }
9
+ function canComparisonFail(maxRollValue, compare, minRollValue = 1) {
10
+ switch (compare.sign) {
11
+ case ">":
12
+ return minRollValue <= compare.value;
13
+ // failure if roll <= value
14
+ case ">=":
15
+ return minRollValue < compare.value;
16
+ // failure if roll < value
17
+ case "<":
18
+ return maxRollValue >= compare.value;
19
+ // failure if roll >= value
20
+ case "<=":
21
+ return maxRollValue > compare.value;
22
+ // failure if roll > value
23
+ case "=":
24
+ case "==":
25
+ return minRollValue !== compare.value || maxRollValue !== compare.value;
26
+ // can differ
27
+ case "!=":
28
+ return minRollValue <= compare.value && compare.value <= maxRollValue;
29
+ // equality possible
30
+ default:
31
+ return true;
32
+ }
33
+ }
34
+ function getCompare(dice, compareRegex, engine = NumberGenerator.engines.nodeCrypto, pity) {
35
+ if (dice.match(/((\{.*,(.*)+\}|([><=!]+\d+f))([><=]|!=)+\d+\}?)|\{(.*)(([><=]|!=)+).*\}/))
6
36
  return { dice, compare: void 0 };
7
37
  dice = dice.replace(SIGN_REGEX_SPACE, "");
8
38
  let compare;
9
- const calc = compareRegex[1];
39
+ const calc = compareRegex[2];
10
40
  const sign = calc.match(/[+-/*^]/)?.[0];
11
41
  const compareSign = compareRegex[0].match(SIGN_REGEX)?.[0];
12
42
  if (sign) {
13
43
  const toCalc = calc.replace(SIGN_REGEX, "").replace(/\s/g, "").replace(/;(.*)/, "");
14
- const rCompare = rollCompare(toCalc, engine);
44
+ const rCompare = rollCompare(toCalc, engine, pity);
15
45
  const total = evaluate(rCompare.value.toString());
16
46
  dice = dice.replace(SIGN_REGEX_SPACE, `${compareSign}${total}`);
17
47
  compare = {
@@ -21,7 +51,7 @@ function getCompare(dice, compareRegex, engine = NumberGenerator.engines.nodeCry
21
51
  rollValue: rCompare.diceResult
22
52
  };
23
53
  } else {
24
- const rcompare = rollCompare(calc, engine);
54
+ const rcompare = rollCompare(calc, engine, pity);
25
55
  compare = {
26
56
  sign: compareSign,
27
57
  value: rcompare.value,
@@ -31,11 +61,19 @@ function getCompare(dice, compareRegex, engine = NumberGenerator.engines.nodeCry
31
61
  }
32
62
  return { dice, compare };
33
63
  }
34
- function rollCompare(value, engine = NumberGenerator.engines.nodeCrypto) {
64
+ function rollCompare(value, engine = NumberGenerator.engines.nodeCrypto, pity) {
35
65
  if (isNumber(value)) return { value: Number.parseInt(value, 10) };
36
- const rollComp = roll(value, engine);
37
- if (!rollComp?.total)
38
- return { value: evaluate(value), diceResult: value };
66
+ if (!value || typeof value === "string" && value.trim() === "") {
67
+ return { value: 0, diceResult: value };
68
+ }
69
+ const rollComp = roll(value, engine, pity);
70
+ if (!rollComp?.total) {
71
+ try {
72
+ return { value: evaluate(value), diceResult: value };
73
+ } catch (error) {
74
+ return { value: 0, diceResult: value };
75
+ }
76
+ }
39
77
  return {
40
78
  dice: value,
41
79
  value: rollComp.total,
@@ -74,20 +112,20 @@ function getModifier(dice) {
74
112
  }
75
113
  return modificator;
76
114
  }
77
- function roll(dice, engine = NumberGenerator.engines.nodeCrypto) {
115
+ function roll(dice, engine = NumberGenerator.engines.nodeCrypto, pity) {
78
116
  dice = standardizeDice(replaceFormulaInDice(dice)).replace(/^\+/, "").replaceAll("=>", ">=").replaceAll("=<", "<=").trimStart();
79
117
  if (!dice.includes("d")) return void 0;
80
118
  dice = dice.replaceAll(DETECT_CRITICAL, "").trimEnd();
81
119
  const compareRegex = dice.match(SIGN_REGEX_SPACE);
82
120
  let compare;
83
121
  if (dice.includes(";")) return sharedRolls(dice, engine);
122
+ dice = fixParenthesis(dice);
123
+ const modificator = getModifier(dice);
84
124
  if (compareRegex) {
85
- const compareResult = getCompare(dice, compareRegex, engine);
125
+ const compareResult = getCompare(dice, compareRegex, engine, pity);
86
126
  dice = compareResult.dice;
87
127
  compare = compareResult.compare;
88
128
  }
89
- dice = fixParenthesis(dice);
90
- const modificator = getModifier(dice);
91
129
  if (dice.match(/\d+?#(.*)/)) {
92
130
  const diceArray = dice.split("#");
93
131
  const numberOfDice = Number.parseInt(diceArray[0], 10);
@@ -115,22 +153,85 @@ function roll(dice, engine = NumberGenerator.engines.nodeCrypto) {
115
153
  const roller = new DiceRoller();
116
154
  NumberGenerator.generator.engine = engine;
117
155
  const diceWithoutComment = dice.replace(COMMENT_REGEX, "").trimEnd();
156
+ let diceRoll;
118
157
  try {
119
- roller.roll(diceWithoutComment);
158
+ diceRoll = roller.roll(diceWithoutComment);
120
159
  } catch (error) {
121
160
  throw new DiceTypeError(diceWithoutComment, "roll", error);
122
161
  }
162
+ if (compare && diceRoll) {
163
+ const currentRoll = Array.isArray(diceRoll) ? diceRoll[0] : diceRoll;
164
+ const maxDiceValue = currentRoll.maxTotal;
165
+ const minDiceValue = currentRoll.minTotal;
166
+ const trivial = isTrivialComparison(maxDiceValue, minDiceValue, compare);
167
+ compare.trivial = trivial ? true : void 0;
168
+ }
123
169
  const commentMatch = dice.match(COMMENT_REGEX);
124
170
  const comment = commentMatch ? commentMatch[2] : void 0;
171
+ let rerollCount = 0;
172
+ let res;
173
+ if (pity && compare) {
174
+ const currentRoll = Array.isArray(diceRoll) ? diceRoll[0] : diceRoll;
175
+ const maxPossible = currentRoll ? currentRoll.maxTotal : null;
176
+ const isComparisonPossible = maxPossible === null || canComparisonSucceed(maxPossible, compare);
177
+ if (isComparisonPossible) {
178
+ let isFail = evaluate(`${roller.total}${compare.sign}${compare.value}`);
179
+ if (!isFail) {
180
+ const maxReroll = 100;
181
+ while (!isFail && rerollCount < maxReroll) {
182
+ try {
183
+ res = roll(diceWithoutComment, engine, false);
184
+ } catch (error) {
185
+ throw new DiceTypeError(diceWithoutComment, "roll", error);
186
+ }
187
+ rerollCount++;
188
+ if (res && res.total !== void 0)
189
+ isFail = evaluate(`${res.total}${compare.sign}${compare.value}`);
190
+ }
191
+ if (res) {
192
+ return {
193
+ ...res,
194
+ dice,
195
+ comment,
196
+ compare,
197
+ modifier: modificator,
198
+ pityLogs: rerollCount
199
+ };
200
+ }
201
+ }
202
+ } else ;
203
+ }
125
204
  return {
126
205
  dice,
127
206
  result: roller.output,
128
207
  comment,
129
208
  compare: compare ? compare : void 0,
130
209
  modifier: modificator,
131
- total: roller.total
210
+ total: roller.total,
211
+ pityLogs: rerollCount > 0 ? rerollCount : void 0
132
212
  };
133
213
  }
214
+ function canComparisonSucceed(maxRollValue, compare, minRollValue) {
215
+ switch (compare.sign) {
216
+ case ">":
217
+ return maxRollValue > compare.value;
218
+ case ">=":
219
+ return maxRollValue >= compare.value;
220
+ case "<":
221
+ return compare.value > (minRollValue ?? 1);
222
+ // Au moins minRollValue possible
223
+ case "<=":
224
+ return compare.value >= (minRollValue ?? 1);
225
+ // Au moins minRollValue possible
226
+ case "=":
227
+ case "==":
228
+ return maxRollValue >= compare.value && compare.value >= (minRollValue ?? 1);
229
+ case "!=":
230
+ return maxRollValue !== compare.value || (minRollValue ?? 1) !== compare.value;
231
+ default:
232
+ return true;
233
+ }
234
+ }
134
235
  function fixParenthesis(dice) {
135
236
  const parenthesisRegex = /d\((\d+)\)/g;
136
237
  return dice.replaceAll(parenthesisRegex, (_match, p1) => `d${p1}`);
@@ -157,7 +258,7 @@ function inverseSign(sign) {
157
258
  return "==";
158
259
  }
159
260
  }
160
- function replaceInFormula(element, diceResult, compareResult, res, engine = NumberGenerator.engines.nodeCrypto) {
261
+ function replaceInFormula(element, diceResult, compareResult, res, engine = NumberGenerator.engines.nodeCrypto, pity) {
161
262
  const { formule, diceAll } = replaceText(
162
263
  element,
163
264
  diceResult.total ?? 0,
@@ -170,13 +271,13 @@ function replaceInFormula(element, diceResult, compareResult, res, engine = Numb
170
271
  evaluateRoll = evaluate(compareResult.dice);
171
272
  return `${validSign} ${diceAll}: ${formule} = ${evaluateRoll}${invertedSign}${compareResult.compare?.value}`;
172
273
  } catch (error) {
173
- const evaluateRoll2 = roll(compareResult.dice, engine);
274
+ const evaluateRoll2 = roll(compareResult.dice, engine, pity);
174
275
  if (evaluateRoll2)
175
276
  return `${validSign} ${diceAll}: ${evaluateRoll2.result.split(":").splice(1).join(":")}`;
176
277
  return `${validSign} ${diceAll}: ${formule} = ${evaluateRoll2}${invertedSign}${compareResult.compare?.value}`;
177
278
  }
178
279
  }
179
- function compareSignFormule(toRoll, compareRegex, element, diceResult, engine = NumberGenerator.engines.nodeCrypto) {
280
+ function compareSignFormule(toRoll, compareRegex, element, diceResult, engine = NumberGenerator.engines.nodeCrypto, pity) {
180
281
  let results = "";
181
282
  const compareResult = getCompare(toRoll, compareRegex, engine);
182
283
  const toCompare = `${compareResult.dice}${compareResult.compare?.sign}${compareResult.compare?.value}`;
@@ -184,10 +285,10 @@ function compareSignFormule(toRoll, compareRegex, element, diceResult, engine =
184
285
  try {
185
286
  res = evaluate(toCompare);
186
287
  } catch (error) {
187
- res = roll(toCompare, engine);
288
+ res = roll(toCompare, engine, pity);
188
289
  }
189
290
  if (typeof res === "boolean") {
190
- results = replaceInFormula(element, diceResult, compareResult, res, engine);
291
+ results = replaceInFormula(element, diceResult, compareResult, res, engine, pity);
191
292
  } else if (res instanceof Object) {
192
293
  const diceResult2 = res;
193
294
  if (diceResult2.compare) {
@@ -211,17 +312,18 @@ function replaceText(element, total, dice) {
211
312
  function formatComment(dice) {
212
313
  const commentsRegex = /\[(?<comments>.*?)\]/;
213
314
  const commentsMatch = commentsRegex.exec(dice);
214
- const optionalComments = OPTIONAL_COMMENT.exec(dice);
215
315
  const comments = commentsMatch?.groups?.comments ? `${commentsMatch.groups.comments}` : "";
316
+ const diceWithoutBrackets = dice.replace(commentsRegex, "");
317
+ const optionalCommentsRegex = /\s+(#|\/\/)(?<comment>.*)/;
318
+ const optionalComments = optionalCommentsRegex.exec(diceWithoutBrackets);
216
319
  const optional = optionalComments?.groups?.comment ? `${optionalComments.groups.comment.trim()}` : "";
217
320
  let finalComment = "";
218
- if (comments && optional)
219
- finalComment = `__${commentsMatch?.groups?.comments} ${optionalComments?.groups?.comment.trim()}__ \u2014 `;
321
+ if (comments && optional) finalComment = `__${comments} ${optional}__ \u2014 `;
220
322
  else if (comments) finalComment = `__${comments}__ \u2014 `;
221
323
  else if (optional) finalComment = `__${optional}__ \u2014 `;
222
324
  return finalComment;
223
325
  }
224
- function sharedRolls(dice, engine = NumberGenerator.engines.nodeCrypto) {
326
+ function sharedRolls(dice, engine = NumberGenerator.engines.nodeCrypto, pity) {
225
327
  if (dice.match(/\d+?#(.*?)/))
226
328
  throw new DiceTypeError(
227
329
  dice,
@@ -232,8 +334,11 @@ function sharedRolls(dice, engine = NumberGenerator.engines.nodeCrypto) {
232
334
  const mainComment = /\s+#(?<comment>.*)/.exec(dice)?.groups?.comment?.trimEnd() ?? void 0;
233
335
  const split = dice.split(";");
234
336
  let diceMain = fixParenthesis(split[0]);
235
- const toHideRegex = /(?<!\[[^\]]*)\((?<dice>[^)]+)\)/;
236
- const toHide = toHideRegex.exec(diceMain)?.groups;
337
+ const commentsRegex = /\[(?<comments>.*?)\]/gi;
338
+ const comments = formatComment(diceMain);
339
+ const diceMainWithoutComments = diceMain.replace(commentsRegex, "").trim();
340
+ const toHideRegex = /\((?<dice>[^)]+)\)/;
341
+ const toHide = toHideRegex.exec(diceMainWithoutComments)?.groups;
237
342
  let hidden = false;
238
343
  if (toHide?.dice) {
239
344
  diceMain = toHide.dice;
@@ -241,14 +346,13 @@ function sharedRolls(dice, engine = NumberGenerator.engines.nodeCrypto) {
241
346
  } else if (toHide) {
242
347
  diceMain = "1d1";
243
348
  hidden = true;
349
+ } else {
350
+ diceMain = diceMainWithoutComments;
244
351
  }
245
- const commentsRegex = /\[(?<comments>.*?)\]/gi;
246
- const comments = formatComment(diceMain);
247
- diceMain = diceMain.replace(commentsRegex, "").trim();
248
- let diceResult = roll(diceMain, engine);
352
+ let diceResult = roll(diceMain, engine, pity);
249
353
  if (!diceResult || !diceResult.total) {
250
354
  if (hidden) {
251
- diceResult = roll(fixParenthesis(split[0]));
355
+ diceResult = roll(fixParenthesis(split[0]), engine, pity);
252
356
  hidden = false;
253
357
  } else return void 0;
254
358
  }
@@ -283,7 +387,7 @@ function sharedRolls(dice, engine = NumberGenerator.engines.nodeCrypto) {
283
387
  results.push(`\u25C8 ${comment}${diceAll}: ${formule} = ${evaluated}`);
284
388
  total += Number.parseInt(evaluated, 10);
285
389
  } catch (error) {
286
- const evaluated = roll(toRoll, engine);
390
+ const evaluated = roll(toRoll, engine, pity);
287
391
  if (evaluated)
288
392
  results.push(
289
393
  `\u25C8 ${comment}${diceAll}: ${evaluated.result.split(":").slice(1).join(":")}`
@@ -372,10 +476,10 @@ var NoStatisticsError = class extends Error {
372
476
 
373
477
  // src/interfaces/constant.ts
374
478
  var COMMENT_REGEX = /\s+(#|\/{2}|\[|\/\*)(?<comment>.*)/gi;
375
- var SIGN_REGEX = /[><=!]+/;
376
- var SIGN_REGEX_SPACE = /[><=!]+(\S+)/;
479
+ var SIGN_REGEX = /==|!=|(?<![!<>])>=|(?<![!<>])<=|(?<!!)(?<![<>])>|(?<!!)(?<![<>])<|(?<!!)(?<![<>])=/;
480
+ var SIGN_REGEX_SPACE = /(==|!=|(?<![!<>])>=|(?<![!<>])<=|(?<!!)(?<![<>])>|(?<!!)(?<![<>])<|(?<!!)(?<![<>])=)(\S+)/;
377
481
  var SYMBOL_DICE = "&";
378
- var DETECT_CRITICAL = /\{\*?c[fs]:[<>=!]+(.+?)}/gim;
482
+ var DETECT_CRITICAL = /\{\*?c[fs]:([<>=]|!=)+(.+?)}/gim;
379
483
  var OPTIONAL_COMMENT = /\s+(#|\/{2}|\[|\/\*)?(?<comment>.*)/gi;
380
484
 
381
485
  // src/interfaces/zod.ts
@@ -595,7 +699,7 @@ import { evaluate as evaluate3 } from "mathjs";
595
699
  import { Random as Random2 } from "random-js";
596
700
  import "uniformize";
597
701
  import { NumberGenerator as NumberGenerator3 } from "@dice-roller/rpg-dice-roller";
598
- function evalStatsDice(testDice, allStats, engine = NumberGenerator3.engines.nodeCrypto) {
702
+ function evalStatsDice(testDice, allStats, engine = NumberGenerator3.engines.nodeCrypto, pity) {
599
703
  let dice = testDice.trimEnd();
600
704
  if (allStats && Object.keys(allStats).length > 0) {
601
705
  const names = Object.keys(allStats);
@@ -608,7 +712,7 @@ function evalStatsDice(testDice, allStats, engine = NumberGenerator3.engines.nod
608
712
  }
609
713
  }
610
714
  try {
611
- if (!roll(replaceFormulaInDice(replaceExpByRandom(dice)), engine))
715
+ if (!roll(replaceFormulaInDice(replaceExpByRandom(dice)), engine, pity))
612
716
  throw new DiceTypeError(dice, "evalStatsDice", "no roll result");
613
717
  return testDice;
614
718
  } catch (error) {