@dicelette/core 1.23.1 → 1.24.1
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 +38 -33
- package/dist/index.d.ts +38 -33
- package/dist/index.js +1062 -579
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1061 -579
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -2
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,341 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import {
|
|
1
|
+
// src/engine.ts
|
|
2
|
+
import { NumberGenerator } from "@dice-roller/rpg-dice-roller";
|
|
3
|
+
function getEngineId(engine) {
|
|
4
|
+
if (engine === NumberGenerator.engines.nodeCrypto) return "nodeCrypto";
|
|
5
|
+
if (engine === NumberGenerator.engines.nativeMath) return "nativeMath";
|
|
6
|
+
if (engine === NumberGenerator.engines.browserCrypto) return "browserCrypto";
|
|
7
|
+
try {
|
|
8
|
+
const e = engine;
|
|
9
|
+
if (e && typeof e === "object") {
|
|
10
|
+
if (typeof e.name === "string" && e.name) return e.name;
|
|
11
|
+
if (e.constructor?.name) return e.constructor.name;
|
|
12
|
+
}
|
|
13
|
+
} catch {
|
|
14
|
+
}
|
|
15
|
+
return "unknown";
|
|
16
|
+
}
|
|
17
|
+
function getEngine(engine) {
|
|
18
|
+
switch (engine) {
|
|
19
|
+
case "nativeMath":
|
|
20
|
+
return NumberGenerator.engines.nativeMath;
|
|
21
|
+
case "browserCrypto":
|
|
22
|
+
return NumberGenerator.engines.browserCrypto;
|
|
23
|
+
case "nodeCrypto":
|
|
24
|
+
return NumberGenerator.engines.nodeCrypto;
|
|
25
|
+
default:
|
|
26
|
+
return NumberGenerator.engines.nativeMath;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/errors.ts
|
|
31
|
+
var DiceTypeError = class extends Error {
|
|
32
|
+
dice;
|
|
33
|
+
cause;
|
|
34
|
+
method;
|
|
35
|
+
constructor(dice, cause, method) {
|
|
36
|
+
super(dice);
|
|
37
|
+
this.name = "Invalid_Dice_Type";
|
|
38
|
+
this.dice = dice;
|
|
39
|
+
this.cause = cause;
|
|
40
|
+
this.method = method;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var FormulaError = class extends Error {
|
|
44
|
+
formula;
|
|
45
|
+
cause;
|
|
46
|
+
method;
|
|
47
|
+
constructor(formula, cause, method) {
|
|
48
|
+
super(formula);
|
|
49
|
+
this.name = "Invalid_Formula";
|
|
50
|
+
this.formula = formula;
|
|
51
|
+
this.cause = cause;
|
|
52
|
+
this.method = method;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var MaxGreater = class extends Error {
|
|
56
|
+
name;
|
|
57
|
+
value;
|
|
58
|
+
max;
|
|
59
|
+
constructor(value, max) {
|
|
60
|
+
super(value.toString());
|
|
61
|
+
this.name = "Max_Greater";
|
|
62
|
+
this.value = value;
|
|
63
|
+
this.max = max;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var EmptyObjectError = class extends Error {
|
|
67
|
+
name;
|
|
68
|
+
constructor() {
|
|
69
|
+
super();
|
|
70
|
+
this.name = "Empty_Object";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var TooManyDice = class extends Error {
|
|
74
|
+
name;
|
|
75
|
+
constructor() {
|
|
76
|
+
super();
|
|
77
|
+
this.name = "Too_Many_Dice";
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var TooManyStats = class extends Error {
|
|
81
|
+
name;
|
|
82
|
+
constructor() {
|
|
83
|
+
super();
|
|
84
|
+
this.name = "Too_Many_Stats";
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var NoStatisticsError = class extends Error {
|
|
88
|
+
name;
|
|
89
|
+
constructor() {
|
|
90
|
+
super();
|
|
91
|
+
this.name = "No_Statistics";
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// src/interfaces/index.ts
|
|
96
|
+
var SortOrder = /* @__PURE__ */ ((SortOrder2) => {
|
|
97
|
+
SortOrder2["Ascending"] = "sa";
|
|
98
|
+
SortOrder2["Descending"] = "sd";
|
|
99
|
+
SortOrder2["None"] = "none";
|
|
100
|
+
return SortOrder2;
|
|
101
|
+
})(SortOrder || {});
|
|
102
|
+
|
|
103
|
+
// src/interfaces/constant.ts
|
|
104
|
+
var COMMENT_REGEX = /\s+(#|\/{2}|\[|\/\*)(?<comment>.*)/gi;
|
|
105
|
+
var SIGN_REGEX = /==|!=|(?<![!<>])>=|(?<![!<>])<=|(?<!!)(?<![<>])>|(?<!!)(?<![<>])<|(?<!!)(?<![<>])=/;
|
|
106
|
+
var SIGN_REGEX_SPACE = /(==|!=|(?<![!<>])>=|(?<![!<>])<=|(?<!!)(?<![<>])>|(?<!!)(?<![<>])<|(?<!!)(?<![<>])=)(\S+)/;
|
|
107
|
+
var SYMBOL_DICE = "&";
|
|
108
|
+
var DETECT_CRITICAL = /\{\*?c[fs]:([<>=]|!=)+(.+?)}/gim;
|
|
109
|
+
var OPTIONAL_COMMENT = /\s+(#|\/{2}|\[|\/\*)?(?<comment>.*)/gi;
|
|
110
|
+
|
|
111
|
+
// src/interfaces/zod.ts
|
|
112
|
+
import { z } from "zod";
|
|
113
|
+
var statisticValueSchema = z.object({
|
|
114
|
+
max: z.number().transform((val) => val === 0 ? void 0 : val).optional(),
|
|
115
|
+
min: z.number().transform(
|
|
116
|
+
(val) => Number.isNaN(Number.parseInt(val, 10)) ? void 0 : val
|
|
117
|
+
).optional(),
|
|
118
|
+
combinaison: z.string().transform((str) => str.trim() || void 0).optional(),
|
|
119
|
+
exclude: z.boolean().optional()
|
|
120
|
+
}).superRefine((data, ctx) => {
|
|
121
|
+
if (data.max !== void 0 && data.min !== void 0 && data.max <= data.min) {
|
|
122
|
+
ctx.addIssue({
|
|
123
|
+
code: "custom",
|
|
124
|
+
message: `Max_Greater; ${data.min}; ${data.max}`,
|
|
125
|
+
path: ["max"]
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
var statisticSchema = z.record(z.string(), statisticValueSchema).optional().refine((stats) => !stats || Object.keys(stats).length <= 25, {
|
|
130
|
+
message: "TooManyStats"
|
|
131
|
+
});
|
|
132
|
+
var criticalSchema = z.object({
|
|
133
|
+
success: z.string().or(z.number().min(0)).optional(),
|
|
134
|
+
failure: z.string().or(z.number().min(0)).optional()
|
|
135
|
+
}).transform((values) => {
|
|
136
|
+
if (values.success === "") values.success = void 0;
|
|
137
|
+
if (values.failure === "") values.failure = void 0;
|
|
138
|
+
if (values.failure === 0) values.failure = void 0;
|
|
139
|
+
if (values.success === 0) values.success = void 0;
|
|
140
|
+
values.success = Number.parseInt(values.success, 10);
|
|
141
|
+
values.failure = Number.parseInt(values.failure, 10);
|
|
142
|
+
return values;
|
|
143
|
+
});
|
|
144
|
+
var criticalValueSchema = z.object({
|
|
145
|
+
sign: z.enum(["<", ">", "<=", ">=", "!=", "=="]),
|
|
146
|
+
value: z.string(),
|
|
147
|
+
onNaturalDice: z.boolean().optional(),
|
|
148
|
+
affectSkill: z.boolean().optional()
|
|
149
|
+
});
|
|
150
|
+
var damageSchema = z.record(z.string(), z.string()).optional().refine((stats) => !stats || Object.keys(stats).length <= 25, {
|
|
151
|
+
message: "TooManyDice"
|
|
152
|
+
});
|
|
153
|
+
var customCriticalSchema = z.record(z.string(), criticalValueSchema).optional().refine((stats) => !stats || Object.keys(stats).length <= 22, {
|
|
154
|
+
message: "TooManyDice"
|
|
155
|
+
});
|
|
156
|
+
var templateSchema = z.object({
|
|
157
|
+
charName: z.boolean().optional(),
|
|
158
|
+
statistics: statisticSchema,
|
|
159
|
+
total: z.number().min(0).transform((val) => val === 0 ? void 0 : val).optional(),
|
|
160
|
+
forceDistrib: z.boolean().optional(),
|
|
161
|
+
diceType: z.string().optional(),
|
|
162
|
+
critical: criticalSchema.optional(),
|
|
163
|
+
customCritical: customCriticalSchema,
|
|
164
|
+
damage: damageSchema
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// src/roll.ts
|
|
168
|
+
import { DiceRoller as DiceRoller3, NumberGenerator as NumberGenerator7 } from "@dice-roller/rpg-dice-roller";
|
|
169
|
+
import { evaluate as evaluate8 } from "mathjs";
|
|
170
|
+
|
|
171
|
+
// src/dice/bulk.ts
|
|
172
|
+
import { DiceRoller as DiceRoller2, NumberGenerator as NumberGenerator6 } from "@dice-roller/rpg-dice-roller";
|
|
173
|
+
import { evaluate as evaluate6 } from "mathjs";
|
|
174
|
+
|
|
175
|
+
// src/dice/compare.ts
|
|
176
|
+
import { NumberGenerator as NumberGenerator3 } from "@dice-roller/rpg-dice-roller";
|
|
177
|
+
import { evaluate as evaluate2 } from "mathjs";
|
|
178
|
+
|
|
179
|
+
// src/utils.ts
|
|
3
180
|
import { evaluate } from "mathjs";
|
|
181
|
+
import "uniformize";
|
|
182
|
+
import { NumberGenerator as NumberGenerator2 } from "@dice-roller/rpg-dice-roller";
|
|
183
|
+
import { Random } from "random-js";
|
|
184
|
+
function escapeRegex(string) {
|
|
185
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
186
|
+
}
|
|
187
|
+
function standardizeDice(dice) {
|
|
188
|
+
return dice.replace(
|
|
189
|
+
/(\[[^\]]+])|([^[]+)/g,
|
|
190
|
+
(_match, insideBrackets, outsideText) => insideBrackets ? insideBrackets : outsideText.standardize().replaceAll("df", "dF")
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
function generateStatsDice(originalDice, stats, dollarValue) {
|
|
194
|
+
let dice = originalDice.standardize();
|
|
195
|
+
if (stats && Object.keys(stats).length > 0) {
|
|
196
|
+
const statKeys = Object.keys(stats);
|
|
197
|
+
const partsRegex = /(\[[^\]]+])|([^[]+)/g;
|
|
198
|
+
let result = "";
|
|
199
|
+
let match;
|
|
200
|
+
while ((match = partsRegex.exec(dice)) !== null) {
|
|
201
|
+
const insideBrackets = match[1];
|
|
202
|
+
const outsideText = match[2];
|
|
203
|
+
if (insideBrackets) {
|
|
204
|
+
result += insideBrackets;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (!outsideText) {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const tokenRegex = /([\p{L}\p{N}_]+)/gu;
|
|
211
|
+
let lastIndex = 0;
|
|
212
|
+
let tokenMatch;
|
|
213
|
+
while ((tokenMatch = tokenRegex.exec(outsideText)) !== null) {
|
|
214
|
+
result += outsideText.slice(lastIndex, tokenMatch.index);
|
|
215
|
+
const token = tokenMatch[0];
|
|
216
|
+
const tokenStd = token.standardize();
|
|
217
|
+
const diceMatch = /^(\d*)d(.+)$/i.exec(tokenStd);
|
|
218
|
+
if (diceMatch) {
|
|
219
|
+
const diceCount = diceMatch[1] || "";
|
|
220
|
+
const afterD = diceMatch[2];
|
|
221
|
+
let foundStatAfterD = false;
|
|
222
|
+
for (const key of statKeys) {
|
|
223
|
+
const keyStd = key.standardize();
|
|
224
|
+
if (afterD === keyStd) {
|
|
225
|
+
result += `${diceCount}d${stats[key].toString()}`;
|
|
226
|
+
foundStatAfterD = true;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (foundStatAfterD) {
|
|
231
|
+
lastIndex = tokenRegex.lastIndex;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
let bestKey = null;
|
|
236
|
+
let bestScore = 0;
|
|
237
|
+
for (const key of statKeys) {
|
|
238
|
+
const keyStd = key.standardize();
|
|
239
|
+
if (tokenStd === keyStd) {
|
|
240
|
+
bestKey = key;
|
|
241
|
+
bestScore = 1;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
const score = similarityScore(tokenStd, keyStd);
|
|
245
|
+
if (score > bestScore) {
|
|
246
|
+
bestScore = score;
|
|
247
|
+
bestKey = key;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (bestKey && bestScore >= 0.6) {
|
|
251
|
+
const statValue = stats[bestKey];
|
|
252
|
+
result += statValue.toString();
|
|
253
|
+
} else {
|
|
254
|
+
result += token;
|
|
255
|
+
}
|
|
256
|
+
lastIndex = tokenRegex.lastIndex;
|
|
257
|
+
}
|
|
258
|
+
result += outsideText.slice(lastIndex);
|
|
259
|
+
}
|
|
260
|
+
dice = result;
|
|
261
|
+
}
|
|
262
|
+
if (dollarValue) dice = dice.replaceAll("$", dollarValue);
|
|
263
|
+
return replaceFormulaInDice(dice);
|
|
264
|
+
}
|
|
265
|
+
function replaceFormulaInDice(dice) {
|
|
266
|
+
const formula = /(?<formula>\{{2}(.+?)}{2})/gim;
|
|
267
|
+
let match;
|
|
268
|
+
let modifiedDice = dice;
|
|
269
|
+
while ((match = formula.exec(dice)) !== null) {
|
|
270
|
+
if (match.groups?.formula) {
|
|
271
|
+
const formulae = match.groups.formula.replaceAll("{{", "").replaceAll("}}", "");
|
|
272
|
+
try {
|
|
273
|
+
const result = evaluate(formulae);
|
|
274
|
+
modifiedDice = modifiedDice.replace(match.groups.formula, result.toString());
|
|
275
|
+
} catch (error) {
|
|
276
|
+
throw new FormulaError(match.groups.formula, "replaceFormulasInDice", error);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return cleanedDice(modifiedDice);
|
|
281
|
+
}
|
|
282
|
+
function cleanedDice(dice) {
|
|
283
|
+
return dice.replaceAll("+-", "-").replaceAll("--", "+").replaceAll("++", "+").replaceAll("=>", ">=").replaceAll("=<", "<=").trimEnd();
|
|
284
|
+
}
|
|
285
|
+
function isNumber(value) {
|
|
286
|
+
return value !== void 0 && (typeof value === "number" || !Number.isNaN(Number(value)) && typeof value === "string" && value.trim().length > 0);
|
|
287
|
+
}
|
|
288
|
+
function replaceExpByRandom(dice, engine = NumberGenerator2.engines.nodeCrypto) {
|
|
289
|
+
const diceRegex = /\{exp( ?\|\| ?(?<default>\d+))?}/gi;
|
|
290
|
+
return dice.replace(diceRegex, (_match, _p1, _p2, _offset, _string, groups) => {
|
|
291
|
+
const defaultValue = groups?.default;
|
|
292
|
+
return defaultValue ?? randomInt(1, 999, engine).toString();
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
function randomInt(min, max, engine = NumberGenerator2.engines.nodeCrypto, rng) {
|
|
296
|
+
if (!rng) rng = new Random(engine || void 0);
|
|
297
|
+
return rng.integer(min, max);
|
|
298
|
+
}
|
|
299
|
+
function levenshteinDistance(a, b) {
|
|
300
|
+
if (a === b) return 0;
|
|
301
|
+
const al = a.length;
|
|
302
|
+
const bl = b.length;
|
|
303
|
+
if (al === 0) return bl;
|
|
304
|
+
if (bl === 0) return al;
|
|
305
|
+
const v0 = new Array(bl + 1);
|
|
306
|
+
const v1 = new Array(bl + 1);
|
|
307
|
+
for (let i = 0; i <= bl; i++) v0[i] = i;
|
|
308
|
+
for (let i = 0; i < al; i++) {
|
|
309
|
+
v1[0] = i + 1;
|
|
310
|
+
for (let j = 0; j < bl; j++) {
|
|
311
|
+
const cost = a[i] === b[j] ? 0 : 1;
|
|
312
|
+
v1[j + 1] = Math.min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost);
|
|
313
|
+
}
|
|
314
|
+
for (let j = 0; j <= bl; j++) v0[j] = v1[j];
|
|
315
|
+
}
|
|
316
|
+
return v1[bl];
|
|
317
|
+
}
|
|
318
|
+
function similarityScore(a, b) {
|
|
319
|
+
const la = a.length;
|
|
320
|
+
const lb = b.length;
|
|
321
|
+
if (la === 0 && lb === 0) return 1;
|
|
322
|
+
const dist = levenshteinDistance(a, b);
|
|
323
|
+
const max = Math.max(la, lb);
|
|
324
|
+
return 1 - dist / max;
|
|
325
|
+
}
|
|
326
|
+
function createCriticalCustom(dice, customCritical, template, engine = NumberGenerator2.engines.nodeCrypto) {
|
|
327
|
+
const compareRegex = dice.match(SIGN_REGEX_SPACE);
|
|
328
|
+
let customDice = dice;
|
|
329
|
+
const compareValue = diceTypeRandomParse(customCritical.value, template, engine);
|
|
330
|
+
if (compareValue.includes("$"))
|
|
331
|
+
throw new DiceTypeError(compareValue, "createCriticalCustom");
|
|
332
|
+
const comparaison = `${customCritical.sign}${compareValue}`;
|
|
333
|
+
if (compareRegex) customDice = customDice.replace(SIGN_REGEX_SPACE, comparaison);
|
|
334
|
+
else customDice += comparaison;
|
|
335
|
+
return diceTypeRandomParse(customDice, template, engine);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/dice/compare.ts
|
|
4
339
|
function isTrivialComparison(maxValue, minValue, compare) {
|
|
5
340
|
const canSucceed = canComparisonSucceed(maxValue, compare, minValue);
|
|
6
341
|
const canFail = canComparisonFail(maxValue, compare, minValue);
|
|
@@ -31,7 +366,26 @@ function canComparisonFail(maxRollValue, compare, minRollValue = 1) {
|
|
|
31
366
|
return true;
|
|
32
367
|
}
|
|
33
368
|
}
|
|
34
|
-
function
|
|
369
|
+
function rollCompare(value, engine = NumberGenerator3.engines.nodeCrypto, pity) {
|
|
370
|
+
if (isNumber(value)) return { value: Number.parseInt(value, 10) };
|
|
371
|
+
if (!value || typeof value === "string" && value.trim() === "") {
|
|
372
|
+
return { value: 0, diceResult: value };
|
|
373
|
+
}
|
|
374
|
+
const rollComp = roll(value, engine, pity);
|
|
375
|
+
if (!rollComp?.total) {
|
|
376
|
+
try {
|
|
377
|
+
return { value: evaluate2(value), diceResult: value };
|
|
378
|
+
} catch (error) {
|
|
379
|
+
return { value: 0, diceResult: value };
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
dice: value,
|
|
384
|
+
value: rollComp.total,
|
|
385
|
+
diceResult: rollComp?.result
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function getCompare(dice, compareRegex, engine = NumberGenerator3.engines.nodeCrypto, pity) {
|
|
35
389
|
if (dice.match(/((\{.*,(.*)+\}|([><=!]+\d+f))([><=]|!=)+\d+\}?)|\{(.*)(([><=]|!=)+).*\}/))
|
|
36
390
|
return { dice, compare: void 0 };
|
|
37
391
|
dice = dice.replace(SIGN_REGEX_SPACE, "");
|
|
@@ -42,7 +396,7 @@ function getCompare(dice, compareRegex, engine = NumberGenerator.engines.nodeCry
|
|
|
42
396
|
if (sign) {
|
|
43
397
|
const toCalc = calc.replace(SIGN_REGEX, "").replace(/\s/g, "").replace(/;(.*)/, "");
|
|
44
398
|
const rCompare = rollCompare(toCalc, engine, pity);
|
|
45
|
-
const total =
|
|
399
|
+
const total = evaluate2(rCompare.value.toString());
|
|
46
400
|
dice = dice.replace(SIGN_REGEX_SPACE, `${compareSign}${total}`);
|
|
47
401
|
compare = {
|
|
48
402
|
sign: compareSign,
|
|
@@ -61,156 +415,6 @@ function getCompare(dice, compareRegex, engine = NumberGenerator.engines.nodeCry
|
|
|
61
415
|
}
|
|
62
416
|
return { dice, compare };
|
|
63
417
|
}
|
|
64
|
-
function rollCompare(value, engine = NumberGenerator.engines.nodeCrypto, pity) {
|
|
65
|
-
if (isNumber(value)) return { value: Number.parseInt(value, 10) };
|
|
66
|
-
if (!value || typeof value === "string" && value.trim() === "") {
|
|
67
|
-
return { value: 0, diceResult: value };
|
|
68
|
-
}
|
|
69
|
-
const rollComp = roll(value, engine, pity);
|
|
70
|
-
if (!rollComp?.total) {
|
|
71
|
-
try {
|
|
72
|
-
return { value: evaluate(value), diceResult: value };
|
|
73
|
-
} catch (error) {
|
|
74
|
-
return { value: 0, diceResult: value };
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
dice: value,
|
|
79
|
-
value: rollComp.total,
|
|
80
|
-
diceResult: rollComp?.result
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function createCriticalCustom(dice, customCritical, template, engine = NumberGenerator.engines.nodeCrypto) {
|
|
84
|
-
const compareRegex = dice.match(SIGN_REGEX_SPACE);
|
|
85
|
-
let customDice = dice;
|
|
86
|
-
const compareValue = diceTypeRandomParse(customCritical.value, template, engine);
|
|
87
|
-
if (compareValue.includes("$"))
|
|
88
|
-
throw new DiceTypeError(compareValue, "createCriticalCustom");
|
|
89
|
-
const comparaison = `${customCritical.sign}${compareValue}`;
|
|
90
|
-
if (compareRegex) customDice = customDice.replace(SIGN_REGEX_SPACE, comparaison);
|
|
91
|
-
else customDice += comparaison;
|
|
92
|
-
return diceTypeRandomParse(customDice, template, engine);
|
|
93
|
-
}
|
|
94
|
-
function getModifier(dice) {
|
|
95
|
-
const modifier = dice.matchAll(/(\+|-|%|\/|\^|\*|\*{2})(\d+)/gi);
|
|
96
|
-
let modificator;
|
|
97
|
-
for (const mod of modifier) {
|
|
98
|
-
if (modificator) {
|
|
99
|
-
const sign = modificator.sign;
|
|
100
|
-
let value = modificator.value;
|
|
101
|
-
if (sign) value = calculator(sign, value, Number.parseInt(mod[2], 10));
|
|
102
|
-
modificator = {
|
|
103
|
-
sign: mod[1],
|
|
104
|
-
value
|
|
105
|
-
};
|
|
106
|
-
} else {
|
|
107
|
-
modificator = {
|
|
108
|
-
sign: mod[1],
|
|
109
|
-
value: Number.parseInt(mod[2], 10)
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return modificator;
|
|
114
|
-
}
|
|
115
|
-
function roll(dice, engine = NumberGenerator.engines.nodeCrypto, pity) {
|
|
116
|
-
dice = standardizeDice(replaceFormulaInDice(dice)).replace(/^\+/, "").replaceAll("=>", ">=").replaceAll("=<", "<=").trimStart();
|
|
117
|
-
if (!dice.includes("d")) return void 0;
|
|
118
|
-
dice = dice.replaceAll(DETECT_CRITICAL, "").trimEnd();
|
|
119
|
-
const compareRegex = dice.match(SIGN_REGEX_SPACE);
|
|
120
|
-
let compare;
|
|
121
|
-
if (dice.includes(";")) return sharedRolls(dice, engine);
|
|
122
|
-
dice = fixParenthesis(dice);
|
|
123
|
-
const modificator = getModifier(dice);
|
|
124
|
-
if (compareRegex) {
|
|
125
|
-
const compareResult = getCompare(dice, compareRegex, engine, pity);
|
|
126
|
-
dice = compareResult.dice;
|
|
127
|
-
compare = compareResult.compare;
|
|
128
|
-
}
|
|
129
|
-
if (dice.match(/\d+?#(.*)/)) {
|
|
130
|
-
const diceArray = dice.split("#");
|
|
131
|
-
const numberOfDice = Number.parseInt(diceArray[0], 10);
|
|
132
|
-
const diceToRoll = diceArray[1].replace(COMMENT_REGEX, "");
|
|
133
|
-
const commentsMatch = diceArray[1].match(COMMENT_REGEX);
|
|
134
|
-
const comments = commentsMatch ? commentsMatch[2] : void 0;
|
|
135
|
-
const roller2 = new DiceRoller();
|
|
136
|
-
NumberGenerator.generator.engine = engine;
|
|
137
|
-
for (let i = 0; i < numberOfDice; i++) {
|
|
138
|
-
try {
|
|
139
|
-
roller2.roll(diceToRoll);
|
|
140
|
-
} catch (error) {
|
|
141
|
-
throw new DiceTypeError(diceToRoll, "roll", error);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return {
|
|
145
|
-
dice: diceToRoll,
|
|
146
|
-
result: roller2.output,
|
|
147
|
-
comment: comments,
|
|
148
|
-
compare: compare ? compare : void 0,
|
|
149
|
-
modifier: modificator,
|
|
150
|
-
total: roller2.total
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
const roller = new DiceRoller();
|
|
154
|
-
NumberGenerator.generator.engine = engine;
|
|
155
|
-
const diceWithoutComment = dice.replace(COMMENT_REGEX, "").trimEnd();
|
|
156
|
-
let diceRoll;
|
|
157
|
-
try {
|
|
158
|
-
diceRoll = roller.roll(diceWithoutComment);
|
|
159
|
-
} catch (error) {
|
|
160
|
-
throw new DiceTypeError(diceWithoutComment, "roll", error);
|
|
161
|
-
}
|
|
162
|
-
if (compare && diceRoll) {
|
|
163
|
-
const currentRoll = Array.isArray(diceRoll) ? diceRoll[0] : diceRoll;
|
|
164
|
-
const maxDiceValue = currentRoll.maxTotal;
|
|
165
|
-
const minDiceValue = currentRoll.minTotal;
|
|
166
|
-
const trivial = isTrivialComparison(maxDiceValue, minDiceValue, compare);
|
|
167
|
-
compare.trivial = trivial ? true : void 0;
|
|
168
|
-
}
|
|
169
|
-
const commentMatch = dice.match(COMMENT_REGEX);
|
|
170
|
-
const comment = commentMatch ? commentMatch[2] : void 0;
|
|
171
|
-
let rerollCount = 0;
|
|
172
|
-
let res;
|
|
173
|
-
if (pity && compare) {
|
|
174
|
-
const currentRoll = Array.isArray(diceRoll) ? diceRoll[0] : diceRoll;
|
|
175
|
-
const maxPossible = currentRoll ? currentRoll.maxTotal : null;
|
|
176
|
-
const isComparisonPossible = maxPossible === null || canComparisonSucceed(maxPossible, compare);
|
|
177
|
-
if (isComparisonPossible) {
|
|
178
|
-
let isFail = evaluate(`${roller.total}${compare.sign}${compare.value}`);
|
|
179
|
-
if (!isFail) {
|
|
180
|
-
const maxReroll = 100;
|
|
181
|
-
while (!isFail && rerollCount < maxReroll) {
|
|
182
|
-
try {
|
|
183
|
-
res = roll(diceWithoutComment, engine, false);
|
|
184
|
-
} catch (error) {
|
|
185
|
-
throw new DiceTypeError(diceWithoutComment, "roll", error);
|
|
186
|
-
}
|
|
187
|
-
rerollCount++;
|
|
188
|
-
if (res && res.total !== void 0)
|
|
189
|
-
isFail = evaluate(`${res.total}${compare.sign}${compare.value}`);
|
|
190
|
-
}
|
|
191
|
-
if (res) {
|
|
192
|
-
return {
|
|
193
|
-
...res,
|
|
194
|
-
dice,
|
|
195
|
-
comment,
|
|
196
|
-
compare,
|
|
197
|
-
modifier: modificator,
|
|
198
|
-
pityLogs: rerollCount
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
} else ;
|
|
203
|
-
}
|
|
204
|
-
return {
|
|
205
|
-
dice,
|
|
206
|
-
result: roller.output,
|
|
207
|
-
comment,
|
|
208
|
-
compare: compare ? compare : void 0,
|
|
209
|
-
modifier: modificator,
|
|
210
|
-
total: roller.total,
|
|
211
|
-
pityLogs: rerollCount > 0 ? rerollCount : void 0
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
418
|
function canComparisonSucceed(maxRollValue, compare, minRollValue) {
|
|
215
419
|
switch (compare.sign) {
|
|
216
420
|
case ">":
|
|
@@ -232,81 +436,22 @@ function canComparisonSucceed(maxRollValue, compare, minRollValue) {
|
|
|
232
436
|
return true;
|
|
233
437
|
}
|
|
234
438
|
}
|
|
439
|
+
|
|
440
|
+
// src/dice/exploding.ts
|
|
441
|
+
import { evaluate as evaluate4 } from "mathjs";
|
|
442
|
+
|
|
443
|
+
// src/dice/signs.ts
|
|
444
|
+
import { NumberGenerator as NumberGenerator4 } from "@dice-roller/rpg-dice-roller";
|
|
445
|
+
import { evaluate as evaluate3 } from "mathjs";
|
|
446
|
+
|
|
447
|
+
// src/dice/replace.ts
|
|
448
|
+
function replaceUnwantedText(dice) {
|
|
449
|
+
return dice.replaceAll(/[{}]/g, "").replaceAll(/s[ad]/gi, "");
|
|
450
|
+
}
|
|
235
451
|
function fixParenthesis(dice) {
|
|
236
452
|
const parenthesisRegex = /d\((\d+)\)/g;
|
|
237
453
|
return dice.replaceAll(parenthesisRegex, (_match, p1) => `d${p1}`);
|
|
238
454
|
}
|
|
239
|
-
function calculator(sign, value, total) {
|
|
240
|
-
if (sign === "^") sign = "**";
|
|
241
|
-
return evaluate(`${total} ${sign} ${value}`);
|
|
242
|
-
}
|
|
243
|
-
function inverseSign(sign) {
|
|
244
|
-
switch (sign) {
|
|
245
|
-
case "<":
|
|
246
|
-
return ">";
|
|
247
|
-
case ">":
|
|
248
|
-
return "<";
|
|
249
|
-
case "<=":
|
|
250
|
-
return ">=";
|
|
251
|
-
case ">=":
|
|
252
|
-
return "<=";
|
|
253
|
-
case "=":
|
|
254
|
-
return "!=";
|
|
255
|
-
case "==":
|
|
256
|
-
return "!=";
|
|
257
|
-
case "!=":
|
|
258
|
-
return "==";
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
function replaceInFormula(element, diceResult, compareResult, res, engine = NumberGenerator.engines.nodeCrypto, pity) {
|
|
262
|
-
const { formule, diceAll } = replaceText(
|
|
263
|
-
element,
|
|
264
|
-
diceResult.total ?? 0,
|
|
265
|
-
diceResult.dice
|
|
266
|
-
);
|
|
267
|
-
const validSign = res ? "\u2713" : "\u2715";
|
|
268
|
-
const invertedSign = res ? compareResult.compare.sign : inverseSign(compareResult.compare.sign);
|
|
269
|
-
let evaluateRoll;
|
|
270
|
-
try {
|
|
271
|
-
evaluateRoll = evaluate(compareResult.dice);
|
|
272
|
-
return `${validSign} ${diceAll}: ${formule} = ${evaluateRoll}${invertedSign}${compareResult.compare?.value}`;
|
|
273
|
-
} catch (error) {
|
|
274
|
-
const evaluateRoll2 = roll(compareResult.dice, engine, pity);
|
|
275
|
-
if (evaluateRoll2)
|
|
276
|
-
return `${validSign} ${diceAll}: ${evaluateRoll2.result.split(":").splice(1).join(":")}`;
|
|
277
|
-
return `${validSign} ${diceAll}: ${formule} = ${evaluateRoll2}${invertedSign}${compareResult.compare?.value}`;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
function compareSignFormule(toRoll, compareRegex, element, diceResult, engine = NumberGenerator.engines.nodeCrypto, pity) {
|
|
281
|
-
let results = "";
|
|
282
|
-
let trivial = false;
|
|
283
|
-
const compareResult = getCompare(toRoll, compareRegex, engine);
|
|
284
|
-
const toCompare = `${compareResult.dice}${compareResult.compare?.sign}${compareResult.compare?.value}`;
|
|
285
|
-
let res;
|
|
286
|
-
try {
|
|
287
|
-
res = evaluate(toCompare);
|
|
288
|
-
} catch (error) {
|
|
289
|
-
res = roll(toCompare, engine, pity);
|
|
290
|
-
}
|
|
291
|
-
if (typeof res === "boolean") {
|
|
292
|
-
trivial = true;
|
|
293
|
-
if (compareResult.compare) compareResult.compare.trivial = true;
|
|
294
|
-
results = replaceInFormula(element, diceResult, compareResult, res, engine, pity);
|
|
295
|
-
} else if (res instanceof Object) {
|
|
296
|
-
const diceResult2 = res;
|
|
297
|
-
if (diceResult2.compare) {
|
|
298
|
-
const toEvaluate = evaluate(
|
|
299
|
-
`${diceResult2.total}${diceResult2.compare.sign}${diceResult2.compare.value}`
|
|
300
|
-
);
|
|
301
|
-
const sign = toEvaluate ? "\u2713" : "\u2715";
|
|
302
|
-
const invertedSign = toEvaluate ? diceResult2.compare.sign : inverseSign(diceResult2.compare.sign);
|
|
303
|
-
const dice = replaceText(element, 0, diceResult2.dice).diceAll;
|
|
304
|
-
results = `${sign} ${dice}: ${diceResult2.result.split(":").splice(1).join(":").trim()}${invertedSign}${diceResult2.compare.value}`;
|
|
305
|
-
if (diceResult2.compare.trivial) trivial = true;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return { dice: compareResult.dice, results, compare: compareResult.compare, trivial };
|
|
309
|
-
}
|
|
310
455
|
function replaceText(element, total, dice) {
|
|
311
456
|
return {
|
|
312
457
|
formule: element.replace(SYMBOL_DICE, `[${total}]`).replace(/%.*%/g, "").trim(),
|
|
@@ -327,390 +472,726 @@ function formatComment(dice) {
|
|
|
327
472
|
else if (optional) finalComment = `__${optional}__ \u2014 `;
|
|
328
473
|
return finalComment;
|
|
329
474
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
hidden = true;
|
|
350
|
-
} else if (toHide) {
|
|
351
|
-
diceMain = "1d1";
|
|
352
|
-
hidden = true;
|
|
353
|
-
} else {
|
|
354
|
-
diceMain = diceMainWithoutComments;
|
|
355
|
-
}
|
|
356
|
-
let diceResult = roll(diceMain, engine, pity);
|
|
357
|
-
if (!diceResult || !diceResult.total) {
|
|
358
|
-
if (hidden) {
|
|
359
|
-
diceResult = roll(fixParenthesis(split[0]), engine, pity);
|
|
360
|
-
hidden = false;
|
|
361
|
-
} else return void 0;
|
|
475
|
+
|
|
476
|
+
// src/dice/signs.ts
|
|
477
|
+
function matchComparison(sign, val, value) {
|
|
478
|
+
switch (sign) {
|
|
479
|
+
case ">":
|
|
480
|
+
return val > value;
|
|
481
|
+
case ">=":
|
|
482
|
+
return val >= value;
|
|
483
|
+
case "<":
|
|
484
|
+
return val < value;
|
|
485
|
+
case "<=":
|
|
486
|
+
return val <= value;
|
|
487
|
+
case "=":
|
|
488
|
+
case "==":
|
|
489
|
+
return val === value;
|
|
490
|
+
case "!=":
|
|
491
|
+
return val !== value;
|
|
492
|
+
default:
|
|
493
|
+
return false;
|
|
362
494
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
495
|
+
}
|
|
496
|
+
function inverseSign(sign) {
|
|
497
|
+
switch (sign) {
|
|
498
|
+
case "<":
|
|
499
|
+
return ">";
|
|
500
|
+
case ">":
|
|
501
|
+
return "<";
|
|
502
|
+
case "<=":
|
|
503
|
+
return ">=";
|
|
504
|
+
case ">=":
|
|
505
|
+
return "<=";
|
|
506
|
+
case "=":
|
|
507
|
+
return "!=";
|
|
508
|
+
case "==":
|
|
509
|
+
return "!=";
|
|
510
|
+
case "!=":
|
|
511
|
+
return "==";
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function compareSignFormule(toRoll, compareRegex, element, diceResult, engine = NumberGenerator4.engines.nodeCrypto, pity, rollBounds) {
|
|
515
|
+
let results = "";
|
|
516
|
+
let trivial = false;
|
|
517
|
+
const compareResult = getCompare(toRoll, compareRegex, engine);
|
|
518
|
+
const toCompare = `${compareResult.dice}${compareResult.compare?.sign}${compareResult.compare?.value}`;
|
|
519
|
+
let res;
|
|
520
|
+
try {
|
|
521
|
+
res = evaluate3(toCompare);
|
|
522
|
+
} catch (error) {
|
|
523
|
+
res = roll(toCompare, engine, pity);
|
|
524
|
+
}
|
|
525
|
+
if (typeof res === "boolean") {
|
|
526
|
+
const detectedTrivial = rollBounds && compareResult.compare ? isTrivialComparison(rollBounds.max, rollBounds.min, compareResult.compare) : false;
|
|
527
|
+
if (detectedTrivial && compareResult.compare) compareResult.compare.trivial = true;
|
|
528
|
+
if (detectedTrivial) trivial = true;
|
|
529
|
+
results = replaceInFormula(element, diceResult, compareResult, res, engine, pity);
|
|
530
|
+
} else if (res instanceof Object) {
|
|
531
|
+
const diceResult2 = res;
|
|
532
|
+
if (diceResult2.compare) {
|
|
533
|
+
const toEvaluate = evaluate3(
|
|
534
|
+
`${diceResult2.total}${diceResult2.compare.sign}${diceResult2.compare.value}`
|
|
392
535
|
);
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const evaluated = roll(toRoll, engine, pity);
|
|
399
|
-
if (evaluated) {
|
|
400
|
-
results.push(
|
|
401
|
-
`\u25C8 ${comment}${diceAll}: ${evaluated.result.split(":").slice(1).join(":")}`
|
|
402
|
-
);
|
|
403
|
-
if (!aggregatedCompare && evaluated.compare)
|
|
404
|
-
aggregatedCompare = evaluated.compare;
|
|
405
|
-
if (evaluated.compare?.trivial) hasTrivialComparison = true;
|
|
406
|
-
} else results.push(`\u25C8 ${comment}${diceAll}: ${formule} = ${evaluated}`);
|
|
407
|
-
total += evaluated?.total ?? 0;
|
|
408
|
-
}
|
|
536
|
+
const sign = toEvaluate ? "\u2713" : "\u2715";
|
|
537
|
+
const invertedSign = toEvaluate ? diceResult2.compare.sign : inverseSign(diceResult2.compare.sign);
|
|
538
|
+
const dice = replaceText(element, 0, diceResult2.dice).diceAll;
|
|
539
|
+
results = `${sign} ${dice}: ${diceResult2.result.split(":").splice(1).join(":").trim()}${invertedSign}${diceResult2.compare.value}`;
|
|
540
|
+
if (diceResult2.compare.trivial) trivial = true;
|
|
409
541
|
}
|
|
410
542
|
}
|
|
411
|
-
|
|
412
|
-
|
|
543
|
+
return { dice: compareResult.dice, results, compare: compareResult.compare, trivial };
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/dice/exploding.ts
|
|
547
|
+
var EXPLODING_SUCCESS_REGEX = /(!(?:>>=|<<=|!==|!!==|>>|<<|==|!=))(-?\d+(?:\.\d+)?)/;
|
|
548
|
+
function normalizeExplodingSuccess(dice) {
|
|
549
|
+
const match = dice.match(EXPLODING_SUCCESS_REGEX);
|
|
550
|
+
if (!match) return void 0;
|
|
551
|
+
const [, doubledSignRaw, valueStr] = match;
|
|
552
|
+
let doubledSign;
|
|
553
|
+
if (doubledSignRaw === "!!==") doubledSign = "==";
|
|
554
|
+
else if (doubledSignRaw === "!==") doubledSign = "!==";
|
|
555
|
+
else doubledSign = doubledSignRaw.replace(/^!/, "");
|
|
556
|
+
const signMap = {
|
|
557
|
+
">>": ">",
|
|
558
|
+
"<<": "<",
|
|
559
|
+
">>=": ">=",
|
|
560
|
+
"<<=": "<=",
|
|
561
|
+
"==": "=",
|
|
562
|
+
"!==": "!=",
|
|
563
|
+
"!!==": "="
|
|
564
|
+
};
|
|
565
|
+
const normalizedSign = signMap[doubledSign];
|
|
566
|
+
if (!normalizedSign) return void 0;
|
|
567
|
+
let parsedValue = Number.parseFloat(valueStr);
|
|
568
|
+
if (Number.isNaN(parsedValue)) {
|
|
569
|
+
try {
|
|
570
|
+
parsedValue = Number.parseFloat(evaluate4(valueStr));
|
|
571
|
+
} catch (_error) {
|
|
572
|
+
parsedValue = 0;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
const normalizedSegment = "!";
|
|
576
|
+
const replacedDice = dice.replace(match[0], normalizedSegment);
|
|
413
577
|
return {
|
|
414
|
-
dice:
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
578
|
+
dice: replacedDice,
|
|
579
|
+
originalDice: dice,
|
|
580
|
+
sign: normalizedSign,
|
|
581
|
+
value: parsedValue,
|
|
582
|
+
normalizedSegment,
|
|
583
|
+
originalSegment: match[0]
|
|
420
584
|
};
|
|
421
585
|
}
|
|
586
|
+
function countExplodingSuccesses(diceRoll, sign, value) {
|
|
587
|
+
const rollsArray = Array.isArray(diceRoll) ? diceRoll : [diceRoll];
|
|
588
|
+
const flatValues = [];
|
|
589
|
+
for (const dr of rollsArray) {
|
|
590
|
+
const groups = dr.rolls ?? [];
|
|
591
|
+
for (const group of groups) {
|
|
592
|
+
const innerRolls = group.rolls ?? [];
|
|
593
|
+
for (const roll2 of innerRolls) {
|
|
594
|
+
if (typeof roll2.value === "number") flatValues.push(roll2.value);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return flatValues.reduce(
|
|
599
|
+
(acc, current) => acc + (matchComparison(sign, current, value) ? 1 : 0),
|
|
600
|
+
0
|
|
601
|
+
);
|
|
602
|
+
}
|
|
422
603
|
|
|
423
|
-
// src/
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
604
|
+
// src/dice/extract.ts
|
|
605
|
+
import { DiceRoller, NumberGenerator as NumberGenerator5 } from "@dice-roller/rpg-dice-roller";
|
|
606
|
+
|
|
607
|
+
// src/dice/calculator.ts
|
|
608
|
+
import { evaluate as evaluate5 } from "mathjs";
|
|
609
|
+
function calculator(sign, value, total) {
|
|
610
|
+
if (sign === "^") sign = "**";
|
|
611
|
+
return evaluate5(`${total} ${sign} ${value}`);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// src/dice/extract.ts
|
|
615
|
+
function getModifier(dice) {
|
|
616
|
+
const modifier = dice.matchAll(/(\+|-|%|\/|\^|\*|\*{2})(\d+)/gi);
|
|
617
|
+
let modificator;
|
|
618
|
+
for (const mod of modifier) {
|
|
619
|
+
if (modificator) {
|
|
620
|
+
const sign = modificator.sign;
|
|
621
|
+
let value = modificator.value;
|
|
622
|
+
if (sign) value = calculator(sign, value, Number.parseInt(mod[2], 10));
|
|
623
|
+
modificator = {
|
|
624
|
+
sign: mod[1],
|
|
625
|
+
value
|
|
626
|
+
};
|
|
627
|
+
} else {
|
|
628
|
+
modificator = {
|
|
629
|
+
sign: mod[1],
|
|
630
|
+
value: Number.parseInt(mod[2], 10)
|
|
631
|
+
};
|
|
632
|
+
}
|
|
434
633
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
this.cause = cause;
|
|
445
|
-
this.method = method;
|
|
634
|
+
return modificator;
|
|
635
|
+
}
|
|
636
|
+
function extractValuesFromOutput(output) {
|
|
637
|
+
const values = [];
|
|
638
|
+
const regex = /\[([^\]]+)\]/g;
|
|
639
|
+
let match;
|
|
640
|
+
while ((match = regex.exec(output)) !== null) {
|
|
641
|
+
const segmentValues = match[1].split(",").map((v) => Number.parseInt(v.replace(/[!*]/g, "").trim(), 10)).filter((v) => !Number.isNaN(v));
|
|
642
|
+
values.push(...segmentValues);
|
|
446
643
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
644
|
+
return values;
|
|
645
|
+
}
|
|
646
|
+
function getRollBounds(dice, engine = NumberGenerator5.engines.nodeCrypto) {
|
|
647
|
+
try {
|
|
648
|
+
const roller = new DiceRoller();
|
|
649
|
+
NumberGenerator5.generator.engine = engine;
|
|
650
|
+
const rollResult = roller.roll(dice);
|
|
651
|
+
const instance = Array.isArray(rollResult) ? rollResult[0] : rollResult;
|
|
652
|
+
const { minTotal, maxTotal } = instance;
|
|
653
|
+
return { min: minTotal, max: maxTotal };
|
|
654
|
+
} catch (error) {
|
|
457
655
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
656
|
+
return void 0;
|
|
657
|
+
}
|
|
658
|
+
function setSortOrder(toRoll, sort) {
|
|
659
|
+
const sortRegex = /(sa|sd|s)/i;
|
|
660
|
+
if (sort && !toRoll.match(sortRegex)) {
|
|
661
|
+
const modifierComparisonRegex = /([+\-*/%^]\d+|([><=!]+\d+f)|([><=]|!=)+\d+)$/;
|
|
662
|
+
const match = toRoll.match(modifierComparisonRegex);
|
|
663
|
+
if (match) {
|
|
664
|
+
const index = match.index;
|
|
665
|
+
toRoll = `${toRoll.slice(0, index)}${sort}${toRoll.slice(index)}`;
|
|
666
|
+
} else {
|
|
667
|
+
toRoll += sort;
|
|
668
|
+
}
|
|
464
669
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
670
|
+
return toRoll;
|
|
671
|
+
}
|
|
672
|
+
function prepareDice(diceInput) {
|
|
673
|
+
let dice = standardizeDice(replaceFormulaInDice(diceInput)).replace(/^\+/, "").replaceAll("=>", ">=").replaceAll("=<", "<=").trimStart();
|
|
674
|
+
dice = dice.replaceAll(DETECT_CRITICAL, "").trimEnd();
|
|
675
|
+
const explodingSuccess = normalizeExplodingSuccess(dice);
|
|
676
|
+
if (explodingSuccess) dice = explodingSuccess.dice;
|
|
677
|
+
let diceDisplay;
|
|
678
|
+
if (dice.includes(";")) {
|
|
679
|
+
const mainDice = dice.split(";")[0];
|
|
680
|
+
diceDisplay = explodingSuccess?.originalDice ?? mainDice;
|
|
681
|
+
} else {
|
|
682
|
+
diceDisplay = explodingSuccess?.originalDice ?? dice;
|
|
471
683
|
}
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
684
|
+
const curlyBulkMatch = dice.match(/^\{(\d+#.*)\}$/);
|
|
685
|
+
const isCurlyBulk = !!curlyBulkMatch;
|
|
686
|
+
const bulkContent = isCurlyBulk ? curlyBulkMatch[1] : "";
|
|
687
|
+
const isSharedRoll = dice.includes(";");
|
|
688
|
+
let isSharedCurly = false;
|
|
689
|
+
if (isSharedRoll && dice.match(/^\{.*;\s*.*\}$/)) {
|
|
690
|
+
dice = dice.slice(1, -1);
|
|
691
|
+
isSharedCurly = true;
|
|
692
|
+
diceDisplay = diceDisplay.slice(1);
|
|
478
693
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
694
|
+
let isSimpleCurly = false;
|
|
695
|
+
if (!isCurlyBulk && !isSharedRoll && dice.match(/^\{.*\}$/)) {
|
|
696
|
+
const innerContent = dice.slice(1, -1);
|
|
697
|
+
const hasModifiers = innerContent.match(/[+\-*/%^]/);
|
|
698
|
+
const hasComparison = innerContent.match(/(([><=!]+\d+f)|([><=]|!=)+\d+)/);
|
|
699
|
+
if (!(hasComparison && !hasModifiers)) {
|
|
700
|
+
dice = innerContent;
|
|
701
|
+
isSimpleCurly = true;
|
|
702
|
+
}
|
|
485
703
|
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
704
|
+
return {
|
|
705
|
+
dice,
|
|
706
|
+
diceDisplay,
|
|
707
|
+
explodingSuccess,
|
|
708
|
+
isSharedRoll,
|
|
709
|
+
isSharedCurly,
|
|
710
|
+
isCurlyBulk,
|
|
711
|
+
bulkContent,
|
|
712
|
+
isSimpleCurly
|
|
713
|
+
};
|
|
714
|
+
}
|
|
495
715
|
|
|
496
|
-
// src/
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
716
|
+
// src/dice/bulk.ts
|
|
717
|
+
function handleBulkRolls(dice, isCurlyBulk, bulkContent, compare, explodingSuccess, diceDisplay, engine, sort) {
|
|
718
|
+
const bulkProcessContent = isCurlyBulk ? bulkContent : dice;
|
|
719
|
+
const diceArray = bulkProcessContent.split("#");
|
|
720
|
+
const numberOfDice = Number.parseInt(diceArray[0], 10);
|
|
721
|
+
let diceToRoll = diceArray[1].replace(COMMENT_REGEX, "");
|
|
722
|
+
const commentsMatch = diceArray[1].match(COMMENT_REGEX);
|
|
723
|
+
const comments = commentsMatch ? commentsMatch[2] : void 0;
|
|
724
|
+
let curlyCompare;
|
|
725
|
+
if (isCurlyBulk) {
|
|
726
|
+
const curlyCompareRegex = diceToRoll.match(SIGN_REGEX_SPACE);
|
|
727
|
+
if (curlyCompareRegex) {
|
|
728
|
+
const compareSign = curlyCompareRegex[0].match(SIGN_REGEX)?.[0];
|
|
729
|
+
const compareValue = curlyCompareRegex[2];
|
|
730
|
+
if (compareSign && compareValue) {
|
|
731
|
+
curlyCompare = {
|
|
732
|
+
sign: compareSign,
|
|
733
|
+
value: Number.parseInt(compareValue, 10)
|
|
734
|
+
};
|
|
735
|
+
diceToRoll = diceToRoll.replace(SIGN_REGEX_SPACE, "");
|
|
736
|
+
}
|
|
737
|
+
}
|
|
512
738
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
})
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
while ((match = partsRegex.exec(dice)) !== null) {
|
|
574
|
-
const insideBrackets = match[1];
|
|
575
|
-
const outsideText = match[2];
|
|
576
|
-
if (insideBrackets) {
|
|
577
|
-
result += insideBrackets;
|
|
578
|
-
continue;
|
|
739
|
+
diceToRoll = setSortOrder(diceToRoll, sort);
|
|
740
|
+
const activeCompare = compare || curlyCompare || (explodingSuccess ? { sign: explodingSuccess.sign, value: explodingSuccess.value } : void 0);
|
|
741
|
+
if (activeCompare) {
|
|
742
|
+
return handleBulkRollsWithComparison(
|
|
743
|
+
numberOfDice,
|
|
744
|
+
diceToRoll,
|
|
745
|
+
comments,
|
|
746
|
+
activeCompare,
|
|
747
|
+
explodingSuccess,
|
|
748
|
+
diceDisplay,
|
|
749
|
+
isCurlyBulk,
|
|
750
|
+
curlyCompare,
|
|
751
|
+
compare,
|
|
752
|
+
engine
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
const roller = new DiceRoller2();
|
|
756
|
+
NumberGenerator6.generator.engine = engine;
|
|
757
|
+
for (let i = 0; i < numberOfDice; i++) {
|
|
758
|
+
try {
|
|
759
|
+
roller.roll(diceToRoll);
|
|
760
|
+
} catch (error) {
|
|
761
|
+
throw new DiceTypeError(diceToRoll, "roll", error);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
const finalDice = isCurlyBulk ? `{${diceToRoll}}` : diceToRoll;
|
|
765
|
+
const modificator = getModifier(dice);
|
|
766
|
+
return {
|
|
767
|
+
dice: finalDice,
|
|
768
|
+
result: replaceUnwantedText(roller.output),
|
|
769
|
+
comment: comments,
|
|
770
|
+
compare: compare ? compare : void 0,
|
|
771
|
+
modifier: modificator,
|
|
772
|
+
total: roller.total
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
function handleBulkRollsWithComparison(numberOfDice, diceToRoll, comments, activeCompare, explodingSuccess, diceDisplay, isCurlyBulk, curlyCompare, compare, engine) {
|
|
776
|
+
const results = [];
|
|
777
|
+
let successCount = 0;
|
|
778
|
+
const roller = new DiceRoller2();
|
|
779
|
+
NumberGenerator6.generator.engine = engine;
|
|
780
|
+
let trivialComparisonDetected = false;
|
|
781
|
+
const formatOutput = (output, addStar) => {
|
|
782
|
+
const formatted = addStar && isCurlyBulk ? output.replace(
|
|
783
|
+
/\[([^\]]+)\]/,
|
|
784
|
+
(_m, content) => `[${content.split(",").map((d) => `${d.trim()}*`).join(", ")}]`
|
|
785
|
+
) : output;
|
|
786
|
+
return curlyCompare ? formatted.replace(/^([^:]+):/, `$1${curlyCompare.sign}${curlyCompare.value}:`) : formatted;
|
|
787
|
+
};
|
|
788
|
+
for (let i = 0; i < numberOfDice; i++) {
|
|
789
|
+
try {
|
|
790
|
+
const individualRoll = roller.roll(diceToRoll);
|
|
791
|
+
const rollInstance = Array.isArray(individualRoll) ? individualRoll[0] : individualRoll;
|
|
792
|
+
if (!trivialComparisonDetected && activeCompare) {
|
|
793
|
+
const { maxTotal, minTotal } = rollInstance;
|
|
794
|
+
trivialComparisonDetected = isTrivialComparison(
|
|
795
|
+
maxTotal,
|
|
796
|
+
minTotal,
|
|
797
|
+
activeCompare
|
|
798
|
+
);
|
|
579
799
|
}
|
|
580
|
-
|
|
581
|
-
|
|
800
|
+
const rollOutput = rollInstance.output;
|
|
801
|
+
if (explodingSuccess) {
|
|
802
|
+
const successesForRoll = countExplodingSuccesses(
|
|
803
|
+
rollInstance,
|
|
804
|
+
explodingSuccess.sign,
|
|
805
|
+
explodingSuccess.value
|
|
806
|
+
);
|
|
807
|
+
successCount += successesForRoll;
|
|
808
|
+
let formattedRollOutput = rollOutput.replace(explodingSuccess.normalizedSegment, explodingSuccess.originalSegment).replace(/=\s*-?\d+(?:\.\d+)?$/, `= ${successesForRoll}`);
|
|
809
|
+
formattedRollOutput = formatOutput(formattedRollOutput, false);
|
|
810
|
+
results.push(formattedRollOutput);
|
|
811
|
+
} else {
|
|
812
|
+
const rollTotal = rollInstance.total;
|
|
813
|
+
const isSuccess = evaluate6(
|
|
814
|
+
`${rollTotal}${activeCompare.sign}${activeCompare.value}`
|
|
815
|
+
);
|
|
816
|
+
if (isSuccess) successCount++;
|
|
817
|
+
results.push(formatOutput(rollOutput, isSuccess));
|
|
582
818
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
819
|
+
} catch (error) {
|
|
820
|
+
throw new DiceTypeError(diceToRoll, "roll", error);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
if (explodingSuccess) {
|
|
824
|
+
const signSource = explodingSuccess?.originalDice ?? diceDisplay;
|
|
825
|
+
const explodingMatch = signSource.match(EXPLODING_SUCCESS_REGEX);
|
|
826
|
+
if (explodingMatch) {
|
|
827
|
+
const [, doubledSignRaw, valueStr] = explodingMatch;
|
|
828
|
+
let doubledSign;
|
|
829
|
+
if (doubledSignRaw === "!!==") doubledSign = "==";
|
|
830
|
+
else if (doubledSignRaw === "!==") doubledSign = "!==";
|
|
831
|
+
else doubledSign = doubledSignRaw.replace(/^!/, "");
|
|
832
|
+
const signMap = {
|
|
833
|
+
">>": ">",
|
|
834
|
+
"<<": "<",
|
|
835
|
+
">=": ">=",
|
|
836
|
+
"<=": "<=",
|
|
837
|
+
"==": "=",
|
|
838
|
+
"!==": "!=",
|
|
839
|
+
"!!==": "="
|
|
840
|
+
};
|
|
841
|
+
const mappedSign = signMap[doubledSign];
|
|
842
|
+
const mappedValue = Number.parseFloat(valueStr);
|
|
843
|
+
if (mappedSign && !Number.isNaN(mappedValue)) {
|
|
844
|
+
const resultsString = replaceUnwantedText(results.join("; "));
|
|
845
|
+
const flatValues = resultsString.split(";").flatMap((segment) => extractValuesFromOutput(segment));
|
|
846
|
+
if (mappedSign === "!=") {
|
|
847
|
+
const equalsCount = flatValues.filter((val) => val === mappedValue).length;
|
|
848
|
+
successCount = flatValues.length - equalsCount;
|
|
608
849
|
} else {
|
|
609
|
-
|
|
850
|
+
successCount = flatValues.filter(
|
|
851
|
+
(val) => matchComparison(mappedSign, val, mappedValue)
|
|
852
|
+
).length;
|
|
610
853
|
}
|
|
611
|
-
lastIndex = tokenRegex.lastIndex;
|
|
612
854
|
}
|
|
613
|
-
result += outsideText.slice(lastIndex);
|
|
614
855
|
}
|
|
615
|
-
dice = result;
|
|
616
856
|
}
|
|
617
|
-
if (
|
|
618
|
-
|
|
857
|
+
if (compare && trivialComparisonDetected) compare.trivial = true;
|
|
858
|
+
const finalDice = isCurlyBulk ? `{${diceToRoll}${curlyCompare?.sign}${curlyCompare?.value}}` : diceToRoll;
|
|
859
|
+
const resultOutput = replaceUnwantedText(results.join("; "));
|
|
860
|
+
const finalTotal = explodingSuccess ? resultOutput.split(";").flatMap((segment) => extractValuesFromOutput(segment)).filter(
|
|
861
|
+
(val) => matchComparison(explodingSuccess.sign, val, explodingSuccess.value)
|
|
862
|
+
).length : successCount;
|
|
863
|
+
const modificator = getModifier(diceDisplay);
|
|
864
|
+
return {
|
|
865
|
+
dice: explodingSuccess ? diceDisplay : finalDice,
|
|
866
|
+
result: resultOutput,
|
|
867
|
+
comment: comments,
|
|
868
|
+
compare: isCurlyBulk ? void 0 : compare,
|
|
869
|
+
modifier: modificator,
|
|
870
|
+
total: finalTotal,
|
|
871
|
+
trivial: trivialComparisonDetected ? true : void 0
|
|
872
|
+
};
|
|
619
873
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
874
|
+
|
|
875
|
+
// src/dice/pity.ts
|
|
876
|
+
import { evaluate as evaluate7 } from "mathjs";
|
|
877
|
+
function handlePitySystem(dice, compare, diceRoll, roller, engine) {
|
|
878
|
+
const currentRoll = Array.isArray(diceRoll) ? diceRoll[0] : diceRoll;
|
|
879
|
+
const maxPossible = currentRoll ? currentRoll.maxTotal : null;
|
|
880
|
+
const isComparisonPossible = maxPossible === null || canComparisonSucceed(maxPossible, compare);
|
|
881
|
+
if (!isComparisonPossible) {
|
|
882
|
+
return { rerollCount: 0 };
|
|
883
|
+
}
|
|
884
|
+
let isFail = evaluate7(`${roller.total}${compare.sign}${compare.value}`);
|
|
885
|
+
if (isFail) {
|
|
886
|
+
return { rerollCount: 0 };
|
|
887
|
+
}
|
|
888
|
+
const maxReroll = 100;
|
|
889
|
+
let rerollCount = 0;
|
|
890
|
+
let res;
|
|
891
|
+
while (!isFail && rerollCount < maxReroll) {
|
|
892
|
+
try {
|
|
893
|
+
res = roll(dice, engine, false);
|
|
894
|
+
} catch (error) {
|
|
895
|
+
throw new DiceTypeError(dice, "roll", error);
|
|
896
|
+
}
|
|
897
|
+
rerollCount++;
|
|
898
|
+
if (res && res.total !== void 0) {
|
|
899
|
+
isFail = evaluate7(`${res.total}${compare.sign}${compare.value}`);
|
|
633
900
|
}
|
|
634
901
|
}
|
|
635
|
-
return
|
|
636
|
-
}
|
|
637
|
-
function cleanedDice(dice) {
|
|
638
|
-
return dice.replaceAll("+-", "-").replaceAll("--", "+").replaceAll("++", "+").replaceAll("=>", ">=").replaceAll("=<", "<=").trimEnd();
|
|
639
|
-
}
|
|
640
|
-
function isNumber(value) {
|
|
641
|
-
return value !== void 0 && (typeof value === "number" || !Number.isNaN(Number(value)) && typeof value === "string" && value.trim().length > 0);
|
|
642
|
-
}
|
|
643
|
-
function replaceExpByRandom(dice, engine = NumberGenerator2.engines.nodeCrypto) {
|
|
644
|
-
const diceRegex = /\{exp( ?\|\| ?(?<default>\d+))?}/gi;
|
|
645
|
-
return dice.replace(diceRegex, (_match, _p1, _p2, _offset, _string, groups) => {
|
|
646
|
-
const defaultValue = groups?.default;
|
|
647
|
-
return defaultValue ?? randomInt(1, 999, engine).toString();
|
|
648
|
-
});
|
|
902
|
+
return { rerollCount, result: res };
|
|
649
903
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
if (
|
|
656
|
-
if (
|
|
657
|
-
|
|
904
|
+
|
|
905
|
+
// src/roll.ts
|
|
906
|
+
function roll(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, sort) {
|
|
907
|
+
if (sort === "none" /* None */) sort = void 0;
|
|
908
|
+
const prepared = prepareDice(dice);
|
|
909
|
+
if (!prepared.dice.includes("d")) return void 0;
|
|
910
|
+
if (prepared.isSharedRoll) {
|
|
911
|
+
return sharedRolls(
|
|
912
|
+
prepared.dice,
|
|
913
|
+
engine,
|
|
914
|
+
pity,
|
|
915
|
+
prepared.explodingSuccess,
|
|
916
|
+
prepared.diceDisplay,
|
|
917
|
+
prepared.isSharedCurly
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
let processedDice = fixParenthesis(prepared.dice);
|
|
921
|
+
const modificator = getModifier(processedDice);
|
|
922
|
+
const compareRegex = processedDice.match(SIGN_REGEX_SPACE);
|
|
923
|
+
let compare;
|
|
924
|
+
if (compareRegex && !prepared.isCurlyBulk) {
|
|
925
|
+
const compareResult = getCompare(processedDice, compareRegex, engine, pity);
|
|
926
|
+
processedDice = compareResult.dice;
|
|
927
|
+
compare = compareResult.compare;
|
|
928
|
+
}
|
|
929
|
+
let finalDiceDisplay = prepared.diceDisplay;
|
|
930
|
+
if (prepared.isSimpleCurly && !prepared.diceDisplay.startsWith("{")) {
|
|
931
|
+
finalDiceDisplay = `{${prepared.diceDisplay}}`;
|
|
932
|
+
}
|
|
933
|
+
const bulkProcessContent = prepared.isCurlyBulk ? prepared.bulkContent : processedDice;
|
|
934
|
+
if (bulkProcessContent.match(/\d+?#(.*)/)) {
|
|
935
|
+
return handleBulkRolls(
|
|
936
|
+
processedDice,
|
|
937
|
+
prepared.isCurlyBulk,
|
|
938
|
+
prepared.bulkContent,
|
|
939
|
+
compare,
|
|
940
|
+
prepared.explodingSuccess,
|
|
941
|
+
prepared.diceDisplay,
|
|
942
|
+
engine,
|
|
943
|
+
sort
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
const roller = new DiceRoller3();
|
|
947
|
+
NumberGenerator7.generator.engine = engine;
|
|
948
|
+
let diceWithoutComment = processedDice.replace(COMMENT_REGEX, "").trimEnd();
|
|
949
|
+
diceWithoutComment = setSortOrder(diceWithoutComment, sort);
|
|
950
|
+
let diceRoll;
|
|
658
951
|
try {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
952
|
+
diceRoll = roller.roll(diceWithoutComment);
|
|
953
|
+
} catch (error) {
|
|
954
|
+
throw new DiceTypeError(diceWithoutComment, "roll", error);
|
|
955
|
+
}
|
|
956
|
+
if (compare && diceRoll) {
|
|
957
|
+
const currentRoll = Array.isArray(diceRoll) ? diceRoll[0] : diceRoll;
|
|
958
|
+
const trivial = isTrivialComparison(
|
|
959
|
+
currentRoll.maxTotal,
|
|
960
|
+
currentRoll.minTotal,
|
|
961
|
+
compare
|
|
962
|
+
);
|
|
963
|
+
compare.trivial = trivial ? true : void 0;
|
|
964
|
+
}
|
|
965
|
+
const commentMatch = processedDice.match(COMMENT_REGEX);
|
|
966
|
+
const comment = commentMatch ? commentMatch[2] : void 0;
|
|
967
|
+
let rerollCount = 0;
|
|
968
|
+
let pityResult;
|
|
969
|
+
if (pity && compare) {
|
|
970
|
+
const pityData = handlePitySystem(
|
|
971
|
+
diceWithoutComment,
|
|
972
|
+
compare,
|
|
973
|
+
diceRoll,
|
|
974
|
+
roller,
|
|
975
|
+
engine
|
|
976
|
+
);
|
|
977
|
+
rerollCount = pityData.rerollCount;
|
|
978
|
+
pityResult = pityData.result;
|
|
979
|
+
if (pityResult) {
|
|
980
|
+
return {
|
|
981
|
+
...pityResult,
|
|
982
|
+
dice: prepared.isSimpleCurly ? finalDiceDisplay : processedDice,
|
|
983
|
+
comment,
|
|
984
|
+
compare,
|
|
985
|
+
modifier: modificator,
|
|
986
|
+
pityLogs: rerollCount,
|
|
987
|
+
trivial: pityResult.trivial ?? (compare?.trivial ? true : void 0)
|
|
988
|
+
};
|
|
663
989
|
}
|
|
664
|
-
} catch {
|
|
665
990
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
991
|
+
let resultOutput = replaceUnwantedText(roller.output);
|
|
992
|
+
if (prepared.explodingSuccess) {
|
|
993
|
+
const successes = countExplodingSuccesses(
|
|
994
|
+
diceRoll,
|
|
995
|
+
prepared.explodingSuccess.sign,
|
|
996
|
+
prepared.explodingSuccess.value
|
|
997
|
+
);
|
|
998
|
+
resultOutput = resultOutput.replace(/=\s*-?\d+(?:\.\d+)?$/, `= ${successes}`).replace(
|
|
999
|
+
prepared.explodingSuccess.normalizedSegment,
|
|
1000
|
+
prepared.explodingSuccess.originalSegment
|
|
1001
|
+
);
|
|
1002
|
+
return {
|
|
1003
|
+
dice: prepared.isSimpleCurly ? finalDiceDisplay : prepared.diceDisplay,
|
|
1004
|
+
result: resultOutput,
|
|
1005
|
+
comment,
|
|
1006
|
+
compare: compare ? compare : void 0,
|
|
1007
|
+
modifier: modificator,
|
|
1008
|
+
total: successes,
|
|
1009
|
+
pityLogs: rerollCount > 0 ? rerollCount : void 0,
|
|
1010
|
+
trivial: compare?.trivial ? true : void 0
|
|
1011
|
+
};
|
|
678
1012
|
}
|
|
1013
|
+
return {
|
|
1014
|
+
dice: prepared.isSimpleCurly ? finalDiceDisplay : processedDice,
|
|
1015
|
+
result: resultOutput,
|
|
1016
|
+
comment,
|
|
1017
|
+
compare: compare ? compare : void 0,
|
|
1018
|
+
modifier: modificator,
|
|
1019
|
+
total: roller.total,
|
|
1020
|
+
pityLogs: rerollCount > 0 ? rerollCount : void 0,
|
|
1021
|
+
trivial: compare?.trivial ? true : void 0
|
|
1022
|
+
};
|
|
679
1023
|
}
|
|
680
|
-
function
|
|
681
|
-
if (
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
1024
|
+
function sharedRolls(dice, engine = NumberGenerator7.engines.nodeCrypto, pity, explodingSuccessMain, diceDisplay, isSharedCurly) {
|
|
1025
|
+
if (!explodingSuccessMain)
|
|
1026
|
+
explodingSuccessMain = normalizeExplodingSuccess(dice.split(";")[0] ?? dice);
|
|
1027
|
+
if (explodingSuccessMain) {
|
|
1028
|
+
dice = dice.replace(explodingSuccessMain.originalSegment, "!");
|
|
1029
|
+
}
|
|
1030
|
+
if (dice.match(/\d+?#(.*?)/))
|
|
1031
|
+
throw new DiceTypeError(
|
|
1032
|
+
dice,
|
|
1033
|
+
"noBulkRoll",
|
|
1034
|
+
"bulk roll are not allowed in shared rolls"
|
|
1035
|
+
);
|
|
1036
|
+
const results = [];
|
|
1037
|
+
const mainComment = /\s+#(?<comment>.*)/.exec(dice)?.groups?.comment?.trimEnd() ?? void 0;
|
|
1038
|
+
const split = dice.split(";");
|
|
1039
|
+
const displayDice = diceDisplay ?? explodingSuccessMain?.originalDice ?? split[0];
|
|
1040
|
+
let diceMain = fixParenthesis(split[0]);
|
|
1041
|
+
const commentsRegex = /\[(?<comments>.*?)\]/gi;
|
|
1042
|
+
const comments = formatComment(diceMain);
|
|
1043
|
+
const diceMainWithoutComments = diceMain.replace(commentsRegex, "").trim();
|
|
1044
|
+
const toHideRegex = /\((?<dice>[^)]+)\)/;
|
|
1045
|
+
const toHide = toHideRegex.exec(diceMainWithoutComments)?.groups;
|
|
1046
|
+
let hidden = false;
|
|
1047
|
+
if (toHide?.dice) {
|
|
1048
|
+
diceMain = toHide.dice;
|
|
1049
|
+
hidden = true;
|
|
1050
|
+
} else if (toHide) {
|
|
1051
|
+
diceMain = "1d1";
|
|
1052
|
+
hidden = true;
|
|
1053
|
+
} else {
|
|
1054
|
+
diceMain = diceMainWithoutComments;
|
|
1055
|
+
}
|
|
1056
|
+
const rollBounds = getRollBounds(diceMain, engine);
|
|
1057
|
+
let diceResult = roll(diceMain, engine, pity);
|
|
1058
|
+
if (!diceResult || !diceResult.total) {
|
|
1059
|
+
if (hidden) {
|
|
1060
|
+
diceResult = roll(fixParenthesis(split[0]), engine, pity);
|
|
1061
|
+
hidden = false;
|
|
1062
|
+
} else return void 0;
|
|
1063
|
+
}
|
|
1064
|
+
if (!diceResult || !diceResult.total) return void 0;
|
|
1065
|
+
if (explodingSuccessMain && diceResult.result) {
|
|
1066
|
+
const values = extractValuesFromOutput(diceResult.result);
|
|
1067
|
+
diceResult.total = values.filter(
|
|
1068
|
+
(v) => matchComparison(explodingSuccessMain.sign, v, explodingSuccessMain.value)
|
|
1069
|
+
).length;
|
|
1070
|
+
}
|
|
1071
|
+
let aggregatedCompare = diceResult.compare;
|
|
1072
|
+
let hasTrivialComparison = diceResult.compare?.trivial === true;
|
|
1073
|
+
results.push(`\u203B ${comments}${diceResult.result}`);
|
|
1074
|
+
let total = diceResult.total;
|
|
1075
|
+
diceResult.comment = mainComment;
|
|
1076
|
+
if (!total) {
|
|
1077
|
+
return {
|
|
1078
|
+
dice: displayDice,
|
|
1079
|
+
result: results.join(";"),
|
|
1080
|
+
comment: mainComment,
|
|
1081
|
+
compare: aggregatedCompare,
|
|
1082
|
+
modifier: diceResult.modifier,
|
|
1083
|
+
total,
|
|
1084
|
+
trivial: hasTrivialComparison ? true : void 0
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
for (let element of split.slice(1)) {
|
|
1088
|
+
const comment = formatComment(element);
|
|
1089
|
+
element = element.replaceAll(commentsRegex, "").replaceAll(OPTIONAL_COMMENT, "").trim();
|
|
1090
|
+
let toRoll = element.replace(SYMBOL_DICE, `${diceResult.total}`);
|
|
1091
|
+
const compareRegex = toRoll.match(SIGN_REGEX_SPACE);
|
|
1092
|
+
if (compareRegex) {
|
|
1093
|
+
if (isSharedCurly) {
|
|
1094
|
+
const compareResult = compareSignFormule(
|
|
1095
|
+
toRoll,
|
|
1096
|
+
compareRegex,
|
|
1097
|
+
element,
|
|
1098
|
+
diceResult,
|
|
1099
|
+
engine,
|
|
1100
|
+
pity,
|
|
1101
|
+
rollBounds
|
|
1102
|
+
);
|
|
1103
|
+
const { diceAll } = replaceText(element, diceResult.total, diceResult.dice);
|
|
1104
|
+
let successCount = 0;
|
|
1105
|
+
try {
|
|
1106
|
+
const evaluated = evaluate8(toRoll);
|
|
1107
|
+
successCount = evaluated ? 1 : 0;
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
const evaluated = roll(toRoll, engine, pity);
|
|
1110
|
+
successCount = evaluated?.total ?? 0 ? 1 : 0;
|
|
1111
|
+
}
|
|
1112
|
+
results.push(`\u203B ${comment}${diceAll}: ${successCount}`);
|
|
1113
|
+
total += successCount;
|
|
1114
|
+
if (!aggregatedCompare && compareResult.compare)
|
|
1115
|
+
aggregatedCompare = compareResult.compare;
|
|
1116
|
+
if (compareResult.trivial) hasTrivialComparison = true;
|
|
1117
|
+
} else {
|
|
1118
|
+
const compareResult = compareSignFormule(
|
|
1119
|
+
toRoll,
|
|
1120
|
+
compareRegex,
|
|
1121
|
+
element,
|
|
1122
|
+
diceResult,
|
|
1123
|
+
engine,
|
|
1124
|
+
pity,
|
|
1125
|
+
rollBounds
|
|
1126
|
+
);
|
|
1127
|
+
toRoll = compareResult.dice;
|
|
1128
|
+
results.push(compareResult.results);
|
|
1129
|
+
if (!aggregatedCompare && compareResult.compare)
|
|
1130
|
+
aggregatedCompare = compareResult.compare;
|
|
1131
|
+
if (compareResult.trivial) hasTrivialComparison = true;
|
|
1132
|
+
}
|
|
1133
|
+
} else {
|
|
1134
|
+
const { formule, diceAll } = replaceText(
|
|
1135
|
+
element,
|
|
1136
|
+
diceResult.total,
|
|
1137
|
+
diceResult.dice
|
|
1138
|
+
);
|
|
1139
|
+
try {
|
|
1140
|
+
const evaluated = evaluate8(toRoll);
|
|
1141
|
+
results.push(`\u25C8 ${comment}${diceAll}: ${formule} = ${evaluated}`);
|
|
1142
|
+
total += Number.parseInt(evaluated, 10);
|
|
1143
|
+
} catch (error) {
|
|
1144
|
+
const evaluated = roll(toRoll, engine, pity);
|
|
1145
|
+
if (evaluated) {
|
|
1146
|
+
results.push(
|
|
1147
|
+
`\u25C8 ${comment}${diceAll}: ${evaluated.result.split(":").slice(1).join(":")}`
|
|
1148
|
+
);
|
|
1149
|
+
if (!aggregatedCompare && evaluated.compare)
|
|
1150
|
+
aggregatedCompare = evaluated.compare;
|
|
1151
|
+
if (evaluated.compare?.trivial) hasTrivialComparison = true;
|
|
1152
|
+
} else results.push(`\u25C8 ${comment}${diceAll}: ${formule} = ${evaluated}`);
|
|
1153
|
+
total += evaluated?.total ?? 0;
|
|
1154
|
+
}
|
|
694
1155
|
}
|
|
695
|
-
for (let j = 0; j <= bl; j++) v0[j] = v1[j];
|
|
696
1156
|
}
|
|
697
|
-
|
|
1157
|
+
if (hidden)
|
|
1158
|
+
results.shift();
|
|
1159
|
+
return {
|
|
1160
|
+
dice: displayDice,
|
|
1161
|
+
result: results.join(";"),
|
|
1162
|
+
comment: mainComment,
|
|
1163
|
+
compare: hasTrivialComparison && aggregatedCompare ? { ...aggregatedCompare, trivial: true } : aggregatedCompare,
|
|
1164
|
+
modifier: diceResult.modifier,
|
|
1165
|
+
total,
|
|
1166
|
+
trivial: hasTrivialComparison ? true : void 0
|
|
1167
|
+
};
|
|
698
1168
|
}
|
|
699
|
-
function
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
1169
|
+
function replaceInFormula(element, diceResult, compareResult, res, engine = NumberGenerator7.engines.nodeCrypto, pity) {
|
|
1170
|
+
const { formule, diceAll } = replaceText(
|
|
1171
|
+
element,
|
|
1172
|
+
diceResult.total ?? 0,
|
|
1173
|
+
diceResult.dice
|
|
1174
|
+
);
|
|
1175
|
+
const validSign = res ? "\u2713" : "\u2715";
|
|
1176
|
+
const invertedSign = res ? compareResult.compare.sign : inverseSign(compareResult.compare.sign);
|
|
1177
|
+
let evaluateRoll;
|
|
1178
|
+
try {
|
|
1179
|
+
evaluateRoll = evaluate8(compareResult.dice);
|
|
1180
|
+
return `${validSign} ${diceAll}: ${formule} = ${evaluateRoll}${invertedSign}${compareResult.compare?.value}`;
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
const evaluateRoll2 = roll(compareResult.dice, engine, pity);
|
|
1183
|
+
if (evaluateRoll2)
|
|
1184
|
+
return `${validSign} ${diceAll}: ${evaluateRoll2.result.split(":").splice(1).join(":")}`;
|
|
1185
|
+
return `${validSign} ${diceAll}: ${formule} = ${evaluateRoll2}${invertedSign}${compareResult.compare?.value}`;
|
|
1186
|
+
}
|
|
706
1187
|
}
|
|
707
1188
|
|
|
708
1189
|
// src/verify_template.ts
|
|
709
|
-
import { evaluate as
|
|
1190
|
+
import { evaluate as evaluate9 } from "mathjs";
|
|
710
1191
|
import { Random as Random2 } from "random-js";
|
|
711
1192
|
import "uniformize";
|
|
712
|
-
import { NumberGenerator as
|
|
713
|
-
function evalStatsDice(testDice, allStats, engine =
|
|
1193
|
+
import { NumberGenerator as NumberGenerator8 } from "@dice-roller/rpg-dice-roller";
|
|
1194
|
+
function evalStatsDice(testDice, allStats, engine = NumberGenerator8.engines.nodeCrypto, pity) {
|
|
714
1195
|
let dice = testDice.trimEnd();
|
|
715
1196
|
if (allStats && Object.keys(allStats).length > 0) {
|
|
716
1197
|
const names = Object.keys(allStats);
|
|
@@ -730,7 +1211,7 @@ function evalStatsDice(testDice, allStats, engine = NumberGenerator3.engines.nod
|
|
|
730
1211
|
throw new DiceTypeError(dice, "evalStatsDice", error);
|
|
731
1212
|
}
|
|
732
1213
|
}
|
|
733
|
-
function diceRandomParse(value, template, engine =
|
|
1214
|
+
function diceRandomParse(value, template, engine = NumberGenerator8.engines.nodeCrypto) {
|
|
734
1215
|
if (!template.statistics) return replaceFormulaInDice(value.standardize());
|
|
735
1216
|
value = value.standardize();
|
|
736
1217
|
const statNames = Object.keys(template.statistics);
|
|
@@ -752,7 +1233,7 @@ function diceRandomParse(value, template, engine = NumberGenerator3.engines.node
|
|
|
752
1233
|
}
|
|
753
1234
|
return replaceFormulaInDice(newDice);
|
|
754
1235
|
}
|
|
755
|
-
function diceTypeRandomParse(dice, template, engine =
|
|
1236
|
+
function diceTypeRandomParse(dice, template, engine = NumberGenerator8.engines.nodeCrypto) {
|
|
756
1237
|
dice = replaceExpByRandom(dice);
|
|
757
1238
|
if (!template.statistics) return dice;
|
|
758
1239
|
const firstStatNotcombinaison = Object.keys(template.statistics).find(
|
|
@@ -774,7 +1255,7 @@ function evalCombinaison(combinaison, stats) {
|
|
|
774
1255
|
formula = formula.replace(regex, value.toString());
|
|
775
1256
|
}
|
|
776
1257
|
try {
|
|
777
|
-
newStats[stat] =
|
|
1258
|
+
newStats[stat] = evaluate9(formula);
|
|
778
1259
|
} catch (error) {
|
|
779
1260
|
throw new FormulaError(stat, "evalCombinaison", error);
|
|
780
1261
|
}
|
|
@@ -788,7 +1269,7 @@ function evalOneCombinaison(combinaison, stats) {
|
|
|
788
1269
|
formula = formula.replace(regex, value.toString());
|
|
789
1270
|
}
|
|
790
1271
|
try {
|
|
791
|
-
return
|
|
1272
|
+
return evaluate9(formula);
|
|
792
1273
|
} catch (error) {
|
|
793
1274
|
throw new FormulaError(combinaison, "evalOneCombinaison", error);
|
|
794
1275
|
}
|
|
@@ -800,7 +1281,7 @@ function convertNumber(number) {
|
|
|
800
1281
|
if (isNumber(number)) return Number.parseInt(number.toString(), 10);
|
|
801
1282
|
return void 0;
|
|
802
1283
|
}
|
|
803
|
-
function verifyTemplateValue(template, verify = true, engine =
|
|
1284
|
+
function verifyTemplateValue(template, verify = true, engine = NumberGenerator8.engines.nodeCrypto) {
|
|
804
1285
|
const parsedTemplate = templateSchema.parse(template);
|
|
805
1286
|
const { success, failure } = parsedTemplate.critical ?? {};
|
|
806
1287
|
const criticicalVal = {
|
|
@@ -855,7 +1336,7 @@ function verifyTemplateValue(template, verify = true, engine = NumberGenerator3.
|
|
|
855
1336
|
testStatCombinaison(statistiqueTemplate, engine);
|
|
856
1337
|
return statistiqueTemplate;
|
|
857
1338
|
}
|
|
858
|
-
function testDiceRegistered(template, engine =
|
|
1339
|
+
function testDiceRegistered(template, engine = NumberGenerator8.engines.nodeCrypto) {
|
|
859
1340
|
if (!template.damage) return;
|
|
860
1341
|
if (Object.keys(template.damage).length === 0) throw new EmptyObjectError();
|
|
861
1342
|
if (Object.keys(template.damage).length > 25) throw new TooManyDice();
|
|
@@ -871,7 +1352,7 @@ function testDiceRegistered(template, engine = NumberGenerator3.engines.nodeCryp
|
|
|
871
1352
|
}
|
|
872
1353
|
}
|
|
873
1354
|
}
|
|
874
|
-
function testStatCombinaison(template, engine =
|
|
1355
|
+
function testStatCombinaison(template, engine = NumberGenerator8.engines.nodeCrypto) {
|
|
875
1356
|
if (!template.statistics) return;
|
|
876
1357
|
const onlycombinaisonStats = Object.fromEntries(
|
|
877
1358
|
Object.entries(template.statistics).filter(
|
|
@@ -897,7 +1378,7 @@ function testStatCombinaison(template, engine = NumberGenerator3.engines.nodeCry
|
|
|
897
1378
|
formula = formula.replace(regex, randomStatValue.toString());
|
|
898
1379
|
}
|
|
899
1380
|
try {
|
|
900
|
-
|
|
1381
|
+
evaluate9(formula);
|
|
901
1382
|
} catch (e) {
|
|
902
1383
|
error.push(stat);
|
|
903
1384
|
}
|
|
@@ -905,9 +1386,9 @@ function testStatCombinaison(template, engine = NumberGenerator3.engines.nodeCry
|
|
|
905
1386
|
if (error.length > 0) throw new FormulaError(error.join(", "), "testStatCombinaison");
|
|
906
1387
|
return;
|
|
907
1388
|
}
|
|
908
|
-
function generateRandomStat(total = 100, max, min, engine =
|
|
1389
|
+
function generateRandomStat(total = 100, max, min, engine = NumberGenerator8.engines.nodeCrypto) {
|
|
909
1390
|
let randomStatValue = total + 1;
|
|
910
|
-
const random = new Random2(engine ||
|
|
1391
|
+
const random = new Random2(engine || NumberGenerator8.engines.nodeCrypto);
|
|
911
1392
|
while (randomStatValue >= total || randomStatValue === 0) {
|
|
912
1393
|
if (max && min) randomStatValue = randomInt(min, max, engine, random);
|
|
913
1394
|
else if (max) randomStatValue = randomInt(1, max, engine, random);
|
|
@@ -928,9 +1409,9 @@ export {
|
|
|
928
1409
|
SIGN_REGEX,
|
|
929
1410
|
SIGN_REGEX_SPACE,
|
|
930
1411
|
SYMBOL_DICE,
|
|
1412
|
+
SortOrder,
|
|
931
1413
|
TooManyDice,
|
|
932
1414
|
TooManyStats,
|
|
933
|
-
calculator,
|
|
934
1415
|
createCriticalCustom,
|
|
935
1416
|
diceRandomParse,
|
|
936
1417
|
diceTypeRandomParse,
|
|
@@ -946,6 +1427,7 @@ export {
|
|
|
946
1427
|
randomInt,
|
|
947
1428
|
replaceExpByRandom,
|
|
948
1429
|
replaceFormulaInDice,
|
|
1430
|
+
replaceInFormula,
|
|
949
1431
|
roll,
|
|
950
1432
|
standardizeDice,
|
|
951
1433
|
templateSchema,
|