@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.d.mts +40 -16
- package/dist/index.d.ts +40 -16
- package/dist/index.js +520 -442
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +513 -437
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
209
|
-
import { evaluate as
|
|
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
|
|
213
|
-
import { evaluate as
|
|
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/
|
|
215
|
+
// src/utils.ts
|
|
216
|
+
import "uniformize";
|
|
216
217
|
import { NumberGenerator as NumberGenerator3 } from "@dice-roller/rpg-dice-roller";
|
|
217
|
-
import {
|
|
218
|
+
import { Random as Random2 } from "random-js";
|
|
218
219
|
|
|
219
|
-
// src/
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
|
283
|
-
searchTerm.standardize(),
|
|
284
|
-
normalizeRecord,
|
|
285
|
-
similarityThreshold
|
|
286
|
-
) || null;
|
|
265
|
+
return replaceFormulaInDice(newDice);
|
|
287
266
|
}
|
|
288
|
-
function
|
|
289
|
-
|
|
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
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
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
|
|
294
|
+
return newStats;
|
|
300
295
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
|
307
|
-
return
|
|
308
|
-
|
|
309
|
-
|
|
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
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const [,
|
|
328
|
-
|
|
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
|
-
|
|
366
|
+
testDiceRegistered(statistiqueTemplate, engine);
|
|
367
|
+
testStatCombinaison(statistiqueTemplate, engine);
|
|
368
|
+
return statistiqueTemplate;
|
|
331
369
|
}
|
|
332
|
-
function
|
|
333
|
-
|
|
334
|
-
if (
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
417
|
+
if (error.length > 0) throw new FormulaError(error.join(", "), "testStatCombinaison");
|
|
418
|
+
return;
|
|
394
419
|
}
|
|
395
|
-
function
|
|
396
|
-
|
|
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 =
|
|
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 =
|
|
409
|
-
if (!rng) rng = new
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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
|
|
1001
|
+
import { evaluate as evaluate7 } from "mathjs";
|
|
715
1002
|
function calculator(sign, value, total) {
|
|
716
1003
|
if (sign === "^") sign = "**";
|
|
717
|
-
return
|
|
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 =
|
|
1039
|
+
function getRollBounds(dice, engine = NumberGenerator6.engines.nodeCrypto) {
|
|
753
1040
|
try {
|
|
754
1041
|
const roller = new DiceRoller();
|
|
755
|
-
|
|
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(
|
|
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
|
-
|
|
833
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|