@dicelette/core 1.28.3 → 1.28.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -3
- package/dist/index.d.mts +3 -7
- package/dist/index.d.ts +3 -7
- package/dist/index.js +77 -80
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +77 -79
- package/dist/index.mjs.map +1 -1
- package/package.json +62 -58
package/dist/index.mjs
CHANGED
|
@@ -166,6 +166,7 @@ var templateSchema = z.object({
|
|
|
166
166
|
});
|
|
167
167
|
|
|
168
168
|
// src/regex.ts
|
|
169
|
+
var REGEX_CACHE_MAX = 500;
|
|
169
170
|
var regexCache = /* @__PURE__ */ new Map();
|
|
170
171
|
var NORMALIZE_SINGLE_DICE = (str) => str.replace(/\b1d(\d+)/gi, "d$1");
|
|
171
172
|
var REMOVER_PATTERN = {
|
|
@@ -180,6 +181,10 @@ function getCachedRegex(pattern, flags = "") {
|
|
|
180
181
|
const key = `${pattern}|${flags}`;
|
|
181
182
|
let regex = regexCache.get(key);
|
|
182
183
|
if (!regex) {
|
|
184
|
+
if (regexCache.size >= REGEX_CACHE_MAX) {
|
|
185
|
+
const oldest = regexCache.keys().next().value;
|
|
186
|
+
if (oldest !== void 0) regexCache.delete(oldest);
|
|
187
|
+
}
|
|
183
188
|
regex = new RegExp(pattern, flags);
|
|
184
189
|
regexCache.set(key, regex);
|
|
185
190
|
}
|
|
@@ -225,12 +230,12 @@ import { NumberGenerator as NumberGenerator2 } from "@dice-roller/rpg-dice-rolle
|
|
|
225
230
|
function evalStatsDice(testDice, allStats, engine = NumberGenerator2.engines.nodeCrypto, pity) {
|
|
226
231
|
let dice = testDice.trimEnd();
|
|
227
232
|
if (allStats && Object.keys(allStats).length > 0) {
|
|
233
|
+
dice = dice.standardize();
|
|
228
234
|
const names = Object.keys(allStats);
|
|
229
235
|
for (const name of names) {
|
|
230
|
-
const regex =
|
|
231
|
-
if (dice.
|
|
232
|
-
|
|
233
|
-
dice = dice.standardize().replace(regex, statValue.toString()).trimEnd();
|
|
236
|
+
const regex = getCachedRegex(name.standardize().escapeRegex(), "gi");
|
|
237
|
+
if (dice.match(regex)) {
|
|
238
|
+
dice = dice.replace(regex, allStats[name].toString()).trimEnd();
|
|
234
239
|
}
|
|
235
240
|
}
|
|
236
241
|
}
|
|
@@ -248,8 +253,8 @@ function diceRandomParse(value, template, engine = NumberGenerator2.engines.node
|
|
|
248
253
|
const statNames = Object.keys(template.statistics);
|
|
249
254
|
let newDice = value;
|
|
250
255
|
for (const name of statNames) {
|
|
251
|
-
const regex =
|
|
252
|
-
if (
|
|
256
|
+
const regex = getCachedRegex(name.standardize().escapeRegex(), "gi");
|
|
257
|
+
if (newDice.match(regex)) {
|
|
253
258
|
let max;
|
|
254
259
|
let min;
|
|
255
260
|
const foundStat = template.statistics?.[name];
|
|
@@ -259,7 +264,7 @@ function diceRandomParse(value, template, engine = NumberGenerator2.engines.node
|
|
|
259
264
|
}
|
|
260
265
|
const total = template.total || 100;
|
|
261
266
|
const randomStatValue = generateRandomStat(total, max, min, engine);
|
|
262
|
-
newDice =
|
|
267
|
+
newDice = newDice.replace(regex, randomStatValue.toString());
|
|
263
268
|
}
|
|
264
269
|
}
|
|
265
270
|
return replaceFormulaInDice(newDice);
|
|
@@ -282,7 +287,7 @@ function evalCombinaison(combinaison, stats) {
|
|
|
282
287
|
for (const [stat, combin] of Object.entries(combinaison)) {
|
|
283
288
|
let formula = combin.standardize();
|
|
284
289
|
for (const [statName, value] of Object.entries(stats)) {
|
|
285
|
-
const regex =
|
|
290
|
+
const regex = getCachedRegex(statName.standardize().escapeRegex(), "gi");
|
|
286
291
|
formula = formula.replace(regex, value.toString());
|
|
287
292
|
}
|
|
288
293
|
try {
|
|
@@ -296,7 +301,7 @@ function evalCombinaison(combinaison, stats) {
|
|
|
296
301
|
function evalOneCombinaison(combinaison, stats) {
|
|
297
302
|
let formula = combinaison.standardize();
|
|
298
303
|
for (const [statName, value] of Object.entries(stats)) {
|
|
299
|
-
const regex =
|
|
304
|
+
const regex = getCachedRegex(statName.standardize().escapeRegex(), "gi");
|
|
300
305
|
formula = formula.replace(regex, value.toString());
|
|
301
306
|
}
|
|
302
307
|
try {
|
|
@@ -344,7 +349,7 @@ function verifyTemplateValue(template, verify = true, engine = NumberGenerator2.
|
|
|
344
349
|
engine
|
|
345
350
|
);
|
|
346
351
|
const rolled = roll(cleanedDice2, engine);
|
|
347
|
-
if (!rolled) throw new DiceTypeError(cleanedDice2, "
|
|
352
|
+
if (!rolled) throw new DiceTypeError(cleanedDice2, "roll");
|
|
348
353
|
}
|
|
349
354
|
if (statistiqueTemplate.customCritical) {
|
|
350
355
|
if (!statistiqueTemplate.diceType) {
|
|
@@ -369,15 +374,16 @@ function verifyTemplateValue(template, verify = true, engine = NumberGenerator2.
|
|
|
369
374
|
}
|
|
370
375
|
function testDiceRegistered(template, engine = NumberGenerator2.engines.nodeCrypto) {
|
|
371
376
|
if (!template.damage) return;
|
|
372
|
-
|
|
373
|
-
if (
|
|
374
|
-
|
|
377
|
+
const damageEntries = Object.entries(template.damage);
|
|
378
|
+
if (damageEntries.length === 0) throw new EmptyObjectError();
|
|
379
|
+
if (damageEntries.length > 25) throw new TooManyDice();
|
|
380
|
+
for (const [name, dice] of damageEntries) {
|
|
375
381
|
if (!dice) continue;
|
|
376
382
|
const diceReplaced = replaceExpByRandom(dice);
|
|
377
383
|
const randomDiceParsed = diceRandomParse(diceReplaced, template, engine);
|
|
378
384
|
try {
|
|
379
385
|
const rolled = roll(randomDiceParsed, engine);
|
|
380
|
-
if (!rolled) throw new DiceTypeError(name, "
|
|
386
|
+
if (!rolled) throw new DiceTypeError(name, "testDiceRegistered", dice);
|
|
381
387
|
} catch (error) {
|
|
382
388
|
throw new DiceTypeError(name, "testDiceRegistered", error);
|
|
383
389
|
}
|
|
@@ -385,19 +391,14 @@ function testDiceRegistered(template, engine = NumberGenerator2.engines.nodeCryp
|
|
|
385
391
|
}
|
|
386
392
|
function testStatCombinaison(template, engine = NumberGenerator2.engines.nodeCrypto) {
|
|
387
393
|
if (!template.statistics) return;
|
|
388
|
-
const onlycombinaisonStats =
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
Object.entries(template.statistics).filter(([_, value]) => !value.combinaison)
|
|
395
|
-
);
|
|
394
|
+
const onlycombinaisonStats = {};
|
|
395
|
+
const allOtherStats = {};
|
|
396
|
+
for (const [k, v] of Object.entries(template.statistics)) {
|
|
397
|
+
if (v.combinaison !== void 0) onlycombinaisonStats[k] = v;
|
|
398
|
+
else allOtherStats[k] = v;
|
|
399
|
+
}
|
|
396
400
|
if (Object.keys(onlycombinaisonStats).length === 0) return;
|
|
397
|
-
|
|
398
|
-
(stat) => !template.statistics[stat].combinaison
|
|
399
|
-
);
|
|
400
|
-
if (allStats.length === 0) throw new NoStatisticsError();
|
|
401
|
+
if (Object.keys(allOtherStats).length === 0) throw new NoStatisticsError();
|
|
401
402
|
const error = [];
|
|
402
403
|
for (const [stat, value] of Object.entries(onlycombinaisonStats)) {
|
|
403
404
|
let formula = value.combinaison;
|
|
@@ -405,7 +406,7 @@ function testStatCombinaison(template, engine = NumberGenerator2.engines.nodeCry
|
|
|
405
406
|
const { max, min } = data;
|
|
406
407
|
const total = template.total || 100;
|
|
407
408
|
const randomStatValue = generateRandomStat(total, max, min, engine);
|
|
408
|
-
const regex =
|
|
409
|
+
const regex = getCachedRegex(other.escapeRegex(), "gi");
|
|
409
410
|
formula = formula.replace(regex, randomStatValue.toString());
|
|
410
411
|
}
|
|
411
412
|
try {
|
|
@@ -418,27 +419,21 @@ function testStatCombinaison(template, engine = NumberGenerator2.engines.nodeCry
|
|
|
418
419
|
return;
|
|
419
420
|
}
|
|
420
421
|
function generateRandomStat(total = 100, max, min, engine = NumberGenerator2.engines.nodeCrypto) {
|
|
421
|
-
|
|
422
|
+
if (total <= 1) throw new RangeError(`total must be greater than 1, got ${total}`);
|
|
422
423
|
const random = new Random(engine || NumberGenerator2.engines.nodeCrypto);
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
else randomStatValue = randomInt(1, total, engine, random);
|
|
428
|
-
}
|
|
429
|
-
return randomStatValue;
|
|
424
|
+
const effectiveMin = Math.max(min ?? 1, 1);
|
|
425
|
+
const effectiveMax = Math.min(max ?? total - 1, total - 1);
|
|
426
|
+
if (effectiveMin > effectiveMax) throw new MaxGreater(effectiveMin, effectiveMax);
|
|
427
|
+
return randomInt(effectiveMin, effectiveMax, engine, random);
|
|
430
428
|
}
|
|
431
429
|
|
|
432
430
|
// src/utils.ts
|
|
433
431
|
function splitDiceComment(dice) {
|
|
434
|
-
const match = /\s
|
|
432
|
+
const match = /\s(#|\/{2}|\[|\/\*)(?<comment>.*)/i.exec(dice);
|
|
435
433
|
if (!match?.groups) return { dice: dice.trimEnd(), comment: void 0 };
|
|
436
434
|
const comment = match.groups.comment.trim() || void 0;
|
|
437
435
|
return { dice: dice.slice(0, match.index).trimEnd(), comment };
|
|
438
436
|
}
|
|
439
|
-
function escapeRegex(string) {
|
|
440
|
-
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
441
|
-
}
|
|
442
437
|
function standardizeDice(dice) {
|
|
443
438
|
return dice.replace(
|
|
444
439
|
/(\[[^\]]+])|([^[]+)/g,
|
|
@@ -583,32 +578,31 @@ import { NumberGenerator as NumberGenerator5 } from "@dice-roller/rpg-dice-rolle
|
|
|
583
578
|
import { evaluate as evaluate3 } from "mathjs";
|
|
584
579
|
|
|
585
580
|
// src/dice/replace.ts
|
|
581
|
+
var PARENTHESIS_REGEX = /d\((\d+)\)/g;
|
|
582
|
+
var BRACKET_COMMENT_REGEX = /\[(?<comments>.*?)\]/;
|
|
583
|
+
var OPTIONAL_COMMENT_REGEX = /\s+(#|\/\/)(?<comment>.*)/;
|
|
586
584
|
function replaceUnwantedText(dice, sortOrder) {
|
|
587
|
-
|
|
588
|
-
if (sortOrder)
|
|
585
|
+
let d = dice.replaceAll(/[{}]/g, "").replaceAll(/s[ad]/gi, "");
|
|
586
|
+
if (sortOrder) d = sortDice(d, sortOrder);
|
|
587
|
+
if (!d.length) throw new DiceTypeError(dice, "empty_dice");
|
|
589
588
|
return d;
|
|
590
589
|
}
|
|
591
590
|
function sortDice(dice, sortOrder) {
|
|
592
591
|
if (sortOrder === "none" /* None */) return dice;
|
|
593
592
|
const dices = dice.split(/; ?/);
|
|
593
|
+
const decorated = dices.map((d) => ({
|
|
594
|
+
d,
|
|
595
|
+
v: Number.parseInt(d.split("= ")[1], 10) || 0
|
|
596
|
+
}));
|
|
594
597
|
if (sortOrder === "sa" /* Ascending */) {
|
|
595
|
-
|
|
596
|
-
const totalA = Number.parseInt(a.split("= ")[1], 10) || 0;
|
|
597
|
-
const totalB = Number.parseInt(b.split("= ")[1], 10) || 0;
|
|
598
|
-
return totalB - totalA;
|
|
599
|
-
});
|
|
598
|
+
decorated.sort((a, b) => b.v - a.v);
|
|
600
599
|
} else if (sortOrder === "sd" /* Descending */) {
|
|
601
|
-
|
|
602
|
-
const totalA = Number.parseInt(a.split("= ")[1], 10) || 0;
|
|
603
|
-
const totalB = Number.parseInt(b.split("= ")[1], 10) || 0;
|
|
604
|
-
return totalA - totalB;
|
|
605
|
-
});
|
|
600
|
+
decorated.sort((a, b) => a.v - b.v);
|
|
606
601
|
}
|
|
607
|
-
return
|
|
602
|
+
return decorated.map((x) => x.d).join("; ");
|
|
608
603
|
}
|
|
609
604
|
function fixParenthesis(dice) {
|
|
610
|
-
|
|
611
|
-
return dice.replaceAll(parenthesisRegex, (_match, p1) => `d${p1}`);
|
|
605
|
+
return dice.replaceAll(PARENTHESIS_REGEX, (_match, p1) => `d${p1}`);
|
|
612
606
|
}
|
|
613
607
|
function replaceText(element, total, dice) {
|
|
614
608
|
return {
|
|
@@ -617,12 +611,10 @@ function replaceText(element, total, dice) {
|
|
|
617
611
|
};
|
|
618
612
|
}
|
|
619
613
|
function formatComment(dice) {
|
|
620
|
-
const
|
|
621
|
-
const commentsMatch = commentsRegex.exec(dice);
|
|
614
|
+
const commentsMatch = BRACKET_COMMENT_REGEX.exec(dice);
|
|
622
615
|
const comments = commentsMatch?.groups?.comments ? `${commentsMatch.groups.comments}` : "";
|
|
623
|
-
const diceWithoutBrackets = dice.replace(
|
|
624
|
-
const
|
|
625
|
-
const optionalComments = optionalCommentsRegex.exec(diceWithoutBrackets);
|
|
616
|
+
const diceWithoutBrackets = dice.replace(BRACKET_COMMENT_REGEX, "");
|
|
617
|
+
const optionalComments = OPTIONAL_COMMENT_REGEX.exec(diceWithoutBrackets);
|
|
626
618
|
const optional = optionalComments?.groups?.comment ? `${optionalComments.groups.comment.trim()}` : "";
|
|
627
619
|
let finalComment = "";
|
|
628
620
|
if (comments && optional) finalComment = `__${comments} ${optional}__ \u2014 `;
|
|
@@ -774,23 +766,27 @@ function calculateSimilarity(str1, str2) {
|
|
|
774
766
|
return (longer.length - distance) / longer.length;
|
|
775
767
|
}
|
|
776
768
|
function levenshteinDistance(str1, str2) {
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
769
|
+
if (str1.length > str2.length) {
|
|
770
|
+
[str1, str2] = [str2, str1];
|
|
771
|
+
}
|
|
772
|
+
let prev = Array.from({ length: str1.length + 1 }, (_, i) => i);
|
|
773
|
+
let curr = new Array(str1.length + 1);
|
|
780
774
|
for (let j = 1; j <= str2.length; j++) {
|
|
775
|
+
curr[0] = j;
|
|
781
776
|
for (let i = 1; i <= str1.length; i++) {
|
|
782
777
|
const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
|
|
783
|
-
|
|
784
|
-
|
|
778
|
+
curr[i] = Math.min(
|
|
779
|
+
curr[i - 1] + 1,
|
|
785
780
|
// insertion
|
|
786
|
-
|
|
781
|
+
prev[i] + 1,
|
|
787
782
|
// deletion
|
|
788
|
-
|
|
783
|
+
prev[i - 1] + cost
|
|
789
784
|
// substitution
|
|
790
785
|
);
|
|
791
786
|
}
|
|
787
|
+
[prev, curr] = [curr, prev];
|
|
792
788
|
}
|
|
793
|
-
return
|
|
789
|
+
return prev[str1.length];
|
|
794
790
|
}
|
|
795
791
|
function findBestStatMatch(searchTerm, normalizedStats, similarityThreshold = MIN_THRESHOLD_MATCH) {
|
|
796
792
|
const exact = normalizedStats.get(searchTerm);
|
|
@@ -1067,25 +1063,20 @@ function prepareDice(diceInput) {
|
|
|
1067
1063
|
dice = dice.replaceAll(REMOVER_PATTERN.CRITICAL_BLOCK, "").trimEnd();
|
|
1068
1064
|
const explodingSuccess = normalizeExplodingSuccess(dice);
|
|
1069
1065
|
if (explodingSuccess) dice = explodingSuccess.dice;
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
diceDisplay = explodingSuccess?.originalDice ?? mainDice;
|
|
1074
|
-
} else {
|
|
1075
|
-
diceDisplay = explodingSuccess?.originalDice ?? dice;
|
|
1076
|
-
}
|
|
1066
|
+
const sharedSeparatorIndex = dice.indexOf(";");
|
|
1067
|
+
const hasSharedSeparator = sharedSeparatorIndex !== -1;
|
|
1068
|
+
let diceDisplay = explodingSuccess?.originalDice ?? (hasSharedSeparator ? dice.slice(0, sharedSeparatorIndex) : dice);
|
|
1077
1069
|
const curlyBulkMatch = dice.match(/^\{(\d+#.*)\}$/);
|
|
1078
1070
|
const isCurlyBulk = !!curlyBulkMatch;
|
|
1079
1071
|
const bulkContent = isCurlyBulk ? curlyBulkMatch[1] : "";
|
|
1080
|
-
const isSharedRoll = dice.includes(";");
|
|
1081
1072
|
let isSharedCurly = false;
|
|
1082
|
-
if (
|
|
1073
|
+
if (hasSharedSeparator && dice.match(/^\{.*;\s*.*\}$/)) {
|
|
1083
1074
|
dice = dice.slice(1, -1);
|
|
1084
1075
|
isSharedCurly = true;
|
|
1085
1076
|
diceDisplay = diceDisplay.slice(1);
|
|
1086
1077
|
}
|
|
1087
1078
|
let isSimpleCurly = false;
|
|
1088
|
-
if (!isCurlyBulk && !
|
|
1079
|
+
if (!isCurlyBulk && !hasSharedSeparator && dice.match(/^\{.*\}$/)) {
|
|
1089
1080
|
const innerContent = dice.slice(1, -1);
|
|
1090
1081
|
const hasModifiers = innerContent.match(/[+\-*/%^]/);
|
|
1091
1082
|
const hasComparison = innerContent.match(/(([><=!]+\d+f)|([><=]|!=)+\d+)/);
|
|
@@ -1098,7 +1089,7 @@ function prepareDice(diceInput) {
|
|
|
1098
1089
|
dice,
|
|
1099
1090
|
diceDisplay,
|
|
1100
1091
|
explodingSuccess,
|
|
1101
|
-
isSharedRoll,
|
|
1092
|
+
isSharedRoll: hasSharedSeparator,
|
|
1102
1093
|
isSharedCurly,
|
|
1103
1094
|
isCurlyBulk,
|
|
1104
1095
|
bulkContent,
|
|
@@ -1115,7 +1106,13 @@ function getSortOrder(dice) {
|
|
|
1115
1106
|
function handleBulkRolls(dice, isCurlyBulk, bulkContent, compare, explodingSuccess, diceDisplay, engine, sort) {
|
|
1116
1107
|
const bulkProcessContent = isCurlyBulk ? bulkContent : dice;
|
|
1117
1108
|
const diceArray = bulkProcessContent.split("#");
|
|
1109
|
+
if (!isNumber(diceArray[0])) {
|
|
1110
|
+
throw new DiceTypeError(dice, "bulk_number");
|
|
1111
|
+
}
|
|
1118
1112
|
const numberOfDice = Number.parseInt(diceArray[0], 10);
|
|
1113
|
+
if (numberOfDice <= 0) {
|
|
1114
|
+
throw new DiceTypeError(dice, "bulk_zero");
|
|
1115
|
+
}
|
|
1119
1116
|
const { dice: diceToRollBase, comment: comments } = splitDiceComment(diceArray[1]);
|
|
1120
1117
|
let diceToRoll = diceToRollBase;
|
|
1121
1118
|
let curlyCompare;
|
|
@@ -1298,6 +1295,7 @@ function handlePitySystem(dice, compare, diceRoll, roller, engine) {
|
|
|
1298
1295
|
isFail = evaluate9(`${res.total}${compare.sign}${compare.value}`);
|
|
1299
1296
|
}
|
|
1300
1297
|
}
|
|
1298
|
+
if (!res?.result.length) throw new DiceTypeError(dice, "empty_dice");
|
|
1301
1299
|
return { rerollCount, result: res };
|
|
1302
1300
|
}
|
|
1303
1301
|
|
|
@@ -1399,6 +1397,7 @@ function roll(dice, engine = NumberGenerator8.engines.nodeCrypto, pity, sort, co
|
|
|
1399
1397
|
prepared.explodingSuccess.normalizedSegment,
|
|
1400
1398
|
prepared.explodingSuccess.originalSegment
|
|
1401
1399
|
);
|
|
1400
|
+
if (!resultOutput.length) throw new DiceTypeError(dice, "empty_dice");
|
|
1402
1401
|
return {
|
|
1403
1402
|
dice: prepared.isSimpleCurly ? finalDiceDisplay : prepared.diceDisplay,
|
|
1404
1403
|
result: resultOutput,
|
|
@@ -1608,7 +1607,6 @@ export {
|
|
|
1608
1607
|
createCriticalCustom,
|
|
1609
1608
|
diceRandomParse,
|
|
1610
1609
|
diceTypeRandomParse,
|
|
1611
|
-
escapeRegex,
|
|
1612
1610
|
evalCombinaison,
|
|
1613
1611
|
evalOneCombinaison,
|
|
1614
1612
|
evalStatsDice,
|