@dicelette/core 1.28.1 → 1.28.3

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
@@ -205,211 +205,261 @@ function includeDiceType(dice, diceType, userStats) {
205
205
  }
206
206
 
207
207
  // src/roll.ts
208
- import { DiceRoller as DiceRoller3, NumberGenerator as NumberGenerator7 } from "@dice-roller/rpg-dice-roller";
209
- import { evaluate as evaluate8 } from "mathjs";
208
+ import { DiceRoller as DiceRoller3, NumberGenerator as NumberGenerator8 } from "@dice-roller/rpg-dice-roller";
209
+ import { evaluate as evaluate10 } from "mathjs";
210
210
 
211
211
  // src/dice/bulk.ts
212
- import { DiceRoller as DiceRoller2, NumberGenerator as NumberGenerator6 } from "@dice-roller/rpg-dice-roller";
213
- import { evaluate as evaluate6 } from "mathjs";
212
+ import { DiceRoller as DiceRoller2, NumberGenerator as NumberGenerator7 } from "@dice-roller/rpg-dice-roller";
213
+ import { evaluate as evaluate8 } from "mathjs";
214
214
 
215
- // src/dice/compare.ts
215
+ // src/utils.ts
216
+ import "uniformize";
216
217
  import { NumberGenerator as NumberGenerator3 } from "@dice-roller/rpg-dice-roller";
217
- import { evaluate as evaluate2 } from "mathjs";
218
+ import { Random as Random2 } from "random-js";
218
219
 
219
- // src/utils.ts
220
+ // src/verify_template.ts
220
221
  import { evaluate } from "mathjs";
222
+ import { Random } from "random-js";
221
223
  import "uniformize";
222
224
  import { NumberGenerator as NumberGenerator2 } from "@dice-roller/rpg-dice-roller";
223
- import { Random } from "random-js";
224
-
225
- // src/similarity.ts
226
- function calculateSimilarity(str1, str2) {
227
- const longer = str1.length > str2.length ? str1 : str2;
228
- const shorter = str1.length > str2.length ? str2 : str1;
229
- if (longer.length === 0) return 1;
230
- const distance = levenshteinDistance(longer, shorter);
231
- return (longer.length - distance) / longer.length;
232
- }
233
- function levenshteinDistance(str1, str2) {
234
- const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
235
- for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
236
- for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
237
- for (let j = 1; j <= str2.length; j++) {
238
- for (let i = 1; i <= str1.length; i++) {
239
- const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
240
- matrix[j][i] = Math.min(
241
- matrix[j][i - 1] + 1,
242
- // insertion
243
- matrix[j - 1][i] + 1,
244
- // deletion
245
- matrix[j - 1][i - 1] + cost
246
- // substitution
247
- );
225
+ function evalStatsDice(testDice, allStats, engine = NumberGenerator2.engines.nodeCrypto, pity) {
226
+ let dice = testDice.trimEnd();
227
+ if (allStats && Object.keys(allStats).length > 0) {
228
+ const names = Object.keys(allStats);
229
+ for (const name of names) {
230
+ const regex = new RegExp(escapeRegex(name.standardize()), "gi");
231
+ if (dice.standardize().match(regex)) {
232
+ const statValue = allStats[name];
233
+ dice = dice.standardize().replace(regex, statValue.toString()).trimEnd();
234
+ }
248
235
  }
249
236
  }
250
- return matrix[str2.length][str1.length];
251
- }
252
- function findBestStatMatch(searchTerm, normalizedStats, similarityThreshold = MIN_THRESHOLD_MATCH) {
253
- const exact = normalizedStats.get(searchTerm);
254
- if (exact) return exact;
255
- const candidates = [];
256
- for (const [normalizedKey, original] of normalizedStats) {
257
- if (normalizedKey.startsWith(searchTerm))
258
- candidates.push([original, calculateSimilarity(searchTerm, normalizedKey)]);
259
- }
260
- if (candidates.length === 1) return candidates[0][0];
261
- if (candidates.length > 0) {
262
- candidates.sort((a, b) => b[1] - a[1]);
263
- if (candidates[0][1] >= similarityThreshold) return candidates[0][0];
264
- }
265
- let bestMatch;
266
- let bestSimilarity = 0;
267
- for (const [normalizedKey, original] of normalizedStats) {
268
- const similarity = calculateSimilarity(searchTerm, normalizedKey);
269
- if (similarity === 1) return original;
270
- if (similarity > bestSimilarity && similarity >= similarityThreshold) {
271
- bestSimilarity = similarity;
272
- bestMatch = original;
273
- }
237
+ try {
238
+ if (!roll(replaceFormulaInDice(replaceExpByRandom(dice)), engine, pity))
239
+ throw new DiceTypeError(dice, "evalStatsDice", "no roll result");
240
+ return testDice;
241
+ } catch (error) {
242
+ throw new DiceTypeError(dice, "evalStatsDice", error);
274
243
  }
275
- return bestMatch;
276
244
  }
277
- function findBestRecord(record, searchTerm, similarityThreshold = MIN_THRESHOLD_MATCH) {
278
- const normalizeRecord = /* @__PURE__ */ new Map();
279
- for (const key of Object.keys(record)) {
280
- normalizeRecord.set(key.standardize(), key);
245
+ function diceRandomParse(value, template, engine = NumberGenerator2.engines.nodeCrypto) {
246
+ if (!template.statistics) return replaceFormulaInDice(value.standardize());
247
+ value = value.standardize();
248
+ const statNames = Object.keys(template.statistics);
249
+ let newDice = value;
250
+ for (const name of statNames) {
251
+ const regex = new RegExp(escapeRegex(name.standardize()), "gi");
252
+ if (value.match(regex)) {
253
+ let max;
254
+ let min;
255
+ const foundStat = template.statistics?.[name];
256
+ if (foundStat) {
257
+ max = foundStat.max;
258
+ min = foundStat.min;
259
+ }
260
+ const total = template.total || 100;
261
+ const randomStatValue = generateRandomStat(total, max, min, engine);
262
+ newDice = value.replace(regex, randomStatValue.toString());
263
+ }
281
264
  }
282
- return findBestStatMatch(
283
- searchTerm.standardize(),
284
- normalizeRecord,
285
- similarityThreshold
286
- ) || null;
265
+ return replaceFormulaInDice(newDice);
287
266
  }
288
- function replaceUnknown(dice, replacer) {
289
- return dice.replaceAll(REMOVER_PATTERN.STAT_MATCHER, replacer).replaceAll("+0", "").replaceAll("-0", "");
267
+ function diceTypeRandomParse(dice, template, engine = NumberGenerator2.engines.nodeCrypto) {
268
+ dice = replaceExpByRandom(dice);
269
+ if (!template.statistics) return dice;
270
+ const firstStatNotcombinaison = Object.keys(template.statistics).find(
271
+ (stat) => !template.statistics?.[stat].combinaison
272
+ );
273
+ if (!firstStatNotcombinaison) return dice;
274
+ const stats = template.statistics[firstStatNotcombinaison];
275
+ const { min, max } = stats;
276
+ const total = template.total || 100;
277
+ const randomStatValue = generateRandomStat(total, max, min, engine);
278
+ return replaceFormulaInDice(dice.replaceAll("$", randomStatValue.toString()));
290
279
  }
291
- function verifyStatMatcherPattern(dice, replaceUnknow) {
292
- if (REMOVER_PATTERN.STAT_MATCHER.test(dice)) {
293
- if (replaceUnknow)
294
- return replaceUnknown(dice, replaceUnknow);
295
- const matched = dice.matchAll(new RegExp(REMOVER_PATTERN.STAT_MATCHER));
296
- const stats = matched ? Array.from(matched, (m) => m?.[0]).map((s) => `\`${s}\``).join(", ") : "unknown";
297
- throw new DiceTypeError(stats, "unknown_stats");
280
+ function evalCombinaison(combinaison, stats) {
281
+ const newStats = {};
282
+ for (const [stat, combin] of Object.entries(combinaison)) {
283
+ let formula = combin.standardize();
284
+ for (const [statName, value] of Object.entries(stats)) {
285
+ const regex = new RegExp(statName.standardize(), "gi");
286
+ formula = formula.replace(regex, value.toString());
287
+ }
288
+ try {
289
+ newStats[stat] = evaluate(formula);
290
+ } catch (error) {
291
+ throw new FormulaError(stat, "evalCombinaison", error);
292
+ }
298
293
  }
299
- return dice.replaceAll("+0", "").replaceAll("-0", "");
294
+ return newStats;
300
295
  }
301
-
302
- // src/utils.ts
303
- function escapeRegex(string) {
304
- return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
296
+ function evalOneCombinaison(combinaison, stats) {
297
+ let formula = combinaison.standardize();
298
+ for (const [statName, value] of Object.entries(stats)) {
299
+ const regex = new RegExp(statName.standardize(), "gi");
300
+ formula = formula.replace(regex, value.toString());
301
+ }
302
+ try {
303
+ return evaluate(formula);
304
+ } catch (error) {
305
+ throw new FormulaError(combinaison, "evalOneCombinaison", error);
306
+ }
305
307
  }
306
- function standardizeDice(dice) {
307
- return dice.replace(
308
- /(\[[^\]]+])|([^[]+)/g,
309
- (_match, insideBrackets, outsideText) => insideBrackets ? insideBrackets : outsideText.standardize().replaceAll("df", "dF")
310
- );
308
+ function convertNumber(number) {
309
+ if (number === void 0 || number === null) return void 0;
310
+ if (number.toString().length === 0 || Number.isNaN(Number.parseInt(number.toString(), 10)))
311
+ return void 0;
312
+ if (isNumber(number)) return Number.parseInt(number.toString(), 10);
313
+ return void 0;
311
314
  }
312
- function handleDiceAfterD(tokenStd, normalizedStats) {
313
- const diceMatch = /^(\d*)d(.+)$/i.exec(tokenStd);
314
- if (!diceMatch) return null;
315
- const diceCount = diceMatch[1] || "";
316
- const afterD = diceMatch[2];
317
- const bestMatch = findBestStatMatch(afterD, normalizedStats, 1);
318
- if (bestMatch) {
319
- const [, value] = bestMatch;
320
- return `${diceCount}d${value.toString()}`;
315
+ function verifyTemplateValue(template, verify = true, engine = NumberGenerator2.engines.nodeCrypto) {
316
+ const parsedTemplate = templateSchema.parse(template);
317
+ const { success, failure } = parsedTemplate.critical ?? {};
318
+ const criticicalVal = {
319
+ success: convertNumber(success),
320
+ failure: convertNumber(failure)
321
+ };
322
+ const statistiqueTemplate = {
323
+ diceType: parsedTemplate.diceType,
324
+ statistics: parsedTemplate.statistics,
325
+ critical: criticicalVal,
326
+ total: parsedTemplate.total,
327
+ charName: parsedTemplate.charName,
328
+ damage: parsedTemplate.damage,
329
+ customCritical: parsedTemplate.customCritical,
330
+ forceDistrib: parsedTemplate.forceDistrib
331
+ };
332
+ if (!verify) return statistiqueTemplate;
333
+ if (statistiqueTemplate.diceType) {
334
+ if (statistiqueTemplate.diceType.match(DETECT_CRITICAL)) {
335
+ throw new DiceTypeError(
336
+ statistiqueTemplate.diceType,
337
+ "critical_dice_type",
338
+ "contains critical detection: should be in custom critical instead"
339
+ );
340
+ }
341
+ const cleanedDice2 = diceTypeRandomParse(
342
+ statistiqueTemplate.diceType,
343
+ statistiqueTemplate,
344
+ engine
345
+ );
346
+ const rolled = roll(cleanedDice2, engine);
347
+ if (!rolled) throw new DiceTypeError(cleanedDice2, "no_roll_result", "no roll result");
321
348
  }
322
- return null;
323
- }
324
- function handleSimpleToken(tokenStd, token, normalizedStats, minThreshold) {
325
- const bestMatch = findBestStatMatch(tokenStd, normalizedStats, minThreshold);
326
- if (bestMatch) {
327
- const [, value] = bestMatch;
328
- return value.toString();
349
+ if (statistiqueTemplate.customCritical) {
350
+ if (!statistiqueTemplate.diceType) {
351
+ throw new DiceTypeError("no_dice_type", "no_dice_type", "no dice type");
352
+ }
353
+ const customCritical = statistiqueTemplate.customCritical;
354
+ for (const [, custom] of Object.entries(customCritical)) {
355
+ const cleanedDice2 = createCriticalCustom(
356
+ statistiqueTemplate.diceType,
357
+ custom,
358
+ statistiqueTemplate,
359
+ engine
360
+ );
361
+ const rolled = roll(cleanedDice2, engine);
362
+ if (!rolled)
363
+ throw new DiceTypeError(cleanedDice2, "verifyTemplateValue", "no roll result");
364
+ }
329
365
  }
330
- return token;
366
+ testDiceRegistered(statistiqueTemplate, engine);
367
+ testStatCombinaison(statistiqueTemplate, engine);
368
+ return statistiqueTemplate;
331
369
  }
332
- function generateStatsDice(originalDice, stats, minThreshold = 0.6, dollarValue) {
333
- let dice = originalDice.standardize();
334
- if (stats && Object.keys(stats).length > 0) {
335
- const normalizedStats = /* @__PURE__ */ new Map();
336
- for (const [key, value] of Object.entries(stats)) {
337
- const normalized = key.standardize();
338
- normalizedStats.set(normalized, [key, value]);
339
- }
340
- const partsRegex = /(\[[^\]]+])|([^[]+)/g;
341
- let result = "";
342
- let match;
343
- while ((match = partsRegex.exec(dice)) !== null) {
344
- const insideBrackets = match[1];
345
- const outsideText = match[2];
346
- if (insideBrackets) {
347
- result += insideBrackets;
348
- continue;
349
- }
350
- if (!outsideText) {
351
- continue;
352
- }
353
- const tokenRegex = /(\$?[\p{L}\p{N}_.]+)/gu;
354
- let lastIndex = 0;
355
- let tokenMatch;
356
- while ((tokenMatch = tokenRegex.exec(outsideText)) !== null) {
357
- result += outsideText.slice(lastIndex, tokenMatch.index);
358
- const token = tokenMatch[0];
359
- const tokenHasDollar = token.startsWith("$");
360
- const tokenForCompare = tokenHasDollar ? token.slice(1) : token;
361
- const tokenStd = tokenForCompare.standardize();
362
- const diceReplacement = handleDiceAfterD(tokenStd, normalizedStats);
363
- if (diceReplacement) {
364
- result += diceReplacement;
365
- lastIndex = tokenRegex.lastIndex;
366
- continue;
367
- }
368
- result += handleSimpleToken(tokenStd, token, normalizedStats, minThreshold);
369
- lastIndex = tokenRegex.lastIndex;
370
- }
371
- result += outsideText.slice(lastIndex);
370
+ function testDiceRegistered(template, engine = NumberGenerator2.engines.nodeCrypto) {
371
+ if (!template.damage) return;
372
+ if (Object.keys(template.damage).length === 0) throw new EmptyObjectError();
373
+ if (Object.keys(template.damage).length > 25) throw new TooManyDice();
374
+ for (const [name, dice] of Object.entries(template.damage)) {
375
+ if (!dice) continue;
376
+ const diceReplaced = replaceExpByRandom(dice);
377
+ const randomDiceParsed = diceRandomParse(diceReplaced, template, engine);
378
+ try {
379
+ const rolled = roll(randomDiceParsed, engine);
380
+ if (!rolled) throw new DiceTypeError(name, "no_roll_result", dice);
381
+ } catch (error) {
382
+ throw new DiceTypeError(name, "testDiceRegistered", error);
372
383
  }
373
- dice = result;
374
384
  }
375
- if (dollarValue) dice = dice.replaceAll("$", dollarValue);
376
- return replaceFormulaInDice(dice);
377
385
  }
378
- function replaceFormulaInDice(dice) {
379
- const formula = /(?<formula>\{{2}(.+?)}{2})/gim;
380
- let match;
381
- let modifiedDice = dice;
382
- while ((match = formula.exec(dice)) !== null) {
383
- if (match.groups?.formula) {
384
- const formulae = match.groups.formula.replaceAll("{{", "").replaceAll("}}", "");
385
- try {
386
- const result = evaluate(formulae);
387
- modifiedDice = modifiedDice.replace(match.groups.formula, result.toString());
388
- } catch (error) {
389
- throw new FormulaError(match.groups.formula, "replaceFormulasInDice", error);
390
- }
386
+ function testStatCombinaison(template, engine = NumberGenerator2.engines.nodeCrypto) {
387
+ if (!template.statistics) return;
388
+ const onlycombinaisonStats = Object.fromEntries(
389
+ Object.entries(template.statistics).filter(
390
+ ([_, value]) => value.combinaison !== void 0
391
+ )
392
+ );
393
+ const allOtherStats = Object.fromEntries(
394
+ Object.entries(template.statistics).filter(([_, value]) => !value.combinaison)
395
+ );
396
+ if (Object.keys(onlycombinaisonStats).length === 0) return;
397
+ const allStats = Object.keys(template.statistics).filter(
398
+ (stat) => !template.statistics[stat].combinaison
399
+ );
400
+ if (allStats.length === 0) throw new NoStatisticsError();
401
+ const error = [];
402
+ for (const [stat, value] of Object.entries(onlycombinaisonStats)) {
403
+ let formula = value.combinaison;
404
+ for (const [other, data] of Object.entries(allOtherStats)) {
405
+ const { max, min } = data;
406
+ const total = template.total || 100;
407
+ const randomStatValue = generateRandomStat(total, max, min, engine);
408
+ const regex = new RegExp(other, "gi");
409
+ formula = formula.replace(regex, randomStatValue.toString());
410
+ }
411
+ try {
412
+ evaluate(formula);
413
+ } catch (e) {
414
+ error.push(stat);
391
415
  }
392
416
  }
393
- return cleanedDice(modifiedDice);
417
+ if (error.length > 0) throw new FormulaError(error.join(", "), "testStatCombinaison");
418
+ return;
394
419
  }
395
- function cleanedDice(dice) {
396
- return dice.replaceAll("+-", "-").replaceAll("--", "+").replaceAll("++", "+").replaceAll("=>", ">=").replaceAll("=<", "<=").trimEnd();
420
+ function generateRandomStat(total = 100, max, min, engine = NumberGenerator2.engines.nodeCrypto) {
421
+ let randomStatValue = total + 1;
422
+ const random = new Random(engine || NumberGenerator2.engines.nodeCrypto);
423
+ while (randomStatValue >= total || randomStatValue === 0) {
424
+ if (max && min) randomStatValue = randomInt(min, max, engine, random);
425
+ else if (max) randomStatValue = randomInt(1, max, engine, random);
426
+ else if (min) randomStatValue = randomInt(min, total, engine, random);
427
+ else randomStatValue = randomInt(1, total, engine, random);
428
+ }
429
+ return randomStatValue;
430
+ }
431
+
432
+ // src/utils.ts
433
+ function splitDiceComment(dice) {
434
+ const match = /\s+(#|\/{2}|\[|\/\*)(?<comment>.*)/i.exec(dice);
435
+ if (!match?.groups) return { dice: dice.trimEnd(), comment: void 0 };
436
+ const comment = match.groups.comment.trim() || void 0;
437
+ return { dice: dice.slice(0, match.index).trimEnd(), comment };
438
+ }
439
+ function escapeRegex(string) {
440
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
441
+ }
442
+ function standardizeDice(dice) {
443
+ return dice.replace(
444
+ /(\[[^\]]+])|([^[]+)/g,
445
+ (_match, insideBrackets, outsideText) => insideBrackets ? insideBrackets : outsideText.standardize().replaceAll("df", "dF")
446
+ );
397
447
  }
398
448
  function isNumber(value) {
399
449
  return value !== void 0 && (typeof value === "number" || !Number.isNaN(Number(value)) && typeof value === "string" && value.trim().length > 0);
400
450
  }
401
- function replaceExpByRandom(dice, engine = NumberGenerator2.engines.nodeCrypto) {
451
+ function replaceExpByRandom(dice, engine = NumberGenerator3.engines.nodeCrypto) {
402
452
  const diceRegex = /\{exp( ?\|\| ?(?<default>\d+))?}/gi;
403
453
  return dice.replace(diceRegex, (_match, _p1, _p2, _offset, _string, groups) => {
404
454
  const defaultValue = groups?.default;
405
455
  return defaultValue ?? randomInt(1, 999, engine).toString();
406
456
  });
407
457
  }
408
- function randomInt(min, max, engine = NumberGenerator2.engines.nodeCrypto, rng) {
409
- if (!rng) rng = new Random(engine || void 0);
458
+ function randomInt(min, max, engine = NumberGenerator3.engines.nodeCrypto, rng) {
459
+ if (!rng) rng = new Random2(engine || void 0);
410
460
  return rng.integer(min, max);
411
461
  }
412
- function createCriticalCustom(dice, customCritical, template, engine = NumberGenerator2.engines.nodeCrypto) {
462
+ function createCriticalCustom(dice, customCritical, template, engine = NumberGenerator3.engines.nodeCrypto) {
413
463
  const compareRegex = dice.match(SIGN_REGEX_SPACE);
414
464
  let customDice = dice;
415
465
  const compareValue = diceTypeRandomParse(customCritical.value, template, engine);
@@ -422,6 +472,8 @@ function createCriticalCustom(dice, customCritical, template, engine = NumberGen
422
472
  }
423
473
 
424
474
  // src/dice/compare.ts
475
+ import { NumberGenerator as NumberGenerator4 } from "@dice-roller/rpg-dice-roller";
476
+ import { evaluate as evaluate2 } from "mathjs";
425
477
  function isTrivialComparison(maxValue, minValue, compare) {
426
478
  const canSucceed = canComparisonSucceed(maxValue, compare, minValue);
427
479
  const canFail = canComparisonFail(maxValue, compare, minValue);
@@ -452,7 +504,7 @@ function canComparisonFail(maxRollValue, compare, minRollValue = 1) {
452
504
  return true;
453
505
  }
454
506
  }
455
- function rollCompare(value, engine = NumberGenerator3.engines.nodeCrypto, pity) {
507
+ function rollCompare(value, engine = NumberGenerator4.engines.nodeCrypto, pity) {
456
508
  if (isNumber(value)) return { value: Number.parseInt(value, 10) };
457
509
  if (!value || typeof value === "string" && value.trim() === "") {
458
510
  return { value: 0, diceResult: value };
@@ -471,7 +523,7 @@ function rollCompare(value, engine = NumberGenerator3.engines.nodeCrypto, pity)
471
523
  diceResult: rollComp?.result
472
524
  };
473
525
  }
474
- function getCompare(dice, compareRegex, engine = NumberGenerator3.engines.nodeCrypto, pity) {
526
+ function getCompare(dice, compareRegex, engine = NumberGenerator4.engines.nodeCrypto, pity) {
475
527
  if (dice.match(/((\{.*,(.*)+\}|([><=!]+\d+f))([><=]|!=)+\d+\}?)|\{(.*)(([><=]|!=)+).*\}/))
476
528
  return { dice, compare: void 0 };
477
529
  dice = dice.replace(SIGN_REGEX_SPACE, "");
@@ -527,7 +579,7 @@ function canComparisonSucceed(maxRollValue, compare, minRollValue) {
527
579
  import { evaluate as evaluate4 } from "mathjs";
528
580
 
529
581
  // src/dice/signs.ts
530
- import { NumberGenerator as NumberGenerator4 } from "@dice-roller/rpg-dice-roller";
582
+ import { NumberGenerator as NumberGenerator5 } from "@dice-roller/rpg-dice-roller";
531
583
  import { evaluate as evaluate3 } from "mathjs";
532
584
 
533
585
  // src/dice/replace.ts
@@ -617,7 +669,7 @@ function inverseSign(sign) {
617
669
  return "==";
618
670
  }
619
671
  }
620
- function compareSignFormule(toRoll, compareRegex, element, diceResult, engine = NumberGenerator4.engines.nodeCrypto, pity, rollBounds) {
672
+ function compareSignFormule(toRoll, compareRegex, element, diceResult, engine = NumberGenerator5.engines.nodeCrypto, pity, rollBounds) {
621
673
  let results = "";
622
674
  let trivial = false;
623
675
  const compareResult = getCompare(toRoll, compareRegex, engine);
@@ -678,43 +730,278 @@ function normalizeExplodingSuccess(dice) {
678
730
  parsedValue = 0;
679
731
  }
680
732
  }
681
- const normalizedSegment = "!";
682
- const replacedDice = dice.replace(match[0], normalizedSegment);
683
- return {
684
- dice: replacedDice,
685
- originalDice: dice,
686
- sign: normalizedSign,
687
- value: parsedValue,
688
- normalizedSegment,
689
- originalSegment: match[0]
690
- };
733
+ const normalizedSegment = "!";
734
+ const replacedDice = dice.replace(match[0], normalizedSegment);
735
+ return {
736
+ dice: replacedDice,
737
+ originalDice: dice,
738
+ sign: normalizedSign,
739
+ value: parsedValue,
740
+ normalizedSegment,
741
+ originalSegment: match[0]
742
+ };
743
+ }
744
+ function countExplodingSuccesses(diceRoll, sign, value) {
745
+ const rollsArray = Array.isArray(diceRoll) ? diceRoll : [diceRoll];
746
+ const flatValues = [];
747
+ for (const dr of rollsArray) {
748
+ const groups = dr.rolls ?? [];
749
+ for (const group of groups) {
750
+ const innerRolls = group.rolls ?? [];
751
+ for (const roll2 of innerRolls) {
752
+ if (typeof roll2.value === "number") flatValues.push(roll2.value);
753
+ }
754
+ }
755
+ }
756
+ return flatValues.reduce(
757
+ (acc, current) => acc + (matchComparison(sign, current, value) ? 1 : 0),
758
+ 0
759
+ );
760
+ }
761
+
762
+ // src/dice/extract.ts
763
+ import { DiceRoller, NumberGenerator as NumberGenerator6 } from "@dice-roller/rpg-dice-roller";
764
+
765
+ // src/similarities/generateStatsDice.ts
766
+ import { evaluate as evaluate5 } from "mathjs";
767
+
768
+ // src/similarities/similarity.ts
769
+ function calculateSimilarity(str1, str2) {
770
+ const longer = str1.length > str2.length ? str1 : str2;
771
+ const shorter = str1.length > str2.length ? str2 : str1;
772
+ if (longer.length === 0) return 1;
773
+ const distance = levenshteinDistance(longer, shorter);
774
+ return (longer.length - distance) / longer.length;
775
+ }
776
+ function levenshteinDistance(str1, str2) {
777
+ const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
778
+ for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
779
+ for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
780
+ for (let j = 1; j <= str2.length; j++) {
781
+ for (let i = 1; i <= str1.length; i++) {
782
+ const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
783
+ matrix[j][i] = Math.min(
784
+ matrix[j][i - 1] + 1,
785
+ // insertion
786
+ matrix[j - 1][i] + 1,
787
+ // deletion
788
+ matrix[j - 1][i - 1] + cost
789
+ // substitution
790
+ );
791
+ }
792
+ }
793
+ return matrix[str2.length][str1.length];
794
+ }
795
+ function findBestStatMatch(searchTerm, normalizedStats, similarityThreshold = MIN_THRESHOLD_MATCH) {
796
+ const exact = normalizedStats.get(searchTerm);
797
+ if (exact) return exact;
798
+ const candidates = [];
799
+ for (const [normalizedKey, original] of normalizedStats) {
800
+ if (normalizedKey.startsWith(searchTerm))
801
+ candidates.push([original, calculateSimilarity(searchTerm, normalizedKey)]);
802
+ }
803
+ if (candidates.length === 1) return candidates[0][0];
804
+ if (candidates.length > 0) {
805
+ candidates.sort((a, b) => b[1] - a[1]);
806
+ if (candidates[0][1] >= similarityThreshold) return candidates[0][0];
807
+ }
808
+ let bestMatch;
809
+ let bestSimilarity = 0;
810
+ for (const [normalizedKey, original] of normalizedStats) {
811
+ const similarity = calculateSimilarity(searchTerm, normalizedKey);
812
+ if (similarity === 1) return original;
813
+ if (similarity > bestSimilarity && similarity >= similarityThreshold) {
814
+ bestSimilarity = similarity;
815
+ bestMatch = original;
816
+ }
817
+ }
818
+ return bestMatch;
819
+ }
820
+ function findBestRecord(record, searchTerm, similarityThreshold = MIN_THRESHOLD_MATCH) {
821
+ const normalizeRecord = /* @__PURE__ */ new Map();
822
+ for (const key of Object.keys(record)) {
823
+ normalizeRecord.set(key.standardize(), key);
824
+ }
825
+ return findBestStatMatch(
826
+ searchTerm.standardize(),
827
+ normalizeRecord,
828
+ similarityThreshold
829
+ ) || null;
830
+ }
831
+ function replaceUnknown(dice, replacer) {
832
+ return dice.replaceAll(REMOVER_PATTERN.STAT_MATCHER, replacer).replaceAll("+0", "").replaceAll("-0", "");
833
+ }
834
+ function verifyStatMatcherPattern(dice, replaceUnknow) {
835
+ if (REMOVER_PATTERN.STAT_MATCHER.test(dice)) {
836
+ if (replaceUnknow)
837
+ return replaceUnknown(dice, replaceUnknow);
838
+ const matched = dice.matchAll(new RegExp(REMOVER_PATTERN.STAT_MATCHER));
839
+ const stats = matched ? Array.from(matched, (m) => m?.[0]).map((s) => `\`${s}\``).join(", ") : "unknown";
840
+ throw new DiceTypeError(stats, "unknown_stats");
841
+ }
842
+ return dice.replaceAll("+0", "").replaceAll("-0", "");
843
+ }
844
+
845
+ // src/similarities/generateStatsDice.ts
846
+ function handleDiceAfterD(tokenStd, normalizedStats) {
847
+ const diceMatch = /^(\d*)d(.+)$/i.exec(tokenStd);
848
+ if (!diceMatch) return null;
849
+ const diceCount = diceMatch[1] || "";
850
+ const afterD = diceMatch[2];
851
+ const bestMatch = findBestStatMatch(afterD, normalizedStats, 1);
852
+ if (bestMatch) {
853
+ const [, value] = bestMatch;
854
+ return `${diceCount}d${value.toString()}`;
855
+ }
856
+ return null;
857
+ }
858
+ function handleSimpleToken(tokenStd, token, normalizedStats, minThreshold) {
859
+ const bestMatch = findBestStatMatch(tokenStd, normalizedStats, minThreshold);
860
+ if (bestMatch) {
861
+ const [, value] = bestMatch;
862
+ return value.toString();
863
+ }
864
+ return token;
865
+ }
866
+ function generateStatsDice(originalDice, stats, minThreshold = 0.6, dollarValue) {
867
+ let dice = originalDice.standardize();
868
+ if (stats && Object.keys(stats).length > 0) {
869
+ const normalizedStats = /* @__PURE__ */ new Map();
870
+ for (const [key, value] of Object.entries(stats)) {
871
+ const normalized = key.standardize();
872
+ normalizedStats.set(normalized, [key, value]);
873
+ }
874
+ const partsRegex = /(\[[^\]]+])|([^[]+)/g;
875
+ let result = "";
876
+ let match;
877
+ while ((match = partsRegex.exec(dice)) !== null) {
878
+ const insideBrackets = match[1];
879
+ const outsideText = match[2];
880
+ if (insideBrackets) {
881
+ result += insideBrackets;
882
+ continue;
883
+ }
884
+ if (!outsideText) {
885
+ continue;
886
+ }
887
+ const tokenRegex = /(\$?[\p{L}\p{N}_.]+)/gu;
888
+ let lastIndex = 0;
889
+ let tokenMatch;
890
+ while ((tokenMatch = tokenRegex.exec(outsideText)) !== null) {
891
+ result += outsideText.slice(lastIndex, tokenMatch.index);
892
+ const token = tokenMatch[0];
893
+ const tokenHasDollar = token.startsWith("$");
894
+ const tokenForCompare = tokenHasDollar ? token.slice(1) : token;
895
+ const tokenStd = tokenForCompare.standardize();
896
+ const diceReplacement = handleDiceAfterD(tokenStd, normalizedStats);
897
+ if (diceReplacement) {
898
+ result += diceReplacement;
899
+ lastIndex = tokenRegex.lastIndex;
900
+ continue;
901
+ }
902
+ result += handleSimpleToken(tokenStd, token, normalizedStats, minThreshold);
903
+ lastIndex = tokenRegex.lastIndex;
904
+ }
905
+ result += outsideText.slice(lastIndex);
906
+ }
907
+ dice = result;
908
+ }
909
+ if (dollarValue) dice = dice.replaceAll("$", dollarValue);
910
+ return replaceFormulaInDice(dice);
911
+ }
912
+ function replaceFormulaInDice(dice) {
913
+ const formula = /(?<formula>\{{2}(.+?)}{2})/gim;
914
+ let match;
915
+ let modifiedDice = dice;
916
+ while ((match = formula.exec(dice)) !== null) {
917
+ if (match.groups?.formula) {
918
+ const formulae = match.groups.formula.replaceAll("{{", "").replaceAll("}}", "");
919
+ try {
920
+ const result = evaluate5(formulae);
921
+ modifiedDice = modifiedDice.replace(match.groups.formula, result.toString());
922
+ } catch (error) {
923
+ throw new FormulaError(match.groups.formula, "replaceFormulasInDice", error);
924
+ }
925
+ }
926
+ }
927
+ return cleanedDice(modifiedDice);
691
928
  }
692
- function countExplodingSuccesses(diceRoll, sign, value) {
693
- const rollsArray = Array.isArray(diceRoll) ? diceRoll : [diceRoll];
694
- const flatValues = [];
695
- for (const dr of rollsArray) {
696
- const groups = dr.rolls ?? [];
697
- for (const group of groups) {
698
- const innerRolls = group.rolls ?? [];
699
- for (const roll2 of innerRolls) {
700
- if (typeof roll2.value === "number") flatValues.push(roll2.value);
929
+ function cleanedDice(dice) {
930
+ return dice.replaceAll("+-", "-").replaceAll("--", "+").replaceAll("++", "+").replaceAll("=>", ">=").replaceAll("=<", "<=").trimEnd();
931
+ }
932
+
933
+ // src/similarities/resolveFormula.ts
934
+ import { evaluate as evaluate6 } from "mathjs";
935
+ function toFiniteNumber(value) {
936
+ if (typeof value === "number" && Number.isFinite(value)) return value;
937
+ return void 0;
938
+ }
939
+ function substituteFormulaTokens(expr, resolvedStats, similarityThreshold = MIN_THRESHOLD_MATCH) {
940
+ return expr.replace(/([\p{L}\p{M}._-]+)/gu, (token) => {
941
+ const match = findBestStatMatch(token, resolvedStats, similarityThreshold);
942
+ return match !== void 0 ? match.toString() : token;
943
+ });
944
+ }
945
+ function resolveFormulaHint(formula, allAttributes, similarityThreshold = MIN_THRESHOLD_MATCH) {
946
+ const trimmed = formula.trim();
947
+ if (!trimmed) return { kind: "not-formula" };
948
+ if (isNumber(trimmed)) return { kind: "not-formula" };
949
+ const resolved = /* @__PURE__ */ new Map();
950
+ const pending = /* @__PURE__ */ new Map();
951
+ for (const [name, val] of Object.entries(allAttributes)) {
952
+ const norm = name.standardize();
953
+ if (typeof val === "number") {
954
+ if (Number.isFinite(val)) resolved.set(norm, val);
955
+ continue;
956
+ }
957
+ const t = val.trim();
958
+ if (!t) continue;
959
+ if (isNumber(t)) {
960
+ const numeric = Number(t);
961
+ if (Number.isFinite(numeric)) resolved.set(norm, numeric);
962
+ } else {
963
+ pending.set(norm, t.standardize());
964
+ }
965
+ }
966
+ for (const [normName, expr2] of pending) {
967
+ pending.set(normName, substituteFormulaTokens(expr2, resolved, similarityThreshold));
968
+ }
969
+ let progress = true;
970
+ while (pending.size > 0 && progress) {
971
+ progress = false;
972
+ for (const [normName, expr2] of pending) {
973
+ try {
974
+ const result = toFiniteNumber(evaluate6(expr2));
975
+ if (result === void 0) continue;
976
+ resolved.set(normName, result);
977
+ pending.delete(normName);
978
+ progress = true;
979
+ for (const [otherNorm, otherExpr] of pending) {
980
+ pending.set(
981
+ otherNorm,
982
+ substituteFormulaTokens(otherExpr, resolved, similarityThreshold)
983
+ );
984
+ }
985
+ } catch {
701
986
  }
702
987
  }
703
988
  }
704
- return flatValues.reduce(
705
- (acc, current) => acc + (matchComparison(sign, current, value) ? 1 : 0),
706
- 0
707
- );
989
+ const normFormula = trimmed.standardize();
990
+ const expr = substituteFormulaTokens(normFormula, resolved, similarityThreshold);
991
+ try {
992
+ const result = toFiniteNumber(evaluate6(expr));
993
+ if (result !== void 0) return { kind: "resolved", value: result };
994
+ return { kind: "error" };
995
+ } catch {
996
+ return { kind: "error" };
997
+ }
708
998
  }
709
999
 
710
- // src/dice/extract.ts
711
- import { DiceRoller, NumberGenerator as NumberGenerator5 } from "@dice-roller/rpg-dice-roller";
712
-
713
1000
  // src/dice/calculator.ts
714
- import { evaluate as evaluate5 } from "mathjs";
1001
+ import { evaluate as evaluate7 } from "mathjs";
715
1002
  function calculator(sign, value, total) {
716
1003
  if (sign === "^") sign = "**";
717
- return evaluate5(`${total} ${sign} ${value}`);
1004
+ return evaluate7(`${total} ${sign} ${value}`);
718
1005
  }
719
1006
 
720
1007
  // src/dice/extract.ts
@@ -749,10 +1036,10 @@ function extractValuesFromOutput(output) {
749
1036
  }
750
1037
  return values;
751
1038
  }
752
- function getRollBounds(dice, engine = NumberGenerator5.engines.nodeCrypto) {
1039
+ function getRollBounds(dice, engine = NumberGenerator6.engines.nodeCrypto) {
753
1040
  try {
754
1041
  const roller = new DiceRoller();
755
- NumberGenerator5.generator.engine = engine;
1042
+ NumberGenerator6.generator.engine = engine;
756
1043
  const rollResult = roller.roll(dice);
757
1044
  const instance = Array.isArray(rollResult) ? rollResult[0] : rollResult;
758
1045
  const { minTotal, maxTotal } = instance;
@@ -777,7 +1064,7 @@ function setSortOrder(toRoll, sort) {
777
1064
  }
778
1065
  function prepareDice(diceInput) {
779
1066
  let dice = standardizeDice(replaceFormulaInDice(diceInput)).replace(/^\+/, "").replaceAll("=>", ">=").replaceAll("=<", "<=").trimStart();
780
- dice = dice.replaceAll(DETECT_CRITICAL, "").trimEnd();
1067
+ dice = dice.replaceAll(REMOVER_PATTERN.CRITICAL_BLOCK, "").trimEnd();
781
1068
  const explodingSuccess = normalizeExplodingSuccess(dice);
782
1069
  if (explodingSuccess) dice = explodingSuccess.dice;
783
1070
  let diceDisplay;
@@ -829,9 +1116,8 @@ function handleBulkRolls(dice, isCurlyBulk, bulkContent, compare, explodingSucce
829
1116
  const bulkProcessContent = isCurlyBulk ? bulkContent : dice;
830
1117
  const diceArray = bulkProcessContent.split("#");
831
1118
  const numberOfDice = Number.parseInt(diceArray[0], 10);
832
- let diceToRoll = diceArray[1].replace(COMMENT_REGEX, "");
833
- const commentsMatch = diceArray[1].match(COMMENT_REGEX);
834
- const comments = commentsMatch ? commentsMatch[2] : void 0;
1119
+ const { dice: diceToRollBase, comment: comments } = splitDiceComment(diceArray[1]);
1120
+ let diceToRoll = diceToRollBase;
835
1121
  let curlyCompare;
836
1122
  if (isCurlyBulk) {
837
1123
  const curlyCompareRegex = diceToRoll.match(SIGN_REGEX_SPACE);
@@ -866,7 +1152,7 @@ function handleBulkRolls(dice, isCurlyBulk, bulkContent, compare, explodingSucce
866
1152
  );
867
1153
  }
868
1154
  const roller = new DiceRoller2();
869
- NumberGenerator6.generator.engine = engine;
1155
+ NumberGenerator7.generator.engine = engine;
870
1156
  for (let i = 0; i < numberOfDice; i++) {
871
1157
  try {
872
1158
  roller.roll(diceToRoll);
@@ -889,7 +1175,7 @@ function handleBulkRollsWithComparison(numberOfDice, diceToRoll, comments, activ
889
1175
  const results = [];
890
1176
  let successCount = 0;
891
1177
  const roller = new DiceRoller2();
892
- NumberGenerator6.generator.engine = engine;
1178
+ NumberGenerator7.generator.engine = engine;
893
1179
  let trivialComparisonDetected = false;
894
1180
  const formatOutput = (output, addStar) => {
895
1181
  const formatted = addStar && isCurlyBulk ? output.replace(
@@ -923,7 +1209,7 @@ function handleBulkRollsWithComparison(numberOfDice, diceToRoll, comments, activ
923
1209
  results.push(formattedRollOutput);
924
1210
  } else {
925
1211
  const rollTotal = rollInstance.total;
926
- const isSuccess = evaluate6(
1212
+ const isSuccess = evaluate8(
927
1213
  `${rollTotal}${activeCompare.sign}${activeCompare.value}`
928
1214
  );
929
1215
  if (isSuccess) successCount++;
@@ -986,7 +1272,7 @@ function handleBulkRollsWithComparison(numberOfDice, diceToRoll, comments, activ
986
1272
  }
987
1273
 
988
1274
  // src/dice/pity.ts
989
- import { evaluate as evaluate7 } from "mathjs";
1275
+ import { evaluate as evaluate9 } from "mathjs";
990
1276
  function handlePitySystem(dice, compare, diceRoll, roller, engine) {
991
1277
  const currentRoll = Array.isArray(diceRoll) ? diceRoll[0] : diceRoll;
992
1278
  const maxPossible = currentRoll ? currentRoll.maxTotal : null;
@@ -994,7 +1280,7 @@ function handlePitySystem(dice, compare, diceRoll, roller, engine) {
994
1280
  if (!isComparisonPossible) {
995
1281
  return { rerollCount: 0 };
996
1282
  }
997
- let isFail = evaluate7(`${roller.total}${compare.sign}${compare.value}`);
1283
+ let isFail = evaluate9(`${roller.total}${compare.sign}${compare.value}`);
998
1284
  if (isFail) {
999
1285
  return { rerollCount: 0 };
1000
1286
  }
@@ -1009,14 +1295,14 @@ function handlePitySystem(dice, compare, diceRoll, roller, engine) {
1009
1295
  }
1010
1296
  rerollCount++;
1011
1297
  if (res && res.total !== void 0) {
1012
- isFail = evaluate7(`${res.total}${compare.sign}${compare.value}`);
1298
+ isFail = evaluate9(`${res.total}${compare.sign}${compare.value}`);
1013
1299
  }
1014
1300
  }
1015
1301
  return { rerollCount, result: res };
1016
1302
  }
1017
1303
 
1018
1304
  // src/roll.ts
1019
- function roll(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, sort) {
1305
+ function roll(dice, engine = NumberGenerator8.engines.nodeCrypto, pity, sort, comment) {
1020
1306
  if (sort === "none" /* None */) sort = void 0;
1021
1307
  const prepared = prepareDice(dice);
1022
1308
  if (!prepared.dice.includes("d")) return void 0;
@@ -1058,9 +1344,11 @@ function roll(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, sort) {
1058
1344
  );
1059
1345
  }
1060
1346
  const roller = new DiceRoller3();
1061
- NumberGenerator7.generator.engine = engine;
1062
- let diceWithoutComment = processedDice.replace(COMMENT_REGEX, "").trimEnd();
1063
- diceWithoutComment = setSortOrder(diceWithoutComment, sort);
1347
+ NumberGenerator8.generator.engine = engine;
1348
+ const splitResult = splitDiceComment(processedDice);
1349
+ const diceBase = comment !== void 0 ? processedDice.trimEnd() : splitResult.dice;
1350
+ const resolvedComment = comment ?? splitResult.comment;
1351
+ const diceWithoutComment = setSortOrder(diceBase, sort);
1064
1352
  let diceRoll;
1065
1353
  try {
1066
1354
  diceRoll = roller.roll(diceWithoutComment);
@@ -1076,8 +1364,6 @@ function roll(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, sort) {
1076
1364
  );
1077
1365
  compare.trivial = trivial ? true : void 0;
1078
1366
  }
1079
- const commentMatch = processedDice.match(COMMENT_REGEX);
1080
- const comment = commentMatch ? commentMatch[2] : void 0;
1081
1367
  let rerollCount = 0;
1082
1368
  let pityResult;
1083
1369
  if (pity && compare) {
@@ -1094,7 +1380,7 @@ function roll(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, sort) {
1094
1380
  return {
1095
1381
  ...pityResult,
1096
1382
  dice: prepared.isSimpleCurly ? finalDiceDisplay : processedDice,
1097
- comment,
1383
+ comment: resolvedComment,
1098
1384
  compare,
1099
1385
  modifier: modificator,
1100
1386
  pityLogs: rerollCount,
@@ -1116,7 +1402,7 @@ function roll(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, sort) {
1116
1402
  return {
1117
1403
  dice: prepared.isSimpleCurly ? finalDiceDisplay : prepared.diceDisplay,
1118
1404
  result: resultOutput,
1119
- comment,
1405
+ comment: resolvedComment,
1120
1406
  compare: compare ? compare : void 0,
1121
1407
  modifier: modificator,
1122
1408
  total: successes,
@@ -1127,7 +1413,7 @@ function roll(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, sort) {
1127
1413
  return {
1128
1414
  dice: prepared.isSimpleCurly ? finalDiceDisplay : processedDice,
1129
1415
  result: resultOutput,
1130
- comment,
1416
+ comment: resolvedComment,
1131
1417
  compare: compare ? compare : void 0,
1132
1418
  modifier: modificator,
1133
1419
  total: roller.total,
@@ -1135,7 +1421,7 @@ function roll(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, sort) {
1135
1421
  trivial: compare?.trivial ? true : void 0
1136
1422
  };
1137
1423
  }
1138
- function sharedRolls(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, explodingSuccessMain, diceDisplay, isSharedCurly, sort) {
1424
+ function sharedRolls(dice, engine = NumberGenerator8.engines.nodeCrypto, pity, explodingSuccessMain, diceDisplay, isSharedCurly, sort) {
1139
1425
  if (!explodingSuccessMain)
1140
1426
  explodingSuccessMain = normalizeExplodingSuccess(dice.split(";")[0] ?? dice);
1141
1427
  if (explodingSuccessMain) {
@@ -1170,13 +1456,13 @@ function sharedRolls(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, e
1170
1456
  const sortFromMain = getSortOrder(diceMain);
1171
1457
  const rollBounds = getRollBounds(diceMain, engine);
1172
1458
  let diceResult = roll(diceMain, engine, pity, sort);
1173
- if (!diceResult || !diceResult.total) {
1459
+ if (!diceResult?.total) {
1174
1460
  if (hidden) {
1175
1461
  diceResult = roll(fixParenthesis(split[0]), engine, pity, sort);
1176
1462
  hidden = false;
1177
1463
  } else return void 0;
1178
1464
  }
1179
- if (!diceResult || !diceResult.total) return void 0;
1465
+ if (!diceResult?.total) return void 0;
1180
1466
  if (explodingSuccessMain && diceResult.result) {
1181
1467
  const values = extractValuesFromOutput(diceResult.result);
1182
1468
  diceResult.total = values.filter(
@@ -1218,7 +1504,7 @@ function sharedRolls(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, e
1218
1504
  const { diceAll } = replaceText(element, diceResult.total, diceResult.dice);
1219
1505
  let successCount = 0;
1220
1506
  try {
1221
- const evaluated = evaluate8(toRoll);
1507
+ const evaluated = evaluate10(toRoll);
1222
1508
  successCount = evaluated ? 1 : 0;
1223
1509
  } catch (error) {
1224
1510
  const evaluated = roll(toRoll, engine, pity);
@@ -1252,7 +1538,7 @@ function sharedRolls(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, e
1252
1538
  diceResult.dice
1253
1539
  );
1254
1540
  try {
1255
- const evaluated = evaluate8(toRoll);
1541
+ const evaluated = evaluate10(toRoll);
1256
1542
  results.push(`\u25C8 ${comment}${diceAll}: ${formule} = ${evaluated}`);
1257
1543
  total += Number.parseInt(evaluated, 10);
1258
1544
  } catch (error) {
@@ -1281,7 +1567,7 @@ function sharedRolls(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, e
1281
1567
  trivial: hasTrivialComparison ? true : void 0
1282
1568
  };
1283
1569
  }
1284
- function replaceInFormula(element, diceResult, compareResult, res, engine = NumberGenerator7.engines.nodeCrypto, pity) {
1570
+ function replaceInFormula(element, diceResult, compareResult, res, engine = NumberGenerator8.engines.nodeCrypto, pity) {
1285
1571
  const { formule, diceAll } = replaceText(
1286
1572
  element,
1287
1573
  diceResult.total ?? 0,
@@ -1291,7 +1577,7 @@ function replaceInFormula(element, diceResult, compareResult, res, engine = Numb
1291
1577
  const invertedSign = res ? compareResult.compare.sign : inverseSign(compareResult.compare.sign);
1292
1578
  let evaluateRoll;
1293
1579
  try {
1294
- evaluateRoll = evaluate8(compareResult.dice);
1580
+ evaluateRoll = evaluate10(compareResult.dice);
1295
1581
  return `${validSign} ${diceAll}: ${formule} = ${evaluateRoll}${invertedSign}${compareResult.compare?.value}`;
1296
1582
  } catch (error) {
1297
1583
  const evaluateRoll2 = roll(compareResult.dice, engine, pity);
@@ -1300,218 +1586,6 @@ function replaceInFormula(element, diceResult, compareResult, res, engine = Numb
1300
1586
  return `${validSign} ${diceAll}: ${formule} = ${evaluateRoll2}${invertedSign}${compareResult.compare?.value}`;
1301
1587
  }
1302
1588
  }
1303
-
1304
- // src/verify_template.ts
1305
- import { evaluate as evaluate9 } from "mathjs";
1306
- import { Random as Random2 } from "random-js";
1307
- import "uniformize";
1308
- import { NumberGenerator as NumberGenerator8 } from "@dice-roller/rpg-dice-roller";
1309
- function evalStatsDice(testDice, allStats, engine = NumberGenerator8.engines.nodeCrypto, pity) {
1310
- let dice = testDice.trimEnd();
1311
- if (allStats && Object.keys(allStats).length > 0) {
1312
- const names = Object.keys(allStats);
1313
- for (const name of names) {
1314
- const regex = new RegExp(escapeRegex(name.standardize()), "gi");
1315
- if (dice.standardize().match(regex)) {
1316
- const statValue = allStats[name];
1317
- dice = dice.standardize().replace(regex, statValue.toString()).trimEnd();
1318
- }
1319
- }
1320
- }
1321
- try {
1322
- if (!roll(replaceFormulaInDice(replaceExpByRandom(dice)), engine, pity))
1323
- throw new DiceTypeError(dice, "evalStatsDice", "no roll result");
1324
- return testDice;
1325
- } catch (error) {
1326
- throw new DiceTypeError(dice, "evalStatsDice", error);
1327
- }
1328
- }
1329
- function diceRandomParse(value, template, engine = NumberGenerator8.engines.nodeCrypto) {
1330
- if (!template.statistics) return replaceFormulaInDice(value.standardize());
1331
- value = value.standardize();
1332
- const statNames = Object.keys(template.statistics);
1333
- let newDice = value;
1334
- for (const name of statNames) {
1335
- const regex = new RegExp(escapeRegex(name.standardize()), "gi");
1336
- if (value.match(regex)) {
1337
- let max;
1338
- let min;
1339
- const foundStat = template.statistics?.[name];
1340
- if (foundStat) {
1341
- max = foundStat.max;
1342
- min = foundStat.min;
1343
- }
1344
- const total = template.total || 100;
1345
- const randomStatValue = generateRandomStat(total, max, min, engine);
1346
- newDice = value.replace(regex, randomStatValue.toString());
1347
- }
1348
- }
1349
- return replaceFormulaInDice(newDice);
1350
- }
1351
- function diceTypeRandomParse(dice, template, engine = NumberGenerator8.engines.nodeCrypto) {
1352
- dice = replaceExpByRandom(dice);
1353
- if (!template.statistics) return dice;
1354
- const firstStatNotcombinaison = Object.keys(template.statistics).find(
1355
- (stat) => !template.statistics?.[stat].combinaison
1356
- );
1357
- if (!firstStatNotcombinaison) return dice;
1358
- const stats = template.statistics[firstStatNotcombinaison];
1359
- const { min, max } = stats;
1360
- const total = template.total || 100;
1361
- const randomStatValue = generateRandomStat(total, max, min, engine);
1362
- return replaceFormulaInDice(dice.replaceAll("$", randomStatValue.toString()));
1363
- }
1364
- function evalCombinaison(combinaison, stats) {
1365
- const newStats = {};
1366
- for (const [stat, combin] of Object.entries(combinaison)) {
1367
- let formula = combin.standardize();
1368
- for (const [statName, value] of Object.entries(stats)) {
1369
- const regex = new RegExp(statName.standardize(), "gi");
1370
- formula = formula.replace(regex, value.toString());
1371
- }
1372
- try {
1373
- newStats[stat] = evaluate9(formula);
1374
- } catch (error) {
1375
- throw new FormulaError(stat, "evalCombinaison", error);
1376
- }
1377
- }
1378
- return newStats;
1379
- }
1380
- function evalOneCombinaison(combinaison, stats) {
1381
- let formula = combinaison.standardize();
1382
- for (const [statName, value] of Object.entries(stats)) {
1383
- const regex = new RegExp(statName.standardize(), "gi");
1384
- formula = formula.replace(regex, value.toString());
1385
- }
1386
- try {
1387
- return evaluate9(formula);
1388
- } catch (error) {
1389
- throw new FormulaError(combinaison, "evalOneCombinaison", error);
1390
- }
1391
- }
1392
- function convertNumber(number) {
1393
- if (number === void 0 || number === null) return void 0;
1394
- if (number.toString().length === 0 || Number.isNaN(Number.parseInt(number.toString(), 10)))
1395
- return void 0;
1396
- if (isNumber(number)) return Number.parseInt(number.toString(), 10);
1397
- return void 0;
1398
- }
1399
- function verifyTemplateValue(template, verify = true, engine = NumberGenerator8.engines.nodeCrypto) {
1400
- const parsedTemplate = templateSchema.parse(template);
1401
- const { success, failure } = parsedTemplate.critical ?? {};
1402
- const criticicalVal = {
1403
- success: convertNumber(success),
1404
- failure: convertNumber(failure)
1405
- };
1406
- const statistiqueTemplate = {
1407
- diceType: parsedTemplate.diceType,
1408
- statistics: parsedTemplate.statistics,
1409
- critical: criticicalVal,
1410
- total: parsedTemplate.total,
1411
- charName: parsedTemplate.charName,
1412
- damage: parsedTemplate.damage,
1413
- customCritical: parsedTemplate.customCritical,
1414
- forceDistrib: parsedTemplate.forceDistrib
1415
- };
1416
- if (!verify) return statistiqueTemplate;
1417
- if (statistiqueTemplate.diceType) {
1418
- if (statistiqueTemplate.diceType.match(DETECT_CRITICAL)) {
1419
- throw new DiceTypeError(
1420
- statistiqueTemplate.diceType,
1421
- "critical_dice_type",
1422
- "contains critical detection: should be in custom critical instead"
1423
- );
1424
- }
1425
- const cleanedDice2 = diceTypeRandomParse(
1426
- statistiqueTemplate.diceType,
1427
- statistiqueTemplate,
1428
- engine
1429
- );
1430
- const rolled = roll(cleanedDice2, engine);
1431
- if (!rolled) throw new DiceTypeError(cleanedDice2, "no_roll_result", "no roll result");
1432
- }
1433
- if (statistiqueTemplate.customCritical) {
1434
- if (!statistiqueTemplate.diceType) {
1435
- throw new DiceTypeError("no_dice_type", "no_dice_type", "no dice type");
1436
- }
1437
- const customCritical = statistiqueTemplate.customCritical;
1438
- for (const [, custom] of Object.entries(customCritical)) {
1439
- const cleanedDice2 = createCriticalCustom(
1440
- statistiqueTemplate.diceType,
1441
- custom,
1442
- statistiqueTemplate,
1443
- engine
1444
- );
1445
- const rolled = roll(cleanedDice2, engine);
1446
- if (!rolled)
1447
- throw new DiceTypeError(cleanedDice2, "verifyTemplateValue", "no roll result");
1448
- }
1449
- }
1450
- testDiceRegistered(statistiqueTemplate, engine);
1451
- testStatCombinaison(statistiqueTemplate, engine);
1452
- return statistiqueTemplate;
1453
- }
1454
- function testDiceRegistered(template, engine = NumberGenerator8.engines.nodeCrypto) {
1455
- if (!template.damage) return;
1456
- if (Object.keys(template.damage).length === 0) throw new EmptyObjectError();
1457
- if (Object.keys(template.damage).length > 25) throw new TooManyDice();
1458
- for (const [name, dice] of Object.entries(template.damage)) {
1459
- if (!dice) continue;
1460
- const diceReplaced = replaceExpByRandom(dice);
1461
- const randomDiceParsed = diceRandomParse(diceReplaced, template, engine);
1462
- try {
1463
- const rolled = roll(randomDiceParsed, engine);
1464
- if (!rolled) throw new DiceTypeError(name, "no_roll_result", dice);
1465
- } catch (error) {
1466
- throw new DiceTypeError(name, "testDiceRegistered", error);
1467
- }
1468
- }
1469
- }
1470
- function testStatCombinaison(template, engine = NumberGenerator8.engines.nodeCrypto) {
1471
- if (!template.statistics) return;
1472
- const onlycombinaisonStats = Object.fromEntries(
1473
- Object.entries(template.statistics).filter(
1474
- ([_, value]) => value.combinaison !== void 0
1475
- )
1476
- );
1477
- const allOtherStats = Object.fromEntries(
1478
- Object.entries(template.statistics).filter(([_, value]) => !value.combinaison)
1479
- );
1480
- if (Object.keys(onlycombinaisonStats).length === 0) return;
1481
- const allStats = Object.keys(template.statistics).filter(
1482
- (stat) => !template.statistics[stat].combinaison
1483
- );
1484
- if (allStats.length === 0) throw new NoStatisticsError();
1485
- const error = [];
1486
- for (const [stat, value] of Object.entries(onlycombinaisonStats)) {
1487
- let formula = value.combinaison;
1488
- for (const [other, data] of Object.entries(allOtherStats)) {
1489
- const { max, min } = data;
1490
- const total = template.total || 100;
1491
- const randomStatValue = generateRandomStat(total, max, min, engine);
1492
- const regex = new RegExp(other, "gi");
1493
- formula = formula.replace(regex, randomStatValue.toString());
1494
- }
1495
- try {
1496
- evaluate9(formula);
1497
- } catch (e) {
1498
- error.push(stat);
1499
- }
1500
- }
1501
- if (error.length > 0) throw new FormulaError(error.join(", "), "testStatCombinaison");
1502
- return;
1503
- }
1504
- function generateRandomStat(total = 100, max, min, engine = NumberGenerator8.engines.nodeCrypto) {
1505
- let randomStatValue = total + 1;
1506
- const random = new Random2(engine || NumberGenerator8.engines.nodeCrypto);
1507
- while (randomStatValue >= total || randomStatValue === 0) {
1508
- if (max && min) randomStatValue = randomInt(min, max, engine, random);
1509
- else if (max) randomStatValue = randomInt(1, max, engine, random);
1510
- else if (min) randomStatValue = randomInt(min, total, engine, random);
1511
- else randomStatValue = randomInt(1, total, engine, random);
1512
- }
1513
- return randomStatValue;
1514
- }
1515
1589
  export {
1516
1590
  COMMENT_REGEX,
1517
1591
  DETECT_CRITICAL,
@@ -1553,7 +1627,9 @@ export {
1553
1627
  replaceFormulaInDice,
1554
1628
  replaceInFormula,
1555
1629
  replaceUnknown,
1630
+ resolveFormulaHint,
1556
1631
  roll,
1632
+ splitDiceComment,
1557
1633
  standardizeDice,
1558
1634
  templateSchema,
1559
1635
  testDiceRegistered,