@dicelette/core 1.28.1 → 1.28.2

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,259 @@ 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
215
  // src/dice/compare.ts
216
- import { NumberGenerator as NumberGenerator3 } from "@dice-roller/rpg-dice-roller";
216
+ import { NumberGenerator as NumberGenerator4 } from "@dice-roller/rpg-dice-roller";
217
217
  import { evaluate as evaluate2 } from "mathjs";
218
218
 
219
219
  // src/utils.ts
220
+ import "uniformize";
221
+ import { NumberGenerator as NumberGenerator3 } from "@dice-roller/rpg-dice-roller";
222
+ import { Random as Random2 } from "random-js";
223
+
224
+ // src/verify_template.ts
220
225
  import { evaluate } from "mathjs";
226
+ import { Random } from "random-js";
221
227
  import "uniformize";
222
228
  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
- );
229
+ function evalStatsDice(testDice, allStats, engine = NumberGenerator2.engines.nodeCrypto, pity) {
230
+ let dice = testDice.trimEnd();
231
+ if (allStats && Object.keys(allStats).length > 0) {
232
+ const names = Object.keys(allStats);
233
+ for (const name of names) {
234
+ const regex = new RegExp(escapeRegex(name.standardize()), "gi");
235
+ if (dice.standardize().match(regex)) {
236
+ const statValue = allStats[name];
237
+ dice = dice.standardize().replace(regex, statValue.toString()).trimEnd();
238
+ }
248
239
  }
249
240
  }
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
- }
241
+ try {
242
+ if (!roll(replaceFormulaInDice(replaceExpByRandom(dice)), engine, pity))
243
+ throw new DiceTypeError(dice, "evalStatsDice", "no roll result");
244
+ return testDice;
245
+ } catch (error) {
246
+ throw new DiceTypeError(dice, "evalStatsDice", error);
274
247
  }
275
- return bestMatch;
276
248
  }
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);
249
+ function diceRandomParse(value, template, engine = NumberGenerator2.engines.nodeCrypto) {
250
+ if (!template.statistics) return replaceFormulaInDice(value.standardize());
251
+ value = value.standardize();
252
+ const statNames = Object.keys(template.statistics);
253
+ let newDice = value;
254
+ for (const name of statNames) {
255
+ const regex = new RegExp(escapeRegex(name.standardize()), "gi");
256
+ if (value.match(regex)) {
257
+ let max;
258
+ let min;
259
+ const foundStat = template.statistics?.[name];
260
+ if (foundStat) {
261
+ max = foundStat.max;
262
+ min = foundStat.min;
263
+ }
264
+ const total = template.total || 100;
265
+ const randomStatValue = generateRandomStat(total, max, min, engine);
266
+ newDice = value.replace(regex, randomStatValue.toString());
267
+ }
281
268
  }
282
- return findBestStatMatch(
283
- searchTerm.standardize(),
284
- normalizeRecord,
285
- similarityThreshold
286
- ) || null;
269
+ return replaceFormulaInDice(newDice);
287
270
  }
288
- function replaceUnknown(dice, replacer) {
289
- return dice.replaceAll(REMOVER_PATTERN.STAT_MATCHER, replacer).replaceAll("+0", "").replaceAll("-0", "");
271
+ function diceTypeRandomParse(dice, template, engine = NumberGenerator2.engines.nodeCrypto) {
272
+ dice = replaceExpByRandom(dice);
273
+ if (!template.statistics) return dice;
274
+ const firstStatNotcombinaison = Object.keys(template.statistics).find(
275
+ (stat) => !template.statistics?.[stat].combinaison
276
+ );
277
+ if (!firstStatNotcombinaison) return dice;
278
+ const stats = template.statistics[firstStatNotcombinaison];
279
+ const { min, max } = stats;
280
+ const total = template.total || 100;
281
+ const randomStatValue = generateRandomStat(total, max, min, engine);
282
+ return replaceFormulaInDice(dice.replaceAll("$", randomStatValue.toString()));
290
283
  }
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");
284
+ function evalCombinaison(combinaison, stats) {
285
+ const newStats = {};
286
+ for (const [stat, combin] of Object.entries(combinaison)) {
287
+ let formula = combin.standardize();
288
+ for (const [statName, value] of Object.entries(stats)) {
289
+ const regex = new RegExp(statName.standardize(), "gi");
290
+ formula = formula.replace(regex, value.toString());
291
+ }
292
+ try {
293
+ newStats[stat] = evaluate(formula);
294
+ } catch (error) {
295
+ throw new FormulaError(stat, "evalCombinaison", error);
296
+ }
298
297
  }
299
- return dice.replaceAll("+0", "").replaceAll("-0", "");
298
+ return newStats;
300
299
  }
301
-
302
- // src/utils.ts
303
- function escapeRegex(string) {
304
- return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
300
+ function evalOneCombinaison(combinaison, stats) {
301
+ let formula = combinaison.standardize();
302
+ for (const [statName, value] of Object.entries(stats)) {
303
+ const regex = new RegExp(statName.standardize(), "gi");
304
+ formula = formula.replace(regex, value.toString());
305
+ }
306
+ try {
307
+ return evaluate(formula);
308
+ } catch (error) {
309
+ throw new FormulaError(combinaison, "evalOneCombinaison", error);
310
+ }
305
311
  }
306
- function standardizeDice(dice) {
307
- return dice.replace(
308
- /(\[[^\]]+])|([^[]+)/g,
309
- (_match, insideBrackets, outsideText) => insideBrackets ? insideBrackets : outsideText.standardize().replaceAll("df", "dF")
310
- );
312
+ function convertNumber(number) {
313
+ if (number === void 0 || number === null) return void 0;
314
+ if (number.toString().length === 0 || Number.isNaN(Number.parseInt(number.toString(), 10)))
315
+ return void 0;
316
+ if (isNumber(number)) return Number.parseInt(number.toString(), 10);
317
+ return void 0;
311
318
  }
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()}`;
319
+ function verifyTemplateValue(template, verify = true, engine = NumberGenerator2.engines.nodeCrypto) {
320
+ const parsedTemplate = templateSchema.parse(template);
321
+ const { success, failure } = parsedTemplate.critical ?? {};
322
+ const criticicalVal = {
323
+ success: convertNumber(success),
324
+ failure: convertNumber(failure)
325
+ };
326
+ const statistiqueTemplate = {
327
+ diceType: parsedTemplate.diceType,
328
+ statistics: parsedTemplate.statistics,
329
+ critical: criticicalVal,
330
+ total: parsedTemplate.total,
331
+ charName: parsedTemplate.charName,
332
+ damage: parsedTemplate.damage,
333
+ customCritical: parsedTemplate.customCritical,
334
+ forceDistrib: parsedTemplate.forceDistrib
335
+ };
336
+ if (!verify) return statistiqueTemplate;
337
+ if (statistiqueTemplate.diceType) {
338
+ if (statistiqueTemplate.diceType.match(DETECT_CRITICAL)) {
339
+ throw new DiceTypeError(
340
+ statistiqueTemplate.diceType,
341
+ "critical_dice_type",
342
+ "contains critical detection: should be in custom critical instead"
343
+ );
344
+ }
345
+ const cleanedDice2 = diceTypeRandomParse(
346
+ statistiqueTemplate.diceType,
347
+ statistiqueTemplate,
348
+ engine
349
+ );
350
+ const rolled = roll(cleanedDice2, engine);
351
+ if (!rolled) throw new DiceTypeError(cleanedDice2, "no_roll_result", "no roll result");
321
352
  }
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();
353
+ if (statistiqueTemplate.customCritical) {
354
+ if (!statistiqueTemplate.diceType) {
355
+ throw new DiceTypeError("no_dice_type", "no_dice_type", "no dice type");
356
+ }
357
+ const customCritical = statistiqueTemplate.customCritical;
358
+ for (const [, custom] of Object.entries(customCritical)) {
359
+ const cleanedDice2 = createCriticalCustom(
360
+ statistiqueTemplate.diceType,
361
+ custom,
362
+ statistiqueTemplate,
363
+ engine
364
+ );
365
+ const rolled = roll(cleanedDice2, engine);
366
+ if (!rolled)
367
+ throw new DiceTypeError(cleanedDice2, "verifyTemplateValue", "no roll result");
368
+ }
329
369
  }
330
- return token;
370
+ testDiceRegistered(statistiqueTemplate, engine);
371
+ testStatCombinaison(statistiqueTemplate, engine);
372
+ return statistiqueTemplate;
331
373
  }
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);
374
+ function testDiceRegistered(template, engine = NumberGenerator2.engines.nodeCrypto) {
375
+ if (!template.damage) return;
376
+ if (Object.keys(template.damage).length === 0) throw new EmptyObjectError();
377
+ if (Object.keys(template.damage).length > 25) throw new TooManyDice();
378
+ for (const [name, dice] of Object.entries(template.damage)) {
379
+ if (!dice) continue;
380
+ const diceReplaced = replaceExpByRandom(dice);
381
+ const randomDiceParsed = diceRandomParse(diceReplaced, template, engine);
382
+ try {
383
+ const rolled = roll(randomDiceParsed, engine);
384
+ if (!rolled) throw new DiceTypeError(name, "no_roll_result", dice);
385
+ } catch (error) {
386
+ throw new DiceTypeError(name, "testDiceRegistered", error);
372
387
  }
373
- dice = result;
374
388
  }
375
- if (dollarValue) dice = dice.replaceAll("$", dollarValue);
376
- return replaceFormulaInDice(dice);
377
389
  }
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
- }
390
+ function testStatCombinaison(template, engine = NumberGenerator2.engines.nodeCrypto) {
391
+ if (!template.statistics) return;
392
+ const onlycombinaisonStats = Object.fromEntries(
393
+ Object.entries(template.statistics).filter(
394
+ ([_, value]) => value.combinaison !== void 0
395
+ )
396
+ );
397
+ const allOtherStats = Object.fromEntries(
398
+ Object.entries(template.statistics).filter(([_, value]) => !value.combinaison)
399
+ );
400
+ if (Object.keys(onlycombinaisonStats).length === 0) return;
401
+ const allStats = Object.keys(template.statistics).filter(
402
+ (stat) => !template.statistics[stat].combinaison
403
+ );
404
+ if (allStats.length === 0) throw new NoStatisticsError();
405
+ const error = [];
406
+ for (const [stat, value] of Object.entries(onlycombinaisonStats)) {
407
+ let formula = value.combinaison;
408
+ for (const [other, data] of Object.entries(allOtherStats)) {
409
+ const { max, min } = data;
410
+ const total = template.total || 100;
411
+ const randomStatValue = generateRandomStat(total, max, min, engine);
412
+ const regex = new RegExp(other, "gi");
413
+ formula = formula.replace(regex, randomStatValue.toString());
414
+ }
415
+ try {
416
+ evaluate(formula);
417
+ } catch (e) {
418
+ error.push(stat);
391
419
  }
392
420
  }
393
- return cleanedDice(modifiedDice);
421
+ if (error.length > 0) throw new FormulaError(error.join(", "), "testStatCombinaison");
422
+ return;
394
423
  }
395
- function cleanedDice(dice) {
396
- return dice.replaceAll("+-", "-").replaceAll("--", "+").replaceAll("++", "+").replaceAll("=>", ">=").replaceAll("=<", "<=").trimEnd();
424
+ function generateRandomStat(total = 100, max, min, engine = NumberGenerator2.engines.nodeCrypto) {
425
+ let randomStatValue = total + 1;
426
+ const random = new Random(engine || NumberGenerator2.engines.nodeCrypto);
427
+ while (randomStatValue >= total || randomStatValue === 0) {
428
+ if (max && min) randomStatValue = randomInt(min, max, engine, random);
429
+ else if (max) randomStatValue = randomInt(1, max, engine, random);
430
+ else if (min) randomStatValue = randomInt(min, total, engine, random);
431
+ else randomStatValue = randomInt(1, total, engine, random);
432
+ }
433
+ return randomStatValue;
434
+ }
435
+
436
+ // src/utils.ts
437
+ function escapeRegex(string) {
438
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
439
+ }
440
+ function standardizeDice(dice) {
441
+ return dice.replace(
442
+ /(\[[^\]]+])|([^[]+)/g,
443
+ (_match, insideBrackets, outsideText) => insideBrackets ? insideBrackets : outsideText.standardize().replaceAll("df", "dF")
444
+ );
397
445
  }
398
446
  function isNumber(value) {
399
447
  return value !== void 0 && (typeof value === "number" || !Number.isNaN(Number(value)) && typeof value === "string" && value.trim().length > 0);
400
448
  }
401
- function replaceExpByRandom(dice, engine = NumberGenerator2.engines.nodeCrypto) {
449
+ function replaceExpByRandom(dice, engine = NumberGenerator3.engines.nodeCrypto) {
402
450
  const diceRegex = /\{exp( ?\|\| ?(?<default>\d+))?}/gi;
403
451
  return dice.replace(diceRegex, (_match, _p1, _p2, _offset, _string, groups) => {
404
452
  const defaultValue = groups?.default;
405
453
  return defaultValue ?? randomInt(1, 999, engine).toString();
406
454
  });
407
455
  }
408
- function randomInt(min, max, engine = NumberGenerator2.engines.nodeCrypto, rng) {
409
- if (!rng) rng = new Random(engine || void 0);
456
+ function randomInt(min, max, engine = NumberGenerator3.engines.nodeCrypto, rng) {
457
+ if (!rng) rng = new Random2(engine || void 0);
410
458
  return rng.integer(min, max);
411
459
  }
412
- function createCriticalCustom(dice, customCritical, template, engine = NumberGenerator2.engines.nodeCrypto) {
460
+ function createCriticalCustom(dice, customCritical, template, engine = NumberGenerator3.engines.nodeCrypto) {
413
461
  const compareRegex = dice.match(SIGN_REGEX_SPACE);
414
462
  let customDice = dice;
415
463
  const compareValue = diceTypeRandomParse(customCritical.value, template, engine);
@@ -452,7 +500,7 @@ function canComparisonFail(maxRollValue, compare, minRollValue = 1) {
452
500
  return true;
453
501
  }
454
502
  }
455
- function rollCompare(value, engine = NumberGenerator3.engines.nodeCrypto, pity) {
503
+ function rollCompare(value, engine = NumberGenerator4.engines.nodeCrypto, pity) {
456
504
  if (isNumber(value)) return { value: Number.parseInt(value, 10) };
457
505
  if (!value || typeof value === "string" && value.trim() === "") {
458
506
  return { value: 0, diceResult: value };
@@ -471,7 +519,7 @@ function rollCompare(value, engine = NumberGenerator3.engines.nodeCrypto, pity)
471
519
  diceResult: rollComp?.result
472
520
  };
473
521
  }
474
- function getCompare(dice, compareRegex, engine = NumberGenerator3.engines.nodeCrypto, pity) {
522
+ function getCompare(dice, compareRegex, engine = NumberGenerator4.engines.nodeCrypto, pity) {
475
523
  if (dice.match(/((\{.*,(.*)+\}|([><=!]+\d+f))([><=]|!=)+\d+\}?)|\{(.*)(([><=]|!=)+).*\}/))
476
524
  return { dice, compare: void 0 };
477
525
  dice = dice.replace(SIGN_REGEX_SPACE, "");
@@ -527,7 +575,7 @@ function canComparisonSucceed(maxRollValue, compare, minRollValue) {
527
575
  import { evaluate as evaluate4 } from "mathjs";
528
576
 
529
577
  // src/dice/signs.ts
530
- import { NumberGenerator as NumberGenerator4 } from "@dice-roller/rpg-dice-roller";
578
+ import { NumberGenerator as NumberGenerator5 } from "@dice-roller/rpg-dice-roller";
531
579
  import { evaluate as evaluate3 } from "mathjs";
532
580
 
533
581
  // src/dice/replace.ts
@@ -617,7 +665,7 @@ function inverseSign(sign) {
617
665
  return "==";
618
666
  }
619
667
  }
620
- function compareSignFormule(toRoll, compareRegex, element, diceResult, engine = NumberGenerator4.engines.nodeCrypto, pity, rollBounds) {
668
+ function compareSignFormule(toRoll, compareRegex, element, diceResult, engine = NumberGenerator5.engines.nodeCrypto, pity, rollBounds) {
621
669
  let results = "";
622
670
  let trivial = false;
623
671
  const compareResult = getCompare(toRoll, compareRegex, engine);
@@ -678,43 +726,278 @@ function normalizeExplodingSuccess(dice) {
678
726
  parsedValue = 0;
679
727
  }
680
728
  }
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
- };
729
+ const normalizedSegment = "!";
730
+ const replacedDice = dice.replace(match[0], normalizedSegment);
731
+ return {
732
+ dice: replacedDice,
733
+ originalDice: dice,
734
+ sign: normalizedSign,
735
+ value: parsedValue,
736
+ normalizedSegment,
737
+ originalSegment: match[0]
738
+ };
739
+ }
740
+ function countExplodingSuccesses(diceRoll, sign, value) {
741
+ const rollsArray = Array.isArray(diceRoll) ? diceRoll : [diceRoll];
742
+ const flatValues = [];
743
+ for (const dr of rollsArray) {
744
+ const groups = dr.rolls ?? [];
745
+ for (const group of groups) {
746
+ const innerRolls = group.rolls ?? [];
747
+ for (const roll2 of innerRolls) {
748
+ if (typeof roll2.value === "number") flatValues.push(roll2.value);
749
+ }
750
+ }
751
+ }
752
+ return flatValues.reduce(
753
+ (acc, current) => acc + (matchComparison(sign, current, value) ? 1 : 0),
754
+ 0
755
+ );
756
+ }
757
+
758
+ // src/dice/extract.ts
759
+ import { DiceRoller, NumberGenerator as NumberGenerator6 } from "@dice-roller/rpg-dice-roller";
760
+
761
+ // src/similarities/generateStatsDice.ts
762
+ import { evaluate as evaluate5 } from "mathjs";
763
+
764
+ // src/similarities/similarity.ts
765
+ function calculateSimilarity(str1, str2) {
766
+ const longer = str1.length > str2.length ? str1 : str2;
767
+ const shorter = str1.length > str2.length ? str2 : str1;
768
+ if (longer.length === 0) return 1;
769
+ const distance = levenshteinDistance(longer, shorter);
770
+ return (longer.length - distance) / longer.length;
771
+ }
772
+ function levenshteinDistance(str1, str2) {
773
+ const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
774
+ for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
775
+ for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
776
+ for (let j = 1; j <= str2.length; j++) {
777
+ for (let i = 1; i <= str1.length; i++) {
778
+ const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
779
+ matrix[j][i] = Math.min(
780
+ matrix[j][i - 1] + 1,
781
+ // insertion
782
+ matrix[j - 1][i] + 1,
783
+ // deletion
784
+ matrix[j - 1][i - 1] + cost
785
+ // substitution
786
+ );
787
+ }
788
+ }
789
+ return matrix[str2.length][str1.length];
790
+ }
791
+ function findBestStatMatch(searchTerm, normalizedStats, similarityThreshold = MIN_THRESHOLD_MATCH) {
792
+ const exact = normalizedStats.get(searchTerm);
793
+ if (exact) return exact;
794
+ const candidates = [];
795
+ for (const [normalizedKey, original] of normalizedStats) {
796
+ if (normalizedKey.startsWith(searchTerm))
797
+ candidates.push([original, calculateSimilarity(searchTerm, normalizedKey)]);
798
+ }
799
+ if (candidates.length === 1) return candidates[0][0];
800
+ if (candidates.length > 0) {
801
+ candidates.sort((a, b) => b[1] - a[1]);
802
+ if (candidates[0][1] >= similarityThreshold) return candidates[0][0];
803
+ }
804
+ let bestMatch;
805
+ let bestSimilarity = 0;
806
+ for (const [normalizedKey, original] of normalizedStats) {
807
+ const similarity = calculateSimilarity(searchTerm, normalizedKey);
808
+ if (similarity === 1) return original;
809
+ if (similarity > bestSimilarity && similarity >= similarityThreshold) {
810
+ bestSimilarity = similarity;
811
+ bestMatch = original;
812
+ }
813
+ }
814
+ return bestMatch;
815
+ }
816
+ function findBestRecord(record, searchTerm, similarityThreshold = MIN_THRESHOLD_MATCH) {
817
+ const normalizeRecord = /* @__PURE__ */ new Map();
818
+ for (const key of Object.keys(record)) {
819
+ normalizeRecord.set(key.standardize(), key);
820
+ }
821
+ return findBestStatMatch(
822
+ searchTerm.standardize(),
823
+ normalizeRecord,
824
+ similarityThreshold
825
+ ) || null;
826
+ }
827
+ function replaceUnknown(dice, replacer) {
828
+ return dice.replaceAll(REMOVER_PATTERN.STAT_MATCHER, replacer).replaceAll("+0", "").replaceAll("-0", "");
829
+ }
830
+ function verifyStatMatcherPattern(dice, replaceUnknow) {
831
+ if (REMOVER_PATTERN.STAT_MATCHER.test(dice)) {
832
+ if (replaceUnknow)
833
+ return replaceUnknown(dice, replaceUnknow);
834
+ const matched = dice.matchAll(new RegExp(REMOVER_PATTERN.STAT_MATCHER));
835
+ const stats = matched ? Array.from(matched, (m) => m?.[0]).map((s) => `\`${s}\``).join(", ") : "unknown";
836
+ throw new DiceTypeError(stats, "unknown_stats");
837
+ }
838
+ return dice.replaceAll("+0", "").replaceAll("-0", "");
839
+ }
840
+
841
+ // src/similarities/generateStatsDice.ts
842
+ function handleDiceAfterD(tokenStd, normalizedStats) {
843
+ const diceMatch = /^(\d*)d(.+)$/i.exec(tokenStd);
844
+ if (!diceMatch) return null;
845
+ const diceCount = diceMatch[1] || "";
846
+ const afterD = diceMatch[2];
847
+ const bestMatch = findBestStatMatch(afterD, normalizedStats, 1);
848
+ if (bestMatch) {
849
+ const [, value] = bestMatch;
850
+ return `${diceCount}d${value.toString()}`;
851
+ }
852
+ return null;
853
+ }
854
+ function handleSimpleToken(tokenStd, token, normalizedStats, minThreshold) {
855
+ const bestMatch = findBestStatMatch(tokenStd, normalizedStats, minThreshold);
856
+ if (bestMatch) {
857
+ const [, value] = bestMatch;
858
+ return value.toString();
859
+ }
860
+ return token;
861
+ }
862
+ function generateStatsDice(originalDice, stats, minThreshold = 0.6, dollarValue) {
863
+ let dice = originalDice.standardize();
864
+ if (stats && Object.keys(stats).length > 0) {
865
+ const normalizedStats = /* @__PURE__ */ new Map();
866
+ for (const [key, value] of Object.entries(stats)) {
867
+ const normalized = key.standardize();
868
+ normalizedStats.set(normalized, [key, value]);
869
+ }
870
+ const partsRegex = /(\[[^\]]+])|([^[]+)/g;
871
+ let result = "";
872
+ let match;
873
+ while ((match = partsRegex.exec(dice)) !== null) {
874
+ const insideBrackets = match[1];
875
+ const outsideText = match[2];
876
+ if (insideBrackets) {
877
+ result += insideBrackets;
878
+ continue;
879
+ }
880
+ if (!outsideText) {
881
+ continue;
882
+ }
883
+ const tokenRegex = /(\$?[\p{L}\p{N}_.]+)/gu;
884
+ let lastIndex = 0;
885
+ let tokenMatch;
886
+ while ((tokenMatch = tokenRegex.exec(outsideText)) !== null) {
887
+ result += outsideText.slice(lastIndex, tokenMatch.index);
888
+ const token = tokenMatch[0];
889
+ const tokenHasDollar = token.startsWith("$");
890
+ const tokenForCompare = tokenHasDollar ? token.slice(1) : token;
891
+ const tokenStd = tokenForCompare.standardize();
892
+ const diceReplacement = handleDiceAfterD(tokenStd, normalizedStats);
893
+ if (diceReplacement) {
894
+ result += diceReplacement;
895
+ lastIndex = tokenRegex.lastIndex;
896
+ continue;
897
+ }
898
+ result += handleSimpleToken(tokenStd, token, normalizedStats, minThreshold);
899
+ lastIndex = tokenRegex.lastIndex;
900
+ }
901
+ result += outsideText.slice(lastIndex);
902
+ }
903
+ dice = result;
904
+ }
905
+ if (dollarValue) dice = dice.replaceAll("$", dollarValue);
906
+ return replaceFormulaInDice(dice);
907
+ }
908
+ function replaceFormulaInDice(dice) {
909
+ const formula = /(?<formula>\{{2}(.+?)}{2})/gim;
910
+ let match;
911
+ let modifiedDice = dice;
912
+ while ((match = formula.exec(dice)) !== null) {
913
+ if (match.groups?.formula) {
914
+ const formulae = match.groups.formula.replaceAll("{{", "").replaceAll("}}", "");
915
+ try {
916
+ const result = evaluate5(formulae);
917
+ modifiedDice = modifiedDice.replace(match.groups.formula, result.toString());
918
+ } catch (error) {
919
+ throw new FormulaError(match.groups.formula, "replaceFormulasInDice", error);
920
+ }
921
+ }
922
+ }
923
+ return cleanedDice(modifiedDice);
691
924
  }
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);
925
+ function cleanedDice(dice) {
926
+ return dice.replaceAll("+-", "-").replaceAll("--", "+").replaceAll("++", "+").replaceAll("=>", ">=").replaceAll("=<", "<=").trimEnd();
927
+ }
928
+
929
+ // src/similarities/resolveFormula.ts
930
+ import { evaluate as evaluate6 } from "mathjs";
931
+ function toFiniteNumber(value) {
932
+ if (typeof value === "number" && Number.isFinite(value)) return value;
933
+ return void 0;
934
+ }
935
+ function substituteFormulaTokens(expr, resolvedStats, similarityThreshold = MIN_THRESHOLD_MATCH) {
936
+ return expr.replace(/([\p{L}\p{M}._-]+)/gu, (token) => {
937
+ const match = findBestStatMatch(token, resolvedStats, similarityThreshold);
938
+ return match !== void 0 ? match.toString() : token;
939
+ });
940
+ }
941
+ function resolveFormulaHint(formula, allAttributes, similarityThreshold = MIN_THRESHOLD_MATCH) {
942
+ const trimmed = formula.trim();
943
+ if (!trimmed) return { kind: "not-formula" };
944
+ if (isNumber(trimmed)) return { kind: "not-formula" };
945
+ const resolved = /* @__PURE__ */ new Map();
946
+ const pending = /* @__PURE__ */ new Map();
947
+ for (const [name, val] of Object.entries(allAttributes)) {
948
+ const norm = name.standardize();
949
+ if (typeof val === "number") {
950
+ if (Number.isFinite(val)) resolved.set(norm, val);
951
+ continue;
952
+ }
953
+ const t = val.trim();
954
+ if (!t) continue;
955
+ if (isNumber(t)) {
956
+ const numeric = Number(t);
957
+ if (Number.isFinite(numeric)) resolved.set(norm, numeric);
958
+ } else {
959
+ pending.set(norm, t.standardize());
960
+ }
961
+ }
962
+ for (const [normName, expr2] of pending) {
963
+ pending.set(normName, substituteFormulaTokens(expr2, resolved, similarityThreshold));
964
+ }
965
+ let progress = true;
966
+ while (pending.size > 0 && progress) {
967
+ progress = false;
968
+ for (const [normName, expr2] of pending) {
969
+ try {
970
+ const result = toFiniteNumber(evaluate6(expr2));
971
+ if (result === void 0) continue;
972
+ resolved.set(normName, result);
973
+ pending.delete(normName);
974
+ progress = true;
975
+ for (const [otherNorm, otherExpr] of pending) {
976
+ pending.set(
977
+ otherNorm,
978
+ substituteFormulaTokens(otherExpr, resolved, similarityThreshold)
979
+ );
980
+ }
981
+ } catch {
701
982
  }
702
983
  }
703
984
  }
704
- return flatValues.reduce(
705
- (acc, current) => acc + (matchComparison(sign, current, value) ? 1 : 0),
706
- 0
707
- );
985
+ const normFormula = trimmed.standardize();
986
+ const expr = substituteFormulaTokens(normFormula, resolved, similarityThreshold);
987
+ try {
988
+ const result = toFiniteNumber(evaluate6(expr));
989
+ if (result !== void 0) return { kind: "resolved", value: result };
990
+ return { kind: "error" };
991
+ } catch {
992
+ return { kind: "error" };
993
+ }
708
994
  }
709
995
 
710
- // src/dice/extract.ts
711
- import { DiceRoller, NumberGenerator as NumberGenerator5 } from "@dice-roller/rpg-dice-roller";
712
-
713
996
  // src/dice/calculator.ts
714
- import { evaluate as evaluate5 } from "mathjs";
997
+ import { evaluate as evaluate7 } from "mathjs";
715
998
  function calculator(sign, value, total) {
716
999
  if (sign === "^") sign = "**";
717
- return evaluate5(`${total} ${sign} ${value}`);
1000
+ return evaluate7(`${total} ${sign} ${value}`);
718
1001
  }
719
1002
 
720
1003
  // src/dice/extract.ts
@@ -749,10 +1032,10 @@ function extractValuesFromOutput(output) {
749
1032
  }
750
1033
  return values;
751
1034
  }
752
- function getRollBounds(dice, engine = NumberGenerator5.engines.nodeCrypto) {
1035
+ function getRollBounds(dice, engine = NumberGenerator6.engines.nodeCrypto) {
753
1036
  try {
754
1037
  const roller = new DiceRoller();
755
- NumberGenerator5.generator.engine = engine;
1038
+ NumberGenerator6.generator.engine = engine;
756
1039
  const rollResult = roller.roll(dice);
757
1040
  const instance = Array.isArray(rollResult) ? rollResult[0] : rollResult;
758
1041
  const { minTotal, maxTotal } = instance;
@@ -866,7 +1149,7 @@ function handleBulkRolls(dice, isCurlyBulk, bulkContent, compare, explodingSucce
866
1149
  );
867
1150
  }
868
1151
  const roller = new DiceRoller2();
869
- NumberGenerator6.generator.engine = engine;
1152
+ NumberGenerator7.generator.engine = engine;
870
1153
  for (let i = 0; i < numberOfDice; i++) {
871
1154
  try {
872
1155
  roller.roll(diceToRoll);
@@ -889,7 +1172,7 @@ function handleBulkRollsWithComparison(numberOfDice, diceToRoll, comments, activ
889
1172
  const results = [];
890
1173
  let successCount = 0;
891
1174
  const roller = new DiceRoller2();
892
- NumberGenerator6.generator.engine = engine;
1175
+ NumberGenerator7.generator.engine = engine;
893
1176
  let trivialComparisonDetected = false;
894
1177
  const formatOutput = (output, addStar) => {
895
1178
  const formatted = addStar && isCurlyBulk ? output.replace(
@@ -923,7 +1206,7 @@ function handleBulkRollsWithComparison(numberOfDice, diceToRoll, comments, activ
923
1206
  results.push(formattedRollOutput);
924
1207
  } else {
925
1208
  const rollTotal = rollInstance.total;
926
- const isSuccess = evaluate6(
1209
+ const isSuccess = evaluate8(
927
1210
  `${rollTotal}${activeCompare.sign}${activeCompare.value}`
928
1211
  );
929
1212
  if (isSuccess) successCount++;
@@ -986,7 +1269,7 @@ function handleBulkRollsWithComparison(numberOfDice, diceToRoll, comments, activ
986
1269
  }
987
1270
 
988
1271
  // src/dice/pity.ts
989
- import { evaluate as evaluate7 } from "mathjs";
1272
+ import { evaluate as evaluate9 } from "mathjs";
990
1273
  function handlePitySystem(dice, compare, diceRoll, roller, engine) {
991
1274
  const currentRoll = Array.isArray(diceRoll) ? diceRoll[0] : diceRoll;
992
1275
  const maxPossible = currentRoll ? currentRoll.maxTotal : null;
@@ -994,7 +1277,7 @@ function handlePitySystem(dice, compare, diceRoll, roller, engine) {
994
1277
  if (!isComparisonPossible) {
995
1278
  return { rerollCount: 0 };
996
1279
  }
997
- let isFail = evaluate7(`${roller.total}${compare.sign}${compare.value}`);
1280
+ let isFail = evaluate9(`${roller.total}${compare.sign}${compare.value}`);
998
1281
  if (isFail) {
999
1282
  return { rerollCount: 0 };
1000
1283
  }
@@ -1009,14 +1292,14 @@ function handlePitySystem(dice, compare, diceRoll, roller, engine) {
1009
1292
  }
1010
1293
  rerollCount++;
1011
1294
  if (res && res.total !== void 0) {
1012
- isFail = evaluate7(`${res.total}${compare.sign}${compare.value}`);
1295
+ isFail = evaluate9(`${res.total}${compare.sign}${compare.value}`);
1013
1296
  }
1014
1297
  }
1015
1298
  return { rerollCount, result: res };
1016
1299
  }
1017
1300
 
1018
1301
  // src/roll.ts
1019
- function roll(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, sort) {
1302
+ function roll(dice, engine = NumberGenerator8.engines.nodeCrypto, pity, sort) {
1020
1303
  if (sort === "none" /* None */) sort = void 0;
1021
1304
  const prepared = prepareDice(dice);
1022
1305
  if (!prepared.dice.includes("d")) return void 0;
@@ -1058,7 +1341,7 @@ function roll(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, sort) {
1058
1341
  );
1059
1342
  }
1060
1343
  const roller = new DiceRoller3();
1061
- NumberGenerator7.generator.engine = engine;
1344
+ NumberGenerator8.generator.engine = engine;
1062
1345
  let diceWithoutComment = processedDice.replace(COMMENT_REGEX, "").trimEnd();
1063
1346
  diceWithoutComment = setSortOrder(diceWithoutComment, sort);
1064
1347
  let diceRoll;
@@ -1135,7 +1418,7 @@ function roll(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, sort) {
1135
1418
  trivial: compare?.trivial ? true : void 0
1136
1419
  };
1137
1420
  }
1138
- function sharedRolls(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, explodingSuccessMain, diceDisplay, isSharedCurly, sort) {
1421
+ function sharedRolls(dice, engine = NumberGenerator8.engines.nodeCrypto, pity, explodingSuccessMain, diceDisplay, isSharedCurly, sort) {
1139
1422
  if (!explodingSuccessMain)
1140
1423
  explodingSuccessMain = normalizeExplodingSuccess(dice.split(";")[0] ?? dice);
1141
1424
  if (explodingSuccessMain) {
@@ -1170,13 +1453,13 @@ function sharedRolls(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, e
1170
1453
  const sortFromMain = getSortOrder(diceMain);
1171
1454
  const rollBounds = getRollBounds(diceMain, engine);
1172
1455
  let diceResult = roll(diceMain, engine, pity, sort);
1173
- if (!diceResult || !diceResult.total) {
1456
+ if (!diceResult?.total) {
1174
1457
  if (hidden) {
1175
1458
  diceResult = roll(fixParenthesis(split[0]), engine, pity, sort);
1176
1459
  hidden = false;
1177
1460
  } else return void 0;
1178
1461
  }
1179
- if (!diceResult || !diceResult.total) return void 0;
1462
+ if (!diceResult?.total) return void 0;
1180
1463
  if (explodingSuccessMain && diceResult.result) {
1181
1464
  const values = extractValuesFromOutput(diceResult.result);
1182
1465
  diceResult.total = values.filter(
@@ -1218,7 +1501,7 @@ function sharedRolls(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, e
1218
1501
  const { diceAll } = replaceText(element, diceResult.total, diceResult.dice);
1219
1502
  let successCount = 0;
1220
1503
  try {
1221
- const evaluated = evaluate8(toRoll);
1504
+ const evaluated = evaluate10(toRoll);
1222
1505
  successCount = evaluated ? 1 : 0;
1223
1506
  } catch (error) {
1224
1507
  const evaluated = roll(toRoll, engine, pity);
@@ -1252,7 +1535,7 @@ function sharedRolls(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, e
1252
1535
  diceResult.dice
1253
1536
  );
1254
1537
  try {
1255
- const evaluated = evaluate8(toRoll);
1538
+ const evaluated = evaluate10(toRoll);
1256
1539
  results.push(`\u25C8 ${comment}${diceAll}: ${formule} = ${evaluated}`);
1257
1540
  total += Number.parseInt(evaluated, 10);
1258
1541
  } catch (error) {
@@ -1281,7 +1564,7 @@ function sharedRolls(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, e
1281
1564
  trivial: hasTrivialComparison ? true : void 0
1282
1565
  };
1283
1566
  }
1284
- function replaceInFormula(element, diceResult, compareResult, res, engine = NumberGenerator7.engines.nodeCrypto, pity) {
1567
+ function replaceInFormula(element, diceResult, compareResult, res, engine = NumberGenerator8.engines.nodeCrypto, pity) {
1285
1568
  const { formule, diceAll } = replaceText(
1286
1569
  element,
1287
1570
  diceResult.total ?? 0,
@@ -1291,7 +1574,7 @@ function replaceInFormula(element, diceResult, compareResult, res, engine = Numb
1291
1574
  const invertedSign = res ? compareResult.compare.sign : inverseSign(compareResult.compare.sign);
1292
1575
  let evaluateRoll;
1293
1576
  try {
1294
- evaluateRoll = evaluate8(compareResult.dice);
1577
+ evaluateRoll = evaluate10(compareResult.dice);
1295
1578
  return `${validSign} ${diceAll}: ${formule} = ${evaluateRoll}${invertedSign}${compareResult.compare?.value}`;
1296
1579
  } catch (error) {
1297
1580
  const evaluateRoll2 = roll(compareResult.dice, engine, pity);
@@ -1300,218 +1583,6 @@ function replaceInFormula(element, diceResult, compareResult, res, engine = Numb
1300
1583
  return `${validSign} ${diceAll}: ${formule} = ${evaluateRoll2}${invertedSign}${compareResult.compare?.value}`;
1301
1584
  }
1302
1585
  }
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
1586
  export {
1516
1587
  COMMENT_REGEX,
1517
1588
  DETECT_CRITICAL,
@@ -1553,6 +1624,7 @@ export {
1553
1624
  replaceFormulaInDice,
1554
1625
  replaceInFormula,
1555
1626
  replaceUnknown,
1627
+ resolveFormulaHint,
1556
1628
  roll,
1557
1629
  standardizeDice,
1558
1630
  templateSchema,