@yipe/dice 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -30
- package/dist/builder/index.cjs +588 -552
- package/dist/builder/index.cjs.map +1 -1
- package/dist/builder/index.d.cts +15 -3
- package/dist/builder/index.d.ts +15 -3
- package/dist/builder/index.js +588 -552
- package/dist/builder/index.js.map +1 -1
- package/dist/index.cjs +63 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +56 -20
- package/dist/index.js.map +1 -1
- package/dist/{pmf-DIegvlyA.d.cts → pmf-D1p2Re-J.d.cts} +29 -12
- package/dist/{pmf-DIegvlyA.d.ts → pmf-D1p2Re-J.d.ts} +29 -12
- package/package.json +1 -1
package/dist/builder/index.cjs
CHANGED
|
@@ -1387,40 +1387,68 @@ var _PMF = class _PMF {
|
|
|
1387
1387
|
* - A PMF representing its outcome (e.g., damage dice).
|
|
1388
1388
|
* - A weight representing its probability of being selected.
|
|
1389
1389
|
*
|
|
1390
|
-
* This is useful for modeling situations like:
|
|
1391
|
-
* - Sneak Attack: 6d6 if first hit is a crit, 3d6 if first hit is non-crit, 0 otherwise.
|
|
1392
|
-
* - Multiple exclusive spells: Fireball vs Cone of Cold vs nothing.
|
|
1393
|
-
*
|
|
1394
1390
|
* Notes:
|
|
1395
|
-
* - If
|
|
1396
|
-
*
|
|
1397
|
-
*
|
|
1398
|
-
*
|
|
1399
|
-
* @param options Array of `{ pmf, weight }` objects, each representing an outcome and its probability.
|
|
1400
|
-
* @param label Optional name for debugging or charting.
|
|
1401
|
-
* @param eps Optional tolerance for floating point rounding
|
|
1402
|
-
* @returns A single PMF representing the exclusive mixture of all options.
|
|
1391
|
+
* - If total weight < 1 (within eps), leftover mass is assumed to be PMF.zero()
|
|
1392
|
+
*
|
|
1393
|
+
* @param options Array of `{ pmf, weight }` or `[PMF, number]`.
|
|
1394
|
+
* @param eps Optional tolerance for floating point rounding.
|
|
1403
1395
|
*/
|
|
1404
1396
|
static exclusive(options, eps = EPS) {
|
|
1405
1397
|
const items = options.map(
|
|
1406
1398
|
(o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o
|
|
1407
1399
|
);
|
|
1408
|
-
|
|
1409
|
-
|
|
1400
|
+
for (const { weight } of items) {
|
|
1401
|
+
if (!Number.isFinite(weight) || weight < -eps) {
|
|
1402
|
+
throw new Error(`PMF.exclusive: invalid weight ${weight}.`);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
let totalWeight = items.reduce((s, { weight }) => s + weight, 0);
|
|
1406
|
+
if (Math.abs(totalWeight) <= eps) totalWeight = 0;
|
|
1407
|
+
if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1;
|
|
1408
|
+
if (totalWeight > 1 + eps) {
|
|
1410
1409
|
throw new Error(
|
|
1411
1410
|
`PMF.exclusive: total weight ${totalWeight.toFixed(6)} exceeds 1.`
|
|
1412
1411
|
);
|
|
1413
1412
|
}
|
|
1414
|
-
let out =
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
const leftover = 1 - totalWeight;
|
|
1413
|
+
let out = _PMF.empty(eps);
|
|
1414
|
+
for (const { pmf, weight } of items) {
|
|
1415
|
+
if (weight > eps) out = out.addScaled(pmf, weight);
|
|
1416
|
+
}
|
|
1417
|
+
const leftover = Math.max(0, 1 - totalWeight);
|
|
1419
1418
|
if (leftover > eps) {
|
|
1420
1419
|
out = out.addScaled(_PMF.zero(), leftover);
|
|
1421
1420
|
}
|
|
1422
1421
|
return out;
|
|
1423
1422
|
}
|
|
1423
|
+
/**
|
|
1424
|
+
* PMF.mix()
|
|
1425
|
+
*
|
|
1426
|
+
* Builds a PMF as a linear combination of input PMFs with the given weights.
|
|
1427
|
+
* Unlike `exclusive`, this does NOT:
|
|
1428
|
+
* - enforce that weights sum to 1
|
|
1429
|
+
* - add leftover probability to δ0 (PMF.zero())
|
|
1430
|
+
*
|
|
1431
|
+
* Use when outcomes are not mutually exclusive, or for interpolation/blending.
|
|
1432
|
+
*
|
|
1433
|
+
* @param options Array of `{ pmf, weight }` or `[PMF, number]`.
|
|
1434
|
+
* @param eps Optional tolerance for skipping tiny weights.
|
|
1435
|
+
*/
|
|
1436
|
+
static mix(options, eps = EPS) {
|
|
1437
|
+
const items = options.map(
|
|
1438
|
+
(o) => Array.isArray(o) ? { pmf: o[0], weight: o[1] } : o
|
|
1439
|
+
);
|
|
1440
|
+
for (const { weight } of items) {
|
|
1441
|
+
if (!Number.isFinite(weight)) {
|
|
1442
|
+
throw new Error(`PMF.mix: invalid weight ${weight}.`);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
let out = _PMF.empty(eps);
|
|
1446
|
+
for (const { pmf, weight } of items) {
|
|
1447
|
+
if (Math.abs(weight) <= eps) continue;
|
|
1448
|
+
out = out.addScaled(pmf, weight);
|
|
1449
|
+
}
|
|
1450
|
+
return out;
|
|
1451
|
+
}
|
|
1424
1452
|
/**
|
|
1425
1453
|
* General-purpose N-way mixture.
|
|
1426
1454
|
* weights: Array of [weight, PMF].
|
|
@@ -2389,386 +2417,113 @@ function d20PMF(rerollOne) {
|
|
|
2389
2417
|
return result;
|
|
2390
2418
|
}
|
|
2391
2419
|
|
|
2392
|
-
// src/builder/
|
|
2393
|
-
var
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
}
|
|
2420
|
-
|
|
2421
|
-
const n = cfg.rollType === "elven accuracy" ? 3 : 2;
|
|
2422
|
-
const mode = cfg.rollType === "disadvantage" ? "lowest" : "highest";
|
|
2423
|
-
const base = { type: "sum", count: n, child: node };
|
|
2424
|
-
node = { type: "keep", mode, count: 1, child: base };
|
|
2425
|
-
}
|
|
2426
|
-
appliedRollType = true;
|
|
2427
|
-
}
|
|
2428
|
-
if (cfg.rollType === "flat" && cfg.keep && cfg.keep.total > 0) {
|
|
2429
|
-
const base = { type: "sum", count: cfg.keep.total, child: node };
|
|
2430
|
-
node = {
|
|
2431
|
-
type: "keep",
|
|
2432
|
-
mode: cfg.keep.mode,
|
|
2433
|
-
count: cfg.keep.count,
|
|
2434
|
-
child: base
|
|
2435
|
-
};
|
|
2420
|
+
// src/builder/roll.ts
|
|
2421
|
+
var defaultConfig = {
|
|
2422
|
+
count: 1,
|
|
2423
|
+
sides: 0,
|
|
2424
|
+
modifier: 0,
|
|
2425
|
+
reroll: 0,
|
|
2426
|
+
explode: 0,
|
|
2427
|
+
minimum: 0,
|
|
2428
|
+
bestOf: 0,
|
|
2429
|
+
keep: void 0,
|
|
2430
|
+
rollType: "flat"
|
|
2431
|
+
};
|
|
2432
|
+
var RollBuilder = class _RollBuilder {
|
|
2433
|
+
constructor(countOrConfigs = 1) {
|
|
2434
|
+
__publicField(this, "subRollConfigs");
|
|
2435
|
+
// --- Dice Shortcut Methods ---
|
|
2436
|
+
__publicField(this, "d4", () => this.d(4));
|
|
2437
|
+
__publicField(this, "d6", () => this.d(6));
|
|
2438
|
+
__publicField(this, "d8", () => this.d(8));
|
|
2439
|
+
__publicField(this, "d10", () => this.d(10));
|
|
2440
|
+
__publicField(this, "d12", () => this.d(12));
|
|
2441
|
+
__publicField(this, "d20", () => this.d(20));
|
|
2442
|
+
__publicField(this, "d100", () => this.d(100));
|
|
2443
|
+
if (typeof countOrConfigs === "number") {
|
|
2444
|
+
const count = countOrConfigs;
|
|
2445
|
+
if (isNaN(count)) throw new Error("Invalid NaN value for count");
|
|
2446
|
+
this.subRollConfigs = [
|
|
2447
|
+
{ ...defaultConfig, count, isSubtraction: count < 0 }
|
|
2448
|
+
];
|
|
2436
2449
|
} else {
|
|
2437
|
-
|
|
2438
|
-
node = { type: "sum", count: c, child: node };
|
|
2450
|
+
this.subRollConfigs = countOrConfigs.map((c) => ({ ...c }));
|
|
2439
2451
|
}
|
|
2440
|
-
children.push({ node, sign });
|
|
2441
2452
|
}
|
|
2442
|
-
|
|
2443
|
-
return
|
|
2453
|
+
get lastConfig() {
|
|
2454
|
+
return this.subRollConfigs[this.subRollConfigs.length - 1];
|
|
2444
2455
|
}
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
});
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
return resolveSingleDie(node, eps);
|
|
2464
|
-
}
|
|
2465
|
-
case "sum": {
|
|
2466
|
-
const base = resolve(node.child, eps);
|
|
2467
|
-
const n = Math.max(0, Math.floor(node.count));
|
|
2468
|
-
if (n === 0) return PMF.delta(0, eps);
|
|
2469
|
-
if (n === 1) return base;
|
|
2470
|
-
return base.power(n, eps);
|
|
2456
|
+
getSubRollConfigs() {
|
|
2457
|
+
return this.subRollConfigs.map((c) => ({ ...c }));
|
|
2458
|
+
}
|
|
2459
|
+
// for testing
|
|
2460
|
+
static fromConfig(config) {
|
|
2461
|
+
return new _RollBuilder([{ ...defaultConfig, ...config }]);
|
|
2462
|
+
}
|
|
2463
|
+
static fromConfigs(configs) {
|
|
2464
|
+
return new _RollBuilder(
|
|
2465
|
+
configs.map((config) => ({ ...defaultConfig, ...config }))
|
|
2466
|
+
);
|
|
2467
|
+
}
|
|
2468
|
+
static fromArgs(...args) {
|
|
2469
|
+
if (args.length === 1) {
|
|
2470
|
+
const arg = args[0];
|
|
2471
|
+
if (typeof arg === "number") {
|
|
2472
|
+
if (isNaN(arg)) throw new Error("Invalid NaN value for argument");
|
|
2473
|
+
return new _RollBuilder(0).plus(arg);
|
|
2471
2474
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
const parts = [];
|
|
2475
|
-
for (const c of node.children) {
|
|
2476
|
-
if (c.node.type === "constant") {
|
|
2477
|
-
shift += c.sign * c.node.value;
|
|
2478
|
-
} else {
|
|
2479
|
-
const p = resolve(c.node, eps);
|
|
2480
|
-
parts.push(c.sign === 1 ? p : p.mapDamage((v) => -v));
|
|
2481
|
-
}
|
|
2482
|
-
}
|
|
2483
|
-
if (parts.length === 0) return PMF.delta(shift, eps);
|
|
2484
|
-
let res = parts.length === 1 ? parts[0] : PMF.convolveMany(parts, eps);
|
|
2485
|
-
if (shift !== 0) res = res.mapDamage((v) => v + shift);
|
|
2486
|
-
return res;
|
|
2475
|
+
if (arg instanceof _RollBuilder) {
|
|
2476
|
+
return arg;
|
|
2487
2477
|
}
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
if (keepCount === 0 || total === 0) return PMF.delta(0, eps);
|
|
2494
|
-
return keepSumPMF(
|
|
2495
|
-
perDie,
|
|
2496
|
-
total,
|
|
2497
|
-
keepCount,
|
|
2498
|
-
node.mode === "highest",
|
|
2499
|
-
eps
|
|
2500
|
-
);
|
|
2478
|
+
}
|
|
2479
|
+
if (args.length === 2 || args.length === 3) {
|
|
2480
|
+
const [count, sidesOrDie, modifier] = args;
|
|
2481
|
+
if (typeof count !== "number") {
|
|
2482
|
+
throw new Error("First argument must be a number for multi-arg call");
|
|
2501
2483
|
}
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
const
|
|
2505
|
-
|
|
2484
|
+
if (isNaN(count)) throw new Error("Invalid NaN value for count argument");
|
|
2485
|
+
if (sidesOrDie instanceof _RollBuilder) {
|
|
2486
|
+
const subRollConfigs = sidesOrDie.getSubRollConfigs();
|
|
2487
|
+
if (subRollConfigs.length === 0) {
|
|
2488
|
+
const result = new _RollBuilder(0);
|
|
2489
|
+
return modifier !== void 0 ? result.plus(modifier) : result;
|
|
2490
|
+
}
|
|
2491
|
+
const absCount = Math.abs(count);
|
|
2492
|
+
const newConfigs = subRollConfigs.map((config) => ({
|
|
2493
|
+
...config,
|
|
2494
|
+
count: config.count * absCount,
|
|
2495
|
+
modifier: config.modifier * absCount
|
|
2496
|
+
}));
|
|
2497
|
+
let resultBuilder = new _RollBuilder(newConfigs);
|
|
2498
|
+
if (count < 0) {
|
|
2499
|
+
const negatedConfigs = resultBuilder.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));
|
|
2500
|
+
resultBuilder = new _RollBuilder(negatedConfigs);
|
|
2501
|
+
}
|
|
2502
|
+
return modifier !== void 0 ? resultBuilder.plus(modifier) : resultBuilder;
|
|
2503
|
+
} else if (typeof sidesOrDie === "number" || sidesOrDie === void 0) {
|
|
2504
|
+
if (typeof sidesOrDie === "number" && isNaN(sidesOrDie))
|
|
2505
|
+
throw new Error("Invalid NaN value for sides argument");
|
|
2506
|
+
let builder = new _RollBuilder(count);
|
|
2507
|
+
if (sidesOrDie && sidesOrDie > 0) {
|
|
2508
|
+
builder = builder.d(sidesOrDie);
|
|
2509
|
+
}
|
|
2510
|
+
return modifier !== void 0 ? builder.plus(modifier) : builder;
|
|
2506
2511
|
}
|
|
2507
2512
|
}
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
function resolveSingleDie(die, eps = defaultEps) {
|
|
2518
|
-
const signature = getASTSignature(die);
|
|
2519
|
-
const cacheKey = `${signature}_${eps}`;
|
|
2520
|
-
const cached = singleDiePMFCache.get(cacheKey);
|
|
2521
|
-
if (cached) return cached;
|
|
2522
|
-
const s = Math.max(0, Math.floor(die.sides));
|
|
2523
|
-
if (s <= 0) return PMF.delta(0, eps);
|
|
2524
|
-
let probs = /* @__PURE__ */ new Map();
|
|
2525
|
-
for (let v = 1; v <= s; v++) probs.set(v, 1 / s);
|
|
2526
|
-
const r = Math.max(0, Math.floor(die.reroll || 0));
|
|
2527
|
-
if (r > 0) {
|
|
2528
|
-
const k = Math.min(r, s);
|
|
2529
|
-
const rerollMass = k / s;
|
|
2530
|
-
const uniformReroll = rerollMass / s;
|
|
2531
|
-
const next = /* @__PURE__ */ new Map();
|
|
2532
|
-
for (let v = 1; v <= s; v++) {
|
|
2533
|
-
const keep = v <= k ? 0 : 1 / s;
|
|
2534
|
-
next.set(v, keep + uniformReroll);
|
|
2513
|
+
throw new Error(`Invalid arguments passed: ${args.join(", ")}`);
|
|
2514
|
+
}
|
|
2515
|
+
// --- Core Dice Methods ---
|
|
2516
|
+
d(sides) {
|
|
2517
|
+
if (sides !== void 0 && isNaN(sides))
|
|
2518
|
+
throw new Error("Invalid NaN value for sides");
|
|
2519
|
+
if (sides === void 0) return this;
|
|
2520
|
+
if (this.lastConfig.sides && this.lastConfig.sides > 0) {
|
|
2521
|
+
throw new Error("Cannot add a die after adding a die");
|
|
2535
2522
|
}
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
if (minV > 0) pmf = pmf.mapDamage((v) => Math.max(v, minV));
|
|
2541
|
-
const explode = die.explode;
|
|
2542
|
-
if (explode && Number.isFinite(explode) && explode > 0) {
|
|
2543
|
-
const times = Math.floor(explode);
|
|
2544
|
-
const maxFace = s;
|
|
2545
|
-
const nonMax = /* @__PURE__ */ new Map();
|
|
2546
|
-
const pMax = pmf.pAt(maxFace);
|
|
2547
|
-
for (const v of pmf.support()) {
|
|
2548
|
-
if (v !== maxFace) nonMax.set(v, pmf.pAt(v));
|
|
2549
|
-
}
|
|
2550
|
-
let nonMaxPMF = PMF.fromMap(nonMax, eps);
|
|
2551
|
-
if (Math.abs(nonMaxPMF.mass() - (1 - pMax)) > eps) {
|
|
2552
|
-
nonMaxPMF = nonMaxPMF.scaleMass(1 - pMax);
|
|
2553
|
-
}
|
|
2554
|
-
let tail = PMF.delta(0, eps);
|
|
2555
|
-
const addOnce = pmf;
|
|
2556
|
-
for (let t = 1; t <= times; t++) {
|
|
2557
|
-
tail = tail.convolve(addOnce, eps);
|
|
2558
|
-
}
|
|
2559
|
-
const exploded = PMF.branch(
|
|
2560
|
-
tail.mapDamage((v) => v + maxFace),
|
|
2561
|
-
nonMaxPMF,
|
|
2562
|
-
pMax
|
|
2563
|
-
);
|
|
2564
|
-
pmf = exploded;
|
|
2565
|
-
}
|
|
2566
|
-
singleDiePMFCache.set(cacheKey, pmf);
|
|
2567
|
-
return pmf;
|
|
2568
|
-
}
|
|
2569
|
-
function findDie(node) {
|
|
2570
|
-
switch (node.type) {
|
|
2571
|
-
case "die":
|
|
2572
|
-
return node;
|
|
2573
|
-
case "constant":
|
|
2574
|
-
return void 0;
|
|
2575
|
-
case "sum":
|
|
2576
|
-
case "d20Roll":
|
|
2577
|
-
return findDie(node.child);
|
|
2578
|
-
case "keep":
|
|
2579
|
-
return findDie(node.child.child);
|
|
2580
|
-
case "add":
|
|
2581
|
-
for (const c of node.children) {
|
|
2582
|
-
const d2 = findDie(c.node);
|
|
2583
|
-
if (d2) return d2;
|
|
2584
|
-
}
|
|
2585
|
-
return void 0;
|
|
2586
|
-
}
|
|
2587
|
-
}
|
|
2588
|
-
function getTotalCount(node) {
|
|
2589
|
-
let cur = node.child;
|
|
2590
|
-
while (cur.type === "keep") cur = cur.child;
|
|
2591
|
-
return cur.type === "sum" ? Math.max(0, Math.floor(cur.count)) : 0;
|
|
2592
|
-
}
|
|
2593
|
-
function keepSumPMF(single, total, keep, highest, eps = defaultEps) {
|
|
2594
|
-
if (keep >= total) return single.power(total, eps);
|
|
2595
|
-
if (keep <= 0) return PMF.delta(0, eps);
|
|
2596
|
-
const sortedSupport = [...single.support()].sort((a, b) => a - b);
|
|
2597
|
-
const pmfSig = sortedSupport.map((val) => `${val}:${single.pAt(val).toPrecision(6)}`).join(",");
|
|
2598
|
-
const cacheKey = `keep|${pmfSig}|t:${total}|k:${keep}|h:${highest ? 1 : 0}|e:${eps}`;
|
|
2599
|
-
const cached = builderPMFCache.get(cacheKey);
|
|
2600
|
-
if (cached) return cached;
|
|
2601
|
-
const support = single.support();
|
|
2602
|
-
const probs = support.map((v) => ({ v, p: single.pAt(v) }));
|
|
2603
|
-
const out = /* @__PURE__ */ new Map();
|
|
2604
|
-
function dfs(i, accValues, weight) {
|
|
2605
|
-
if (i === total) {
|
|
2606
|
-
const sorted = [...accValues].sort((a, b) => highest ? b - a : a - b);
|
|
2607
|
-
let sum = 0;
|
|
2608
|
-
for (let j = 0; j < keep; j++) sum += sorted[j];
|
|
2609
|
-
out.set(sum, (out.get(sum) || 0) + weight);
|
|
2610
|
-
return;
|
|
2611
|
-
}
|
|
2612
|
-
for (const { v, p } of probs) {
|
|
2613
|
-
if (p <= 0) continue;
|
|
2614
|
-
dfs(i + 1, [...accValues, v], weight * p);
|
|
2615
|
-
}
|
|
2616
|
-
}
|
|
2617
|
-
dfs(0, [], 1);
|
|
2618
|
-
const result = PMF.fromMap(out, eps);
|
|
2619
|
-
builderPMFCache.set(cacheKey, result);
|
|
2620
|
-
return result;
|
|
2621
|
-
}
|
|
2622
|
-
function getASTSignature(node) {
|
|
2623
|
-
switch (node.type) {
|
|
2624
|
-
case "constant":
|
|
2625
|
-
return `c:${node.value}`;
|
|
2626
|
-
case "die": {
|
|
2627
|
-
const parts = [];
|
|
2628
|
-
parts.push(`s:${node.sides}`);
|
|
2629
|
-
if (node.reroll) parts.push(`r:${node.reroll}`);
|
|
2630
|
-
if (node.minimum) parts.push(`m:${node.minimum}`);
|
|
2631
|
-
if (node.explode) parts.push(`e:${node.explode}`);
|
|
2632
|
-
return `d{${parts.join(",")}}`;
|
|
2633
|
-
}
|
|
2634
|
-
case "sum":
|
|
2635
|
-
return `sum{c:${node.count},ch:${getASTSignature(node.child)}}`;
|
|
2636
|
-
case "d20Roll":
|
|
2637
|
-
return `d20{t:${node.rollType},ch:${getASTSignature(node.child)}}`;
|
|
2638
|
-
case "keep":
|
|
2639
|
-
return `keep{c:${node.count},m:${node.mode},ch:${getASTSignature(
|
|
2640
|
-
node.child
|
|
2641
|
-
)}}`;
|
|
2642
|
-
case "add": {
|
|
2643
|
-
let constantValue = 0;
|
|
2644
|
-
const otherChildrenSigs = [];
|
|
2645
|
-
for (const c of node.children) {
|
|
2646
|
-
if (c.node.type === "constant") {
|
|
2647
|
-
constantValue += c.sign * c.node.value;
|
|
2648
|
-
} else {
|
|
2649
|
-
otherChildrenSigs.push(
|
|
2650
|
-
`${c.sign === -1 ? "-" : "+"}${getASTSignature(c.node)}`
|
|
2651
|
-
);
|
|
2652
|
-
}
|
|
2653
|
-
}
|
|
2654
|
-
if (constantValue !== 0) {
|
|
2655
|
-
otherChildrenSigs.push(
|
|
2656
|
-
constantValue > 0 ? `+c:${constantValue}` : `-c:${-constantValue}`
|
|
2657
|
-
);
|
|
2658
|
-
}
|
|
2659
|
-
otherChildrenSigs.sort();
|
|
2660
|
-
return `add[${otherChildrenSigs.join("")}]`;
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
|
|
2665
|
-
// src/builder/roll.ts
|
|
2666
|
-
var defaultConfig = {
|
|
2667
|
-
count: 1,
|
|
2668
|
-
sides: 0,
|
|
2669
|
-
modifier: 0,
|
|
2670
|
-
reroll: 0,
|
|
2671
|
-
explode: 0,
|
|
2672
|
-
minimum: 0,
|
|
2673
|
-
bestOf: 0,
|
|
2674
|
-
keep: void 0,
|
|
2675
|
-
rollType: "flat"
|
|
2676
|
-
};
|
|
2677
|
-
var RollBuilder = class _RollBuilder {
|
|
2678
|
-
constructor(countOrConfigs = 1) {
|
|
2679
|
-
__publicField(this, "subRollConfigs");
|
|
2680
|
-
// --- Dice Shortcut Methods ---
|
|
2681
|
-
__publicField(this, "d4", () => this.d(4));
|
|
2682
|
-
__publicField(this, "d6", () => this.d(6));
|
|
2683
|
-
__publicField(this, "d8", () => this.d(8));
|
|
2684
|
-
__publicField(this, "d10", () => this.d(10));
|
|
2685
|
-
__publicField(this, "d12", () => this.d(12));
|
|
2686
|
-
__publicField(this, "d20", () => this.d(20));
|
|
2687
|
-
__publicField(this, "d100", () => this.d(100));
|
|
2688
|
-
if (typeof countOrConfigs === "number") {
|
|
2689
|
-
const count = countOrConfigs;
|
|
2690
|
-
if (isNaN(count)) throw new Error("Invalid NaN value for count");
|
|
2691
|
-
this.subRollConfigs = [
|
|
2692
|
-
{ ...defaultConfig, count, isSubtraction: count < 0 }
|
|
2693
|
-
];
|
|
2694
|
-
} else {
|
|
2695
|
-
this.subRollConfigs = countOrConfigs.map((c) => ({ ...c }));
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
2698
|
-
get lastConfig() {
|
|
2699
|
-
return this.subRollConfigs[this.subRollConfigs.length - 1];
|
|
2700
|
-
}
|
|
2701
|
-
getSubRollConfigs() {
|
|
2702
|
-
return this.subRollConfigs.map((c) => ({ ...c }));
|
|
2703
|
-
}
|
|
2704
|
-
// for testing
|
|
2705
|
-
static fromConfig(config) {
|
|
2706
|
-
return new _RollBuilder([{ ...defaultConfig, ...config }]);
|
|
2707
|
-
}
|
|
2708
|
-
static fromConfigs(configs) {
|
|
2709
|
-
return new _RollBuilder(
|
|
2710
|
-
configs.map((config) => ({ ...defaultConfig, ...config }))
|
|
2711
|
-
);
|
|
2712
|
-
}
|
|
2713
|
-
static fromArgs(...args) {
|
|
2714
|
-
if (args.length === 1) {
|
|
2715
|
-
const arg = args[0];
|
|
2716
|
-
if (typeof arg === "number") {
|
|
2717
|
-
if (isNaN(arg)) throw new Error("Invalid NaN value for argument");
|
|
2718
|
-
return new _RollBuilder(0).plus(arg);
|
|
2719
|
-
}
|
|
2720
|
-
if (arg instanceof _RollBuilder) {
|
|
2721
|
-
return arg;
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
|
-
if (args.length === 2 || args.length === 3) {
|
|
2725
|
-
const [count, sidesOrDie, modifier] = args;
|
|
2726
|
-
if (typeof count !== "number") {
|
|
2727
|
-
throw new Error("First argument must be a number for multi-arg call");
|
|
2728
|
-
}
|
|
2729
|
-
if (isNaN(count)) throw new Error("Invalid NaN value for count argument");
|
|
2730
|
-
if (sidesOrDie instanceof _RollBuilder) {
|
|
2731
|
-
const subRollConfigs = sidesOrDie.getSubRollConfigs();
|
|
2732
|
-
if (subRollConfigs.length === 0) {
|
|
2733
|
-
const result = new _RollBuilder(0);
|
|
2734
|
-
return modifier !== void 0 ? result.plus(modifier) : result;
|
|
2735
|
-
}
|
|
2736
|
-
const absCount = Math.abs(count);
|
|
2737
|
-
const newConfigs = subRollConfigs.map((config) => ({
|
|
2738
|
-
...config,
|
|
2739
|
-
count: config.count * absCount,
|
|
2740
|
-
modifier: config.modifier * absCount
|
|
2741
|
-
}));
|
|
2742
|
-
let resultBuilder = new _RollBuilder(newConfigs);
|
|
2743
|
-
if (count < 0) {
|
|
2744
|
-
const negatedConfigs = resultBuilder.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));
|
|
2745
|
-
resultBuilder = new _RollBuilder(negatedConfigs);
|
|
2746
|
-
}
|
|
2747
|
-
return modifier !== void 0 ? resultBuilder.plus(modifier) : resultBuilder;
|
|
2748
|
-
} else if (typeof sidesOrDie === "number" || sidesOrDie === void 0) {
|
|
2749
|
-
if (typeof sidesOrDie === "number" && isNaN(sidesOrDie))
|
|
2750
|
-
throw new Error("Invalid NaN value for sides argument");
|
|
2751
|
-
let builder = new _RollBuilder(count);
|
|
2752
|
-
if (sidesOrDie && sidesOrDie > 0) {
|
|
2753
|
-
builder = builder.d(sidesOrDie);
|
|
2754
|
-
}
|
|
2755
|
-
return modifier !== void 0 ? builder.plus(modifier) : builder;
|
|
2756
|
-
}
|
|
2757
|
-
}
|
|
2758
|
-
throw new Error(`Invalid arguments passed: ${args.join(", ")}`);
|
|
2759
|
-
}
|
|
2760
|
-
// --- Core Dice Methods ---
|
|
2761
|
-
d(sides) {
|
|
2762
|
-
if (sides !== void 0 && isNaN(sides))
|
|
2763
|
-
throw new Error("Invalid NaN value for sides");
|
|
2764
|
-
if (sides === void 0) return this;
|
|
2765
|
-
if (this.lastConfig.sides && this.lastConfig.sides > 0) {
|
|
2766
|
-
throw new Error("Cannot add a die after adding a die");
|
|
2767
|
-
}
|
|
2768
|
-
if (sides === 0) return this;
|
|
2769
|
-
const newConfigs = this.getSubRollConfigs();
|
|
2770
|
-
newConfigs[newConfigs.length - 1].sides = sides;
|
|
2771
|
-
return new this.constructor(newConfigs);
|
|
2523
|
+
if (sides === 0) return this;
|
|
2524
|
+
const newConfigs = this.getSubRollConfigs();
|
|
2525
|
+
newConfigs[newConfigs.length - 1].sides = sides;
|
|
2526
|
+
return new this.constructor(newConfigs);
|
|
2772
2527
|
}
|
|
2773
2528
|
plus(modOrRoll, die) {
|
|
2774
2529
|
if (typeof modOrRoll === "number" && isNaN(modOrRoll))
|
|
@@ -2903,19 +2658,11 @@ var RollBuilder = class _RollBuilder {
|
|
|
2903
2658
|
copy() {
|
|
2904
2659
|
return new this.constructor(this.getSubRollConfigs());
|
|
2905
2660
|
}
|
|
2906
|
-
ac(targetAC) {
|
|
2907
|
-
if (isNaN(targetAC)) throw new Error("Invalid NaN value for targetAC");
|
|
2908
|
-
return new ACBuilder(this, targetAC);
|
|
2909
|
-
}
|
|
2910
2661
|
withElvenAccuracy() {
|
|
2911
2662
|
const newConfigs = this.getSubRollConfigs();
|
|
2912
2663
|
newConfigs[newConfigs.length - 1].rollType = "elven accuracy";
|
|
2913
2664
|
return new this.constructor(newConfigs);
|
|
2914
2665
|
}
|
|
2915
|
-
dc(saveDC) {
|
|
2916
|
-
if (isNaN(saveDC)) throw new Error("Invalid NaN value for saveDC");
|
|
2917
|
-
return new DCBuilder(this).dc(saveDC);
|
|
2918
|
-
}
|
|
2919
2666
|
toExpression() {
|
|
2920
2667
|
const diceConfigs = this.subRollConfigs.filter(
|
|
2921
2668
|
(config) => config.sides && config.sides > 0
|
|
@@ -2943,118 +2690,444 @@ var RollBuilder = class _RollBuilder {
|
|
|
2943
2690
|
result += operator + expression;
|
|
2944
2691
|
}
|
|
2945
2692
|
}
|
|
2946
|
-
if (totalModifier > 0) result += ` + ${totalModifier}`;
|
|
2947
|
-
else if (totalModifier < 0) result += ` - ${Math.abs(totalModifier)}`;
|
|
2948
|
-
return result.replace(/\+ -/g, "-");
|
|
2949
|
-
}
|
|
2950
|
-
toPMF(eps = 0) {
|
|
2951
|
-
return pmfFromRollBuilder(this, eps);
|
|
2952
|
-
}
|
|
2953
|
-
|
|
2954
|
-
return this.toPMF(
|
|
2955
|
-
}
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2693
|
+
if (totalModifier > 0) result += ` + ${totalModifier}`;
|
|
2694
|
+
else if (totalModifier < 0) result += ` - ${Math.abs(totalModifier)}`;
|
|
2695
|
+
return result.replace(/\+ -/g, "-");
|
|
2696
|
+
}
|
|
2697
|
+
toPMF(eps = 0) {
|
|
2698
|
+
return pmfFromRollBuilder(this, eps);
|
|
2699
|
+
}
|
|
2700
|
+
get pmf() {
|
|
2701
|
+
return this.toPMF();
|
|
2702
|
+
}
|
|
2703
|
+
toQuery(eps = 0) {
|
|
2704
|
+
return this.toPMF(eps).query();
|
|
2705
|
+
}
|
|
2706
|
+
toAST() {
|
|
2707
|
+
const configs = this.getSubRollConfigs();
|
|
2708
|
+
return astFromRollConfigs(configs) || { type: "constant", value: 0 };
|
|
2709
|
+
}
|
|
2710
|
+
configToSingleExpressionWithoutModifier(config, isRootDie) {
|
|
2711
|
+
if (!config.sides || config.sides <= 0) return "";
|
|
2712
|
+
let baseDie = `d${config.sides}`;
|
|
2713
|
+
if (config.reroll > 0) {
|
|
2714
|
+
if (config.minimum > 0 && config.explode > 0) ; else if (config.minimum > 0) {
|
|
2715
|
+
for (let i = config.reroll; i >= 1; i--) baseDie += ` reroll ${i}`;
|
|
2716
|
+
} else {
|
|
2717
|
+
for (let i = 1; i <= config.reroll; i++) baseDie += ` reroll ${i}`;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
if (config.minimum > 0) {
|
|
2721
|
+
baseDie = `${config.minimum}>${baseDie}`;
|
|
2722
|
+
if (config.reroll > 0 && config.explode > 0) {
|
|
2723
|
+
for (let i = 1; i <= config.reroll; i++) {
|
|
2724
|
+
baseDie += ` reroll ${i}`;
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
if (baseDie === "d20 reroll 1" && config.minimum <= 1) baseDie = "hd20";
|
|
2729
|
+
let mainExpression = "";
|
|
2730
|
+
switch (config.rollType) {
|
|
2731
|
+
case "advantage":
|
|
2732
|
+
mainExpression = `${baseDie} > ${baseDie}`;
|
|
2733
|
+
break;
|
|
2734
|
+
case "disadvantage":
|
|
2735
|
+
mainExpression = `${baseDie} < ${baseDie}`;
|
|
2736
|
+
break;
|
|
2737
|
+
case "elven accuracy":
|
|
2738
|
+
mainExpression = `${baseDie} > ${baseDie} > ${baseDie}`;
|
|
2739
|
+
break;
|
|
2740
|
+
case "flat":
|
|
2741
|
+
if (config.keep) {
|
|
2742
|
+
const mode = config.keep.mode === "highest" ? "kh" : "kl";
|
|
2743
|
+
mainExpression = `${config.keep.total}${mode}${config.keep.count}(${baseDie})`;
|
|
2744
|
+
} else {
|
|
2745
|
+
const isComplex = baseDie.length > `d${config.sides}`.length;
|
|
2746
|
+
const isHalflingShorthand = baseDie === "hd20";
|
|
2747
|
+
const isD20Shorthand = baseDie === "d20" && isRootDie;
|
|
2748
|
+
const hasMinimum = baseDie.startsWith("(") && baseDie.includes(">");
|
|
2749
|
+
const hasReroll = baseDie.includes("reroll");
|
|
2750
|
+
const effectiveCount = config.isSubtraction ? Math.abs(config.count) : config.count < 0 ? 1 : Math.abs(config.count);
|
|
2751
|
+
if (effectiveCount > 1) {
|
|
2752
|
+
const shouldAddParentheses = isComplex && (// Complex single roll: explode + reroll
|
|
2753
|
+
!hasMinimum && !hasReroll || // Simple complex case
|
|
2754
|
+
hasReroll && !hasMinimum);
|
|
2755
|
+
mainExpression = shouldAddParentheses ? `${effectiveCount}(${baseDie})` : `${effectiveCount}${baseDie}`;
|
|
2756
|
+
} else if (effectiveCount === 1) {
|
|
2757
|
+
if (config.isSubtraction) {
|
|
2758
|
+
mainExpression = `1${baseDie}`;
|
|
2759
|
+
} else if (isComplex || isHalflingShorthand || isD20Shorthand || config.count < 0) {
|
|
2760
|
+
mainExpression = baseDie;
|
|
2761
|
+
} else {
|
|
2762
|
+
mainExpression = `1${baseDie}`;
|
|
2763
|
+
}
|
|
2764
|
+
} else {
|
|
2765
|
+
mainExpression = baseDie;
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
if (config.bestOf && config.count && config.bestOf < config.count) {
|
|
2769
|
+
mainExpression += `kh${config.bestOf}`;
|
|
2770
|
+
}
|
|
2771
|
+
break;
|
|
2772
|
+
}
|
|
2773
|
+
return mainExpression;
|
|
2774
|
+
}
|
|
2775
|
+
getRootDieConfig() {
|
|
2776
|
+
const configs = this.subRollConfigs;
|
|
2777
|
+
return configs.find((config) => config.sides > 0) || configs[0];
|
|
2778
|
+
}
|
|
2779
|
+
getAllDieConfigs() {
|
|
2780
|
+
return this.getSubRollConfigs();
|
|
2781
|
+
}
|
|
2782
|
+
getBonusDiceConfigs() {
|
|
2783
|
+
const allConfigs = this.subRollConfigs;
|
|
2784
|
+
const rootConfig = allConfigs.find((config) => config.sides > 0) || allConfigs[0];
|
|
2785
|
+
if (!rootConfig) return [];
|
|
2786
|
+
return allConfigs.filter((config) => config.sides > 0).filter((config) => config !== rootConfig);
|
|
2787
|
+
}
|
|
2788
|
+
getBonusDicePMFs(check, eps = 0) {
|
|
2789
|
+
return check.getBonusDiceConfigs().map(
|
|
2790
|
+
(config) => pmfFromRollBuilder(_RollBuilder.fromConfigs([config]), eps)
|
|
2791
|
+
);
|
|
2792
|
+
}
|
|
2793
|
+
get modifier() {
|
|
2794
|
+
return this.subRollConfigs.reduce(
|
|
2795
|
+
(sum, config) => sum + config.modifier,
|
|
2796
|
+
0
|
|
2797
|
+
);
|
|
2798
|
+
}
|
|
2799
|
+
get rollType() {
|
|
2800
|
+
const rootConfig = this.getRootDieConfig();
|
|
2801
|
+
return rootConfig?.rollType || "flat";
|
|
2802
|
+
}
|
|
2803
|
+
get baseReroll() {
|
|
2804
|
+
const rootConfig = this.getRootDieConfig();
|
|
2805
|
+
return rootConfig?.reroll || 0;
|
|
2806
|
+
}
|
|
2807
|
+
};
|
|
2808
|
+
|
|
2809
|
+
// src/builder/factory.ts
|
|
2810
|
+
var rollFn = (count, sidesOrDie, modifier) => {
|
|
2811
|
+
if (sidesOrDie instanceof RollBuilder) {
|
|
2812
|
+
const subRollConfigs = sidesOrDie.getSubRollConfigs();
|
|
2813
|
+
if (subRollConfigs.length === 0) return new RollBuilder(0).plus(modifier);
|
|
2814
|
+
const absCount = Math.abs(count);
|
|
2815
|
+
const newConfigs = subRollConfigs.map((config) => ({
|
|
2816
|
+
...config,
|
|
2817
|
+
count: config.count * absCount,
|
|
2818
|
+
modifier: config.modifier * absCount
|
|
2819
|
+
}));
|
|
2820
|
+
let resultBuilder = new RollBuilder(newConfigs);
|
|
2821
|
+
if (count < 0) {
|
|
2822
|
+
const negatedConfigs = resultBuilder.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));
|
|
2823
|
+
resultBuilder = new RollBuilder(negatedConfigs);
|
|
2824
|
+
}
|
|
2825
|
+
return resultBuilder.plus(modifier);
|
|
2826
|
+
} else {
|
|
2827
|
+
let builder = new RollBuilder(count);
|
|
2828
|
+
if (sidesOrDie && sidesOrDie > 0) {
|
|
2829
|
+
builder = builder.d(sidesOrDie);
|
|
2830
|
+
}
|
|
2831
|
+
return builder.plus(modifier);
|
|
2832
|
+
}
|
|
2833
|
+
};
|
|
2834
|
+
rollFn.d = (sides) => new RollBuilder(1).d(sides);
|
|
2835
|
+
rollFn.hd20 = () => new RollBuilder(1).d20().reroll(1);
|
|
2836
|
+
rollFn.d4 = () => new RollBuilder(1).d4();
|
|
2837
|
+
rollFn.d6 = () => new RollBuilder(1).d6();
|
|
2838
|
+
rollFn.d8 = () => new RollBuilder(1).d8();
|
|
2839
|
+
rollFn.d10 = () => new RollBuilder(1).d10();
|
|
2840
|
+
rollFn.d12 = () => new RollBuilder(1).d12();
|
|
2841
|
+
rollFn.d20 = () => new RollBuilder(1).d20();
|
|
2842
|
+
rollFn.d100 = () => new RollBuilder(1).d100();
|
|
2843
|
+
rollFn.flat = (n) => new RollBuilder(0).plus(n);
|
|
2844
|
+
function d(sides) {
|
|
2845
|
+
return new RollBuilder(1).d(sides);
|
|
2846
|
+
}
|
|
2847
|
+
var d4 = new RollBuilder(1).d4();
|
|
2848
|
+
var d6 = new RollBuilder(1).d6();
|
|
2849
|
+
var d8 = new RollBuilder(1).d8();
|
|
2850
|
+
var d10 = new RollBuilder(1).d10();
|
|
2851
|
+
var d12 = new RollBuilder(1).d12();
|
|
2852
|
+
var d20 = new RollBuilder(1).d20();
|
|
2853
|
+
var hd20 = new RollBuilder(1).d20().reroll(1);
|
|
2854
|
+
var d100 = new RollBuilder(1).d100();
|
|
2855
|
+
var flat = (n) => new RollBuilder(0).plus(n);
|
|
2856
|
+
var roll = rollFn;
|
|
2857
|
+
var builderPMFCache = new LRUCache(1e3);
|
|
2858
|
+
|
|
2859
|
+
// src/builder/ast.ts
|
|
2860
|
+
var defaultEps = 0;
|
|
2861
|
+
var singleDiePMFCache = new LRUCache(1e3);
|
|
2862
|
+
function astFromRollConfigs(configs) {
|
|
2863
|
+
if (!configs || configs.length === 0) return void 0;
|
|
2864
|
+
const children = [];
|
|
2865
|
+
let constantSum = 0;
|
|
2866
|
+
for (const cfg of configs) {
|
|
2867
|
+
const sign = cfg.isSubtraction || cfg.count < 0 ? -1 : 1;
|
|
2868
|
+
const count = Math.abs(cfg.count || 0);
|
|
2869
|
+
constantSum += cfg.modifier || 0;
|
|
2870
|
+
if ((cfg.sides || 0) <= 0) continue;
|
|
2871
|
+
const die = {
|
|
2872
|
+
type: "die",
|
|
2873
|
+
sides: cfg.sides,
|
|
2874
|
+
reroll: cfg.reroll > 0 ? cfg.reroll : void 0,
|
|
2875
|
+
minimum: cfg.minimum > 0 ? cfg.minimum : void 0,
|
|
2876
|
+
explode: cfg.explode && Number.isFinite(cfg.explode) && cfg.explode > 0 ? cfg.explode : void 0
|
|
2877
|
+
};
|
|
2878
|
+
let node = die;
|
|
2879
|
+
let appliedRollType = false;
|
|
2880
|
+
if (cfg.rollType && cfg.rollType !== "flat") {
|
|
2881
|
+
if (cfg.sides === 20) {
|
|
2882
|
+
node = {
|
|
2883
|
+
type: "d20Roll",
|
|
2884
|
+
rollType: cfg.rollType,
|
|
2885
|
+
child: node
|
|
2886
|
+
};
|
|
2887
|
+
} else {
|
|
2888
|
+
const n = cfg.rollType === "elven accuracy" ? 3 : 2;
|
|
2889
|
+
const mode = cfg.rollType === "disadvantage" ? "lowest" : "highest";
|
|
2890
|
+
const base = { type: "sum", count: n, child: node };
|
|
2891
|
+
node = { type: "keep", mode, count: 1, child: base };
|
|
2892
|
+
}
|
|
2893
|
+
appliedRollType = true;
|
|
2894
|
+
}
|
|
2895
|
+
if (cfg.rollType === "flat" && cfg.keep && cfg.keep.total > 0) {
|
|
2896
|
+
const base = { type: "sum", count: cfg.keep.total, child: node };
|
|
2897
|
+
node = {
|
|
2898
|
+
type: "keep",
|
|
2899
|
+
mode: cfg.keep.mode,
|
|
2900
|
+
count: cfg.keep.count,
|
|
2901
|
+
child: base
|
|
2902
|
+
};
|
|
2903
|
+
} else {
|
|
2904
|
+
const c = appliedRollType ? 1 : Math.max(1, count || 1);
|
|
2905
|
+
node = { type: "sum", count: c, child: node };
|
|
2906
|
+
}
|
|
2907
|
+
children.push({ node, sign });
|
|
2908
|
+
}
|
|
2909
|
+
if (children.length === 0) {
|
|
2910
|
+
return { type: "constant", value: constantSum };
|
|
2911
|
+
}
|
|
2912
|
+
const add = { type: "add", children };
|
|
2913
|
+
if (constantSum !== 0)
|
|
2914
|
+
add.children.push({
|
|
2915
|
+
node: { type: "constant", value: constantSum },
|
|
2916
|
+
sign: 1
|
|
2917
|
+
});
|
|
2918
|
+
return add;
|
|
2919
|
+
}
|
|
2920
|
+
function resolve(node, eps = defaultEps) {
|
|
2921
|
+
const signature = getASTSignature(node);
|
|
2922
|
+
const cacheKey = `${signature}_${eps}`;
|
|
2923
|
+
const cached = builderPMFCache.get(cacheKey);
|
|
2924
|
+
if (cached) return cached;
|
|
2925
|
+
const result = (() => {
|
|
2926
|
+
switch (node.type) {
|
|
2927
|
+
case "constant":
|
|
2928
|
+
return PMF.delta(node.value, eps);
|
|
2929
|
+
case "die": {
|
|
2930
|
+
return resolveSingleDie(node, eps);
|
|
2931
|
+
}
|
|
2932
|
+
case "sum": {
|
|
2933
|
+
const base = resolve(node.child, eps);
|
|
2934
|
+
const n = Math.max(0, Math.floor(node.count));
|
|
2935
|
+
if (n === 0) return PMF.delta(0, eps);
|
|
2936
|
+
if (n === 1) return base;
|
|
2937
|
+
return base.power(n, eps);
|
|
2938
|
+
}
|
|
2939
|
+
case "add": {
|
|
2940
|
+
let shift = 0;
|
|
2941
|
+
const parts = [];
|
|
2942
|
+
for (const c of node.children) {
|
|
2943
|
+
if (c.node.type === "constant") {
|
|
2944
|
+
shift += c.sign * c.node.value;
|
|
2945
|
+
} else {
|
|
2946
|
+
const p = resolve(c.node, eps);
|
|
2947
|
+
parts.push(c.sign === 1 ? p : p.mapDamage((v) => -v));
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
if (parts.length === 0) return PMF.delta(shift, eps);
|
|
2951
|
+
let res = parts.length === 1 ? parts[0] : PMF.convolveMany(parts, eps);
|
|
2952
|
+
if (shift !== 0) res = res.mapDamage((v) => v + shift);
|
|
2953
|
+
return res;
|
|
2954
|
+
}
|
|
2955
|
+
case "keep": {
|
|
2956
|
+
const total = getTotalCount(node);
|
|
2957
|
+
const die = findDie(node.child.child);
|
|
2958
|
+
const perDie = die ? resolveSingleDie(die, eps) : PMF.delta(0, eps);
|
|
2959
|
+
const keepCount = Math.max(0, Math.min(node.count, total));
|
|
2960
|
+
if (keepCount === 0 || total === 0) return PMF.delta(0, eps);
|
|
2961
|
+
return keepSumPMF(
|
|
2962
|
+
perDie,
|
|
2963
|
+
total,
|
|
2964
|
+
keepCount,
|
|
2965
|
+
node.mode === "highest",
|
|
2966
|
+
eps
|
|
2967
|
+
);
|
|
2968
|
+
}
|
|
2969
|
+
case "d20Roll": {
|
|
2970
|
+
const childDie = findDie(node.child);
|
|
2971
|
+
const rerollOne = !!childDie && (childDie.reroll || 0) >= 1;
|
|
2972
|
+
return d20RollPMF(node.rollType, rerollOne);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
})();
|
|
2976
|
+
builderPMFCache.set(cacheKey, result);
|
|
2977
|
+
return result;
|
|
2978
|
+
}
|
|
2979
|
+
function pmfFromRollBuilder(rb, eps = defaultEps) {
|
|
2980
|
+
const configs = rb.getSubRollConfigs?.() ?? [];
|
|
2981
|
+
const ast = astFromRollConfigs(configs) || { type: "constant", value: 0 };
|
|
2982
|
+
return resolve(ast, eps);
|
|
2983
|
+
}
|
|
2984
|
+
function resolveSingleDie(die, eps = defaultEps) {
|
|
2985
|
+
const signature = getASTSignature(die);
|
|
2986
|
+
const cacheKey = `${signature}_${eps}`;
|
|
2987
|
+
const cached = singleDiePMFCache.get(cacheKey);
|
|
2988
|
+
if (cached) return cached;
|
|
2989
|
+
const s = Math.max(0, Math.floor(die.sides));
|
|
2990
|
+
if (s <= 0) return PMF.delta(0, eps);
|
|
2991
|
+
let probs = /* @__PURE__ */ new Map();
|
|
2992
|
+
for (let v = 1; v <= s; v++) probs.set(v, 1 / s);
|
|
2993
|
+
const r = Math.max(0, Math.floor(die.reroll || 0));
|
|
2994
|
+
if (r > 0) {
|
|
2995
|
+
const k = Math.min(r, s);
|
|
2996
|
+
const rerollMass = k / s;
|
|
2997
|
+
const uniformReroll = rerollMass / s;
|
|
2998
|
+
const next = /* @__PURE__ */ new Map();
|
|
2999
|
+
for (let v = 1; v <= s; v++) {
|
|
3000
|
+
const keep = v <= k ? 0 : 1 / s;
|
|
3001
|
+
next.set(v, keep + uniformReroll);
|
|
3002
|
+
}
|
|
3003
|
+
probs = next;
|
|
3004
|
+
}
|
|
3005
|
+
let pmf = PMF.fromMap(new Map(probs), eps);
|
|
3006
|
+
const minV = Math.max(0, Math.floor(die.minimum || 0));
|
|
3007
|
+
if (minV > 0) pmf = pmf.mapDamage((v) => Math.max(v, minV));
|
|
3008
|
+
const explode = die.explode;
|
|
3009
|
+
if (explode && Number.isFinite(explode) && explode > 0) {
|
|
3010
|
+
const times = Math.floor(explode);
|
|
3011
|
+
const maxFace = s;
|
|
3012
|
+
const nonMax = /* @__PURE__ */ new Map();
|
|
3013
|
+
const pMax = pmf.pAt(maxFace);
|
|
3014
|
+
for (const v of pmf.support()) {
|
|
3015
|
+
if (v !== maxFace) nonMax.set(v, pmf.pAt(v));
|
|
3016
|
+
}
|
|
3017
|
+
let nonMaxPMF = PMF.fromMap(nonMax, eps);
|
|
3018
|
+
if (Math.abs(nonMaxPMF.mass() - (1 - pMax)) > eps) {
|
|
3019
|
+
nonMaxPMF = nonMaxPMF.scaleMass(1 - pMax);
|
|
3020
|
+
}
|
|
3021
|
+
let tail = PMF.delta(0, eps);
|
|
3022
|
+
const addOnce = pmf;
|
|
3023
|
+
for (let t = 1; t <= times; t++) {
|
|
3024
|
+
tail = tail.convolve(addOnce, eps);
|
|
3025
|
+
}
|
|
3026
|
+
const exploded = PMF.branch(
|
|
3027
|
+
tail.mapDamage((v) => v + maxFace),
|
|
3028
|
+
nonMaxPMF,
|
|
3029
|
+
pMax
|
|
3030
|
+
);
|
|
3031
|
+
pmf = exploded;
|
|
2959
3032
|
}
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
3033
|
+
singleDiePMFCache.set(cacheKey, pmf);
|
|
3034
|
+
return pmf;
|
|
3035
|
+
}
|
|
3036
|
+
function findDie(node) {
|
|
3037
|
+
switch (node.type) {
|
|
3038
|
+
case "die":
|
|
3039
|
+
return node;
|
|
3040
|
+
case "constant":
|
|
3041
|
+
return void 0;
|
|
3042
|
+
case "sum":
|
|
3043
|
+
case "d20Roll":
|
|
3044
|
+
return findDie(node.child);
|
|
3045
|
+
case "keep":
|
|
3046
|
+
return findDie(node.child.child);
|
|
3047
|
+
case "add":
|
|
3048
|
+
for (const c of node.children) {
|
|
3049
|
+
const d2 = findDie(c.node);
|
|
3050
|
+
if (d2) return d2;
|
|
2968
3051
|
}
|
|
3052
|
+
return void 0;
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
function getTotalCount(node) {
|
|
3056
|
+
let cur = node.child;
|
|
3057
|
+
while (cur.type === "keep") cur = cur.child;
|
|
3058
|
+
return cur.type === "sum" ? Math.max(0, Math.floor(cur.count)) : 0;
|
|
3059
|
+
}
|
|
3060
|
+
function keepSumPMF(single, total, keep, highest, eps = defaultEps) {
|
|
3061
|
+
if (keep >= total) return single.power(total, eps);
|
|
3062
|
+
if (keep <= 0) return PMF.delta(0, eps);
|
|
3063
|
+
const sortedSupport = [...single.support()].sort((a, b) => a - b);
|
|
3064
|
+
const pmfSig = sortedSupport.map((val) => `${val}:${single.pAt(val).toPrecision(6)}`).join(",");
|
|
3065
|
+
const cacheKey = `keep|${pmfSig}|t:${total}|k:${keep}|h:${highest ? 1 : 0}|e:${eps}`;
|
|
3066
|
+
const cached = builderPMFCache.get(cacheKey);
|
|
3067
|
+
if (cached) return cached;
|
|
3068
|
+
const support = single.support();
|
|
3069
|
+
const probs = support.map((v) => ({ v, p: single.pAt(v) }));
|
|
3070
|
+
const out = /* @__PURE__ */ new Map();
|
|
3071
|
+
function dfs(i, accValues, weight) {
|
|
3072
|
+
if (i === total) {
|
|
3073
|
+
const sorted = [...accValues].sort((a, b) => highest ? b - a : a - b);
|
|
3074
|
+
let sum = 0;
|
|
3075
|
+
for (let j = 0; j < keep; j++) sum += sorted[j];
|
|
3076
|
+
out.set(sum, (out.get(sum) || 0) + weight);
|
|
3077
|
+
return;
|
|
2969
3078
|
}
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
for (let i = 1; i <= config.reroll; i++) {
|
|
2974
|
-
baseDie += ` reroll ${i}`;
|
|
2975
|
-
}
|
|
2976
|
-
}
|
|
3079
|
+
for (const { v, p } of probs) {
|
|
3080
|
+
if (p <= 0) continue;
|
|
3081
|
+
dfs(i + 1, [...accValues, v], weight * p);
|
|
2977
3082
|
}
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
3083
|
+
}
|
|
3084
|
+
dfs(0, [], 1);
|
|
3085
|
+
const result = PMF.fromMap(out, eps);
|
|
3086
|
+
builderPMFCache.set(cacheKey, result);
|
|
3087
|
+
return result;
|
|
3088
|
+
}
|
|
3089
|
+
function getASTSignature(node) {
|
|
3090
|
+
switch (node.type) {
|
|
3091
|
+
case "constant":
|
|
3092
|
+
return `c:${node.value}`;
|
|
3093
|
+
case "die": {
|
|
3094
|
+
const parts = [];
|
|
3095
|
+
parts.push(`s:${node.sides}`);
|
|
3096
|
+
if (node.reroll) parts.push(`r:${node.reroll}`);
|
|
3097
|
+
if (node.minimum) parts.push(`m:${node.minimum}`);
|
|
3098
|
+
if (node.explode) parts.push(`e:${node.explode}`);
|
|
3099
|
+
return `d{${parts.join(",")}}`;
|
|
3100
|
+
}
|
|
3101
|
+
case "sum":
|
|
3102
|
+
return `sum{c:${node.count},ch:${getASTSignature(node.child)}}`;
|
|
3103
|
+
case "d20Roll":
|
|
3104
|
+
return `d20{t:${node.rollType},ch:${getASTSignature(node.child)}}`;
|
|
3105
|
+
case "keep":
|
|
3106
|
+
return `keep{c:${node.count},m:${node.mode},ch:${getASTSignature(
|
|
3107
|
+
node.child
|
|
3108
|
+
)}}`;
|
|
3109
|
+
case "add": {
|
|
3110
|
+
let constantValue = 0;
|
|
3111
|
+
const otherChildrenSigs = [];
|
|
3112
|
+
for (const c of node.children) {
|
|
3113
|
+
if (c.node.type === "constant") {
|
|
3114
|
+
constantValue += c.sign * c.node.value;
|
|
2994
3115
|
} else {
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
const hasMinimum = baseDie.startsWith("(") && baseDie.includes(">");
|
|
2999
|
-
const hasReroll = baseDie.includes("reroll");
|
|
3000
|
-
const effectiveCount = config.isSubtraction ? Math.abs(config.count) : config.count < 0 ? 1 : Math.abs(config.count);
|
|
3001
|
-
if (effectiveCount > 1) {
|
|
3002
|
-
const shouldAddParentheses = isComplex && (// Complex single roll: explode + reroll
|
|
3003
|
-
!hasMinimum && !hasReroll || // Simple complex case
|
|
3004
|
-
hasReroll && !hasMinimum);
|
|
3005
|
-
mainExpression = shouldAddParentheses ? `${effectiveCount}(${baseDie})` : `${effectiveCount}${baseDie}`;
|
|
3006
|
-
} else if (effectiveCount === 1) {
|
|
3007
|
-
if (config.isSubtraction) {
|
|
3008
|
-
mainExpression = `1${baseDie}`;
|
|
3009
|
-
} else if (isComplex || isHalflingShorthand || isD20Shorthand || config.count < 0) {
|
|
3010
|
-
mainExpression = baseDie;
|
|
3011
|
-
} else {
|
|
3012
|
-
mainExpression = `1${baseDie}`;
|
|
3013
|
-
}
|
|
3014
|
-
} else {
|
|
3015
|
-
mainExpression = baseDie;
|
|
3016
|
-
}
|
|
3017
|
-
}
|
|
3018
|
-
if (config.bestOf && config.count && config.bestOf < config.count) {
|
|
3019
|
-
mainExpression += `kh${config.bestOf}`;
|
|
3116
|
+
otherChildrenSigs.push(
|
|
3117
|
+
`${c.sign === -1 ? "-" : "+"}${getASTSignature(c.node)}`
|
|
3118
|
+
);
|
|
3020
3119
|
}
|
|
3021
|
-
|
|
3120
|
+
}
|
|
3121
|
+
if (constantValue !== 0) {
|
|
3122
|
+
otherChildrenSigs.push(
|
|
3123
|
+
constantValue > 0 ? `+c:${constantValue}` : `-c:${-constantValue}`
|
|
3124
|
+
);
|
|
3125
|
+
}
|
|
3126
|
+
otherChildrenSigs.sort();
|
|
3127
|
+
return `add[${otherChildrenSigs.join("")}]`;
|
|
3022
3128
|
}
|
|
3023
|
-
return mainExpression;
|
|
3024
|
-
}
|
|
3025
|
-
getRootDieConfig() {
|
|
3026
|
-
const configs = this.subRollConfigs;
|
|
3027
|
-
return configs.find((config) => config.sides > 0) || configs[0];
|
|
3028
|
-
}
|
|
3029
|
-
getAllDieConfigs() {
|
|
3030
|
-
return this.getSubRollConfigs();
|
|
3031
|
-
}
|
|
3032
|
-
getBonusDiceConfigs() {
|
|
3033
|
-
const allConfigs = this.subRollConfigs;
|
|
3034
|
-
const rootConfig = allConfigs.find((config) => config.sides > 0) || allConfigs[0];
|
|
3035
|
-
if (!rootConfig) return [];
|
|
3036
|
-
return allConfigs.filter((config) => config.sides > 0).filter((config) => config !== rootConfig);
|
|
3037
|
-
}
|
|
3038
|
-
getBonusDicePMFs(check, eps = 0) {
|
|
3039
|
-
return check.getBonusDiceConfigs().map(
|
|
3040
|
-
(config) => pmfFromRollBuilder(_RollBuilder.fromConfigs([config]), eps)
|
|
3041
|
-
);
|
|
3042
|
-
}
|
|
3043
|
-
get modifier() {
|
|
3044
|
-
return this.subRollConfigs.reduce(
|
|
3045
|
-
(sum, config) => sum + config.modifier,
|
|
3046
|
-
0
|
|
3047
|
-
);
|
|
3048
|
-
}
|
|
3049
|
-
get rollType() {
|
|
3050
|
-
const rootConfig = this.getRootDieConfig();
|
|
3051
|
-
return rootConfig?.rollType || "flat";
|
|
3052
|
-
}
|
|
3053
|
-
get baseReroll() {
|
|
3054
|
-
const rootConfig = this.getRootDieConfig();
|
|
3055
|
-
return rootConfig?.reroll || 0;
|
|
3056
3129
|
}
|
|
3057
|
-
}
|
|
3130
|
+
}
|
|
3058
3131
|
|
|
3059
3132
|
// src/builder/attack.ts
|
|
3060
3133
|
var AttackBuilder = class _AttackBuilder {
|
|
@@ -3091,9 +3164,8 @@ var AttackBuilder = class _AttackBuilder {
|
|
|
3091
3164
|
let effectPart = "";
|
|
3092
3165
|
if (this.hitEffect) {
|
|
3093
3166
|
effectPart = `(${this.hitEffect.toExpression()})`;
|
|
3094
|
-
let crit = void 0;
|
|
3095
3167
|
if (this.critEffect !== null) {
|
|
3096
|
-
crit = this.critEffect ?? this.hitEffect.copy().doubleDice();
|
|
3168
|
+
const crit = this.critEffect ?? this.hitEffect.copy().doubleDice();
|
|
3097
3169
|
const critThreshold = this.check.critThreshold;
|
|
3098
3170
|
if (critThreshold < 15 || critThreshold > 20) {
|
|
3099
3171
|
throw new Error(
|
|
@@ -3181,6 +3253,9 @@ var AttackBuilder = class _AttackBuilder {
|
|
|
3181
3253
|
toPMF(eps = 0) {
|
|
3182
3254
|
return this.resolve(eps).pmf;
|
|
3183
3255
|
}
|
|
3256
|
+
get pmf() {
|
|
3257
|
+
return this.toPMF();
|
|
3258
|
+
}
|
|
3184
3259
|
// By default, create query on PMF with no pruning
|
|
3185
3260
|
toQuery(eps = 0) {
|
|
3186
3261
|
return this.toPMF(eps).query();
|
|
@@ -3254,6 +3329,10 @@ var ACBuilder = class _ACBuilder extends RollBuilder {
|
|
|
3254
3329
|
return new _ACBuilder(baseCopy, newConfig.ac, newConfig);
|
|
3255
3330
|
}
|
|
3256
3331
|
};
|
|
3332
|
+
RollBuilder.prototype.ac = function(targetAC) {
|
|
3333
|
+
if (isNaN(targetAC)) throw new Error("Invalid NaN value for targetAC");
|
|
3334
|
+
return new ACBuilder(this, targetAC);
|
|
3335
|
+
};
|
|
3257
3336
|
|
|
3258
3337
|
// src/builder/save.ts
|
|
3259
3338
|
var SaveBuilder = class _SaveBuilder {
|
|
@@ -3296,6 +3375,9 @@ var SaveBuilder = class _SaveBuilder {
|
|
|
3296
3375
|
toPMF(eps = 0) {
|
|
3297
3376
|
return this.resolve(eps).pmf;
|
|
3298
3377
|
}
|
|
3378
|
+
get pmf() {
|
|
3379
|
+
return this.toPMF();
|
|
3380
|
+
}
|
|
3299
3381
|
// By default, create query on PMF with no pruning
|
|
3300
3382
|
toQuery(eps = 0) {
|
|
3301
3383
|
return this.toPMF(eps).query();
|
|
@@ -3393,56 +3475,10 @@ var DCBuilder = class _DCBuilder extends RollBuilder {
|
|
|
3393
3475
|
return PMF.fromMap(m, eps);
|
|
3394
3476
|
}
|
|
3395
3477
|
};
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
if (sidesOrDie instanceof RollBuilder) {
|
|
3400
|
-
const subRollConfigs = sidesOrDie.getSubRollConfigs();
|
|
3401
|
-
if (subRollConfigs.length === 0) return new RollBuilder(0).plus(modifier);
|
|
3402
|
-
const absCount = Math.abs(count);
|
|
3403
|
-
const newConfigs = subRollConfigs.map((config) => ({
|
|
3404
|
-
...config,
|
|
3405
|
-
count: config.count * absCount,
|
|
3406
|
-
modifier: config.modifier * absCount
|
|
3407
|
-
}));
|
|
3408
|
-
let resultBuilder = new RollBuilder(newConfigs);
|
|
3409
|
-
if (count < 0) {
|
|
3410
|
-
const negatedConfigs = resultBuilder.getSubRollConfigs().map((c) => ({ ...c, isSubtraction: !c.isSubtraction }));
|
|
3411
|
-
resultBuilder = new RollBuilder(negatedConfigs);
|
|
3412
|
-
}
|
|
3413
|
-
return resultBuilder.plus(modifier);
|
|
3414
|
-
} else {
|
|
3415
|
-
let builder = new RollBuilder(count);
|
|
3416
|
-
if (sidesOrDie && sidesOrDie > 0) {
|
|
3417
|
-
builder = builder.d(sidesOrDie);
|
|
3418
|
-
}
|
|
3419
|
-
return builder.plus(modifier);
|
|
3420
|
-
}
|
|
3478
|
+
RollBuilder.prototype.dc = function(saveDC) {
|
|
3479
|
+
if (isNaN(saveDC)) throw new Error("Invalid NaN value for saveDC");
|
|
3480
|
+
return new DCBuilder(this).dc(saveDC);
|
|
3421
3481
|
};
|
|
3422
|
-
rollFn.d = (sides) => new RollBuilder(1).d(sides);
|
|
3423
|
-
rollFn.hd20 = () => new RollBuilder(1).d20().reroll(1);
|
|
3424
|
-
rollFn.d4 = () => new RollBuilder(1).d4();
|
|
3425
|
-
rollFn.d6 = () => new RollBuilder(1).d6();
|
|
3426
|
-
rollFn.d8 = () => new RollBuilder(1).d8();
|
|
3427
|
-
rollFn.d10 = () => new RollBuilder(1).d10();
|
|
3428
|
-
rollFn.d12 = () => new RollBuilder(1).d12();
|
|
3429
|
-
rollFn.d20 = () => new RollBuilder(1).d20();
|
|
3430
|
-
rollFn.d100 = () => new RollBuilder(1).d100();
|
|
3431
|
-
rollFn.flat = (n) => new RollBuilder(0).plus(n);
|
|
3432
|
-
function d(sides) {
|
|
3433
|
-
return new RollBuilder(1).d(sides);
|
|
3434
|
-
}
|
|
3435
|
-
var d4 = new RollBuilder(1).d4();
|
|
3436
|
-
var d6 = new RollBuilder(1).d6();
|
|
3437
|
-
var d8 = new RollBuilder(1).d8();
|
|
3438
|
-
var d10 = new RollBuilder(1).d10();
|
|
3439
|
-
var d12 = new RollBuilder(1).d12();
|
|
3440
|
-
var d20 = new RollBuilder(1).d20();
|
|
3441
|
-
var hd20 = new RollBuilder(1).d20().reroll(1);
|
|
3442
|
-
var d100 = new RollBuilder(1).d100();
|
|
3443
|
-
var flat = (n) => new RollBuilder(0).plus(n);
|
|
3444
|
-
var roll = rollFn;
|
|
3445
|
-
var builderPMFCache = new LRUCache(1e3);
|
|
3446
3482
|
|
|
3447
3483
|
exports.ACBuilder = ACBuilder;
|
|
3448
3484
|
exports.DCBuilder = DCBuilder;
|