@yipe/dice 0.2.15 → 0.2.17
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/builder/index.cjs +129 -19
- package/dist/builder/index.cjs.map +1 -1
- package/dist/builder/index.d.cts +19 -5
- package/dist/builder/index.d.ts +19 -5
- package/dist/builder/index.js +129 -19
- package/dist/builder/index.js.map +1 -1
- package/dist/index.cjs +70 -2
- 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 +70 -2
- package/dist/index.js.map +1 -1
- package/dist/{pmf-CYaEbY7m.d.cts → pmf-BC1poIqF.d.cts} +31 -0
- package/dist/{pmf-CYaEbY7m.d.ts → pmf-BC1poIqF.d.ts} +31 -0
- package/package.json +1 -1
package/dist/builder/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as PMF, C as CritConfig, i as DiceQuery, L as LRUCache } from '../pmf-
|
|
1
|
+
import { P as PMF, C as CritConfig, i as DiceQuery, L as LRUCache } from '../pmf-BC1poIqF.cjs';
|
|
2
2
|
|
|
3
3
|
type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode | HalfNode | MaxOfNode;
|
|
4
4
|
type DieNode = {
|
|
@@ -139,6 +139,7 @@ declare class RollBuilder {
|
|
|
139
139
|
scaleDice(scale: number): RollBuilder;
|
|
140
140
|
doubleDice(): RollBuilder;
|
|
141
141
|
alwaysHits(): AlwaysHitBuilder;
|
|
142
|
+
alwaysCrits(): AlwaysCritBuilder;
|
|
142
143
|
copy(): RollBuilder;
|
|
143
144
|
d4: () => RollBuilder;
|
|
144
145
|
d6: () => RollBuilder;
|
|
@@ -198,18 +199,30 @@ declare class AlwaysHitBuilder extends RollBuilder {
|
|
|
198
199
|
onHit(count: number, sides: number, modifier: number): AttackBuilder;
|
|
199
200
|
get critThreshold(): number;
|
|
200
201
|
critOn(critThreshold: number): AlwaysHitBuilder;
|
|
202
|
+
alwaysCrits(): AlwaysCritBuilder;
|
|
201
203
|
toExpression(): string;
|
|
202
204
|
toPMF(): PMF;
|
|
203
205
|
copy(): AlwaysHitBuilder;
|
|
204
206
|
}
|
|
205
207
|
declare class AlwaysCritBuilder extends RollBuilder {
|
|
206
|
-
readonly attackConfig: CritConfig
|
|
207
|
-
|
|
208
|
+
readonly attackConfig: CritConfig & {
|
|
209
|
+
ac?: number;
|
|
210
|
+
};
|
|
211
|
+
readonly fromAlwaysHit: boolean;
|
|
212
|
+
constructor(baseRoll: RollBuilder, attackConfig?: CritConfig & {
|
|
213
|
+
ac?: number;
|
|
214
|
+
}, fromAlwaysHit?: boolean);
|
|
215
|
+
onHit(val: number): AttackBuilder;
|
|
216
|
+
onHit(val: RollBuilder): AttackBuilder;
|
|
217
|
+
onHit(count: number, die: RollBuilder): AttackBuilder;
|
|
218
|
+
onHit(count: number, sides: number): AttackBuilder;
|
|
219
|
+
onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;
|
|
220
|
+
onHit(count: number, sides: number, modifier: number): AttackBuilder;
|
|
208
221
|
get critThreshold(): number;
|
|
209
|
-
critOn(critThreshold: number):
|
|
222
|
+
critOn(critThreshold: number): AlwaysCritBuilder;
|
|
210
223
|
toExpression(): string;
|
|
211
224
|
toPMF(): PMF;
|
|
212
|
-
copy():
|
|
225
|
+
copy(): AlwaysCritBuilder;
|
|
213
226
|
}
|
|
214
227
|
|
|
215
228
|
type ActionEffect = RollBuilder;
|
|
@@ -260,6 +273,7 @@ declare class ACBuilder extends RollBuilder {
|
|
|
260
273
|
onHit(count: number, sides: number, modifier: number): AttackBuilder;
|
|
261
274
|
get critThreshold(): number;
|
|
262
275
|
critOn(threshold: number): ACBuilder;
|
|
276
|
+
alwaysCrits(): AlwaysCritBuilder;
|
|
263
277
|
toExpression(): string;
|
|
264
278
|
toPMF(eps?: number): PMF;
|
|
265
279
|
copy(): ACBuilder;
|
package/dist/builder/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as PMF, C as CritConfig, i as DiceQuery, L as LRUCache } from '../pmf-
|
|
1
|
+
import { P as PMF, C as CritConfig, i as DiceQuery, L as LRUCache } from '../pmf-BC1poIqF.js';
|
|
2
2
|
|
|
3
3
|
type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode | HalfNode | MaxOfNode;
|
|
4
4
|
type DieNode = {
|
|
@@ -139,6 +139,7 @@ declare class RollBuilder {
|
|
|
139
139
|
scaleDice(scale: number): RollBuilder;
|
|
140
140
|
doubleDice(): RollBuilder;
|
|
141
141
|
alwaysHits(): AlwaysHitBuilder;
|
|
142
|
+
alwaysCrits(): AlwaysCritBuilder;
|
|
142
143
|
copy(): RollBuilder;
|
|
143
144
|
d4: () => RollBuilder;
|
|
144
145
|
d6: () => RollBuilder;
|
|
@@ -198,18 +199,30 @@ declare class AlwaysHitBuilder extends RollBuilder {
|
|
|
198
199
|
onHit(count: number, sides: number, modifier: number): AttackBuilder;
|
|
199
200
|
get critThreshold(): number;
|
|
200
201
|
critOn(critThreshold: number): AlwaysHitBuilder;
|
|
202
|
+
alwaysCrits(): AlwaysCritBuilder;
|
|
201
203
|
toExpression(): string;
|
|
202
204
|
toPMF(): PMF;
|
|
203
205
|
copy(): AlwaysHitBuilder;
|
|
204
206
|
}
|
|
205
207
|
declare class AlwaysCritBuilder extends RollBuilder {
|
|
206
|
-
readonly attackConfig: CritConfig
|
|
207
|
-
|
|
208
|
+
readonly attackConfig: CritConfig & {
|
|
209
|
+
ac?: number;
|
|
210
|
+
};
|
|
211
|
+
readonly fromAlwaysHit: boolean;
|
|
212
|
+
constructor(baseRoll: RollBuilder, attackConfig?: CritConfig & {
|
|
213
|
+
ac?: number;
|
|
214
|
+
}, fromAlwaysHit?: boolean);
|
|
215
|
+
onHit(val: number): AttackBuilder;
|
|
216
|
+
onHit(val: RollBuilder): AttackBuilder;
|
|
217
|
+
onHit(count: number, die: RollBuilder): AttackBuilder;
|
|
218
|
+
onHit(count: number, sides: number): AttackBuilder;
|
|
219
|
+
onHit(count: number, die: RollBuilder, modifier: number): AttackBuilder;
|
|
220
|
+
onHit(count: number, sides: number, modifier: number): AttackBuilder;
|
|
208
221
|
get critThreshold(): number;
|
|
209
|
-
critOn(critThreshold: number):
|
|
222
|
+
critOn(critThreshold: number): AlwaysCritBuilder;
|
|
210
223
|
toExpression(): string;
|
|
211
224
|
toPMF(): PMF;
|
|
212
|
-
copy():
|
|
225
|
+
copy(): AlwaysCritBuilder;
|
|
213
226
|
}
|
|
214
227
|
|
|
215
228
|
type ActionEffect = RollBuilder;
|
|
@@ -260,6 +273,7 @@ declare class ACBuilder extends RollBuilder {
|
|
|
260
273
|
onHit(count: number, sides: number, modifier: number): AttackBuilder;
|
|
261
274
|
get critThreshold(): number;
|
|
262
275
|
critOn(threshold: number): ACBuilder;
|
|
276
|
+
alwaysCrits(): AlwaysCritBuilder;
|
|
263
277
|
toExpression(): string;
|
|
264
278
|
toPMF(eps?: number): PMF;
|
|
265
279
|
copy(): ACBuilder;
|
package/dist/builder/index.js
CHANGED
|
@@ -52,6 +52,7 @@ var _DiceQuery = class _DiceQuery {
|
|
|
52
52
|
constructor(singles, combined, eps = EPS) {
|
|
53
53
|
__publicField(this, "singles");
|
|
54
54
|
__publicField(this, "combined");
|
|
55
|
+
__publicField(this, "_combinedWithAttr");
|
|
55
56
|
this.singles = Array.isArray(singles) ? singles : [singles];
|
|
56
57
|
if (this.singles.some((s) => s === void 0)) {
|
|
57
58
|
throw new Error("DiceQuery contains undefined singles");
|
|
@@ -59,6 +60,35 @@ var _DiceQuery = class _DiceQuery {
|
|
|
59
60
|
const c = combined ?? PMF.convolveMany(this.singles);
|
|
60
61
|
this.combined = Math.abs(c.mass() - 1) <= eps ? c : c.normalize();
|
|
61
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Returns a new PMF with damage attribution metadata populated.
|
|
65
|
+
*
|
|
66
|
+
* This method computes attribution on-demand for builder-generated PMFs,
|
|
67
|
+
* enabling them to work with damage attribution charts. The `attr` field
|
|
68
|
+
* tracks how much damage each outcome type contributes at each damage value.
|
|
69
|
+
*
|
|
70
|
+
* For each bin at damage D: sum(attr.values()) ≈ D × P(damage = D)
|
|
71
|
+
*
|
|
72
|
+
* Performance: Cached after first call. Adds minimal overhead vs `combined`.
|
|
73
|
+
*
|
|
74
|
+
* @returns PMF with attr field populated for damage attribution charts
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* const attack = d20.plus(5).ac(15).onHit(d(2,6).plus(3)).onCrit(d(2,6))
|
|
78
|
+
* const query = attack.toQuery()
|
|
79
|
+
* const pmf = query.combinedWithAttribution()
|
|
80
|
+
* // Now pmf can be used with toDamageAttributionChartSeries()
|
|
81
|
+
*/
|
|
82
|
+
combinedWithAttribution() {
|
|
83
|
+
if (this._combinedWithAttr) {
|
|
84
|
+
return this._combinedWithAttr;
|
|
85
|
+
}
|
|
86
|
+
const singlesWithAttr = this.singles.map((pmf) => pmf.withAttribution());
|
|
87
|
+
const combined = PMF.convolveMany(singlesWithAttr, this.combined.epsilon);
|
|
88
|
+
const normalized = Math.abs(combined.mass() - 1) <= this.combined.epsilon ? combined : combined.normalize();
|
|
89
|
+
this._combinedWithAttr = normalized;
|
|
90
|
+
return normalized;
|
|
91
|
+
}
|
|
62
92
|
/**
|
|
63
93
|
* Returns the expected damage across all possible outcomes.
|
|
64
94
|
*
|
|
@@ -1403,9 +1433,9 @@ var _PMF = class _PMF {
|
|
|
1403
1433
|
let totalWeight = items.reduce((s, { weight }) => s + weight, 0);
|
|
1404
1434
|
if (Math.abs(totalWeight) <= eps) totalWeight = 0;
|
|
1405
1435
|
if (Math.abs(1 - totalWeight) <= eps) totalWeight = 1;
|
|
1406
|
-
if (totalWeight > 1 +
|
|
1436
|
+
if (totalWeight > 1 + EPS) {
|
|
1407
1437
|
throw new Error(
|
|
1408
|
-
`PMF.exclusive: total weight ${totalWeight
|
|
1438
|
+
`PMF.exclusive: total weight ${totalWeight} exceeds 1. (epsilon: ${eps})`
|
|
1409
1439
|
);
|
|
1410
1440
|
}
|
|
1411
1441
|
let out = _PMF.empty(eps);
|
|
@@ -1447,6 +1477,44 @@ var _PMF = class _PMF {
|
|
|
1447
1477
|
}
|
|
1448
1478
|
return out;
|
|
1449
1479
|
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Adds damage attribution metadata to this PMF based on existing count metadata.
|
|
1482
|
+
* For each bin, sets attr[outcome] = damage × count[outcome].
|
|
1483
|
+
*
|
|
1484
|
+
* This enables damage attribution charts to work with builder-generated PMFs.
|
|
1485
|
+
* The parser generates attr automatically, but builder PMFs only have count.
|
|
1486
|
+
*
|
|
1487
|
+
* @returns New PMF with attr field populated in each bin
|
|
1488
|
+
*/
|
|
1489
|
+
withAttribution() {
|
|
1490
|
+
for (const [damage, bin] of this.map) {
|
|
1491
|
+
if (damage !== 0 && bin.attr && Object.keys(bin.attr).length > 0) {
|
|
1492
|
+
return this;
|
|
1493
|
+
}
|
|
1494
|
+
if (damage > 0) break;
|
|
1495
|
+
}
|
|
1496
|
+
const newMap = /* @__PURE__ */ new Map();
|
|
1497
|
+
for (const [damage, bin] of this.map) {
|
|
1498
|
+
const attr = {};
|
|
1499
|
+
for (const outcome in bin.count) {
|
|
1500
|
+
const probability = bin.count[outcome];
|
|
1501
|
+
if (probability > 0) {
|
|
1502
|
+
attr[outcome] = damage * probability;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
newMap.set(damage, {
|
|
1506
|
+
p: bin.p,
|
|
1507
|
+
count: { ...bin.count },
|
|
1508
|
+
attr: Object.keys(attr).length > 0 ? attr : void 0
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
return new _PMF(
|
|
1512
|
+
newMap,
|
|
1513
|
+
this.epsilon,
|
|
1514
|
+
this.normalized,
|
|
1515
|
+
`${this.identifier}~attr`
|
|
1516
|
+
);
|
|
1517
|
+
}
|
|
1450
1518
|
/**
|
|
1451
1519
|
* General-purpose N-way mixture.
|
|
1452
1520
|
* weights: Array of [weight, PMF].
|
|
@@ -2669,6 +2737,9 @@ var RollBuilder = class _RollBuilder {
|
|
|
2669
2737
|
alwaysHits() {
|
|
2670
2738
|
return new AlwaysHitBuilder(this);
|
|
2671
2739
|
}
|
|
2740
|
+
alwaysCrits() {
|
|
2741
|
+
return new AlwaysCritBuilder(this);
|
|
2742
|
+
}
|
|
2672
2743
|
copy() {
|
|
2673
2744
|
return new this.constructor(this.getSubRollConfigs());
|
|
2674
2745
|
}
|
|
@@ -2960,7 +3031,7 @@ var MaxOfRollBuilder = class _MaxOfRollBuilder extends RollBuilder {
|
|
|
2960
3031
|
child: sumChild
|
|
2961
3032
|
};
|
|
2962
3033
|
}
|
|
2963
|
-
} catch
|
|
3034
|
+
} catch {
|
|
2964
3035
|
}
|
|
2965
3036
|
throw new Error(
|
|
2966
3037
|
`MaxOfRollBuilder.toAST(): Unsupported innerRoll configuration`
|
|
@@ -2995,6 +3066,9 @@ var AlwaysHitBuilder = class _AlwaysHitBuilder extends RollBuilder {
|
|
|
2995
3066
|
const newConfig = { critThreshold };
|
|
2996
3067
|
return new _AlwaysHitBuilder(this, newConfig);
|
|
2997
3068
|
}
|
|
3069
|
+
alwaysCrits() {
|
|
3070
|
+
return new AlwaysCritBuilder(this, void 0, true);
|
|
3071
|
+
}
|
|
2998
3072
|
// Legacy expressions
|
|
2999
3073
|
toExpression() {
|
|
3000
3074
|
const configs = this.getSubRollConfigs();
|
|
@@ -3012,23 +3086,28 @@ var AlwaysHitBuilder = class _AlwaysHitBuilder extends RollBuilder {
|
|
|
3012
3086
|
return new _AlwaysHitBuilder(baseCopy, newConfig);
|
|
3013
3087
|
}
|
|
3014
3088
|
};
|
|
3015
|
-
var AlwaysCritBuilder = class extends RollBuilder {
|
|
3016
|
-
constructor(baseRoll, attackConfig) {
|
|
3089
|
+
var AlwaysCritBuilder = class _AlwaysCritBuilder extends RollBuilder {
|
|
3090
|
+
constructor(baseRoll, attackConfig, fromAlwaysHit = false) {
|
|
3017
3091
|
super(baseRoll.getSubRollConfigs());
|
|
3018
3092
|
__publicField(this, "attackConfig");
|
|
3093
|
+
__publicField(this, "fromAlwaysHit");
|
|
3019
3094
|
if (attackConfig) {
|
|
3020
3095
|
this.attackConfig = { ...attackConfig };
|
|
3021
3096
|
} else {
|
|
3022
3097
|
this.attackConfig = { critThreshold: 20 };
|
|
3023
3098
|
}
|
|
3099
|
+
this.fromAlwaysHit = fromAlwaysHit || baseRoll instanceof AlwaysHitBuilder;
|
|
3100
|
+
}
|
|
3101
|
+
onHit(...args) {
|
|
3102
|
+
const damageRoll = RollBuilder.fromArgs(...args);
|
|
3103
|
+
return new AttackBuilder(this, damageRoll);
|
|
3024
3104
|
}
|
|
3025
3105
|
get critThreshold() {
|
|
3026
3106
|
return this.attackConfig.critThreshold;
|
|
3027
3107
|
}
|
|
3028
|
-
// TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error?
|
|
3029
3108
|
critOn(critThreshold) {
|
|
3030
|
-
const newConfig = { critThreshold };
|
|
3031
|
-
return new
|
|
3109
|
+
const newConfig = { critThreshold, ac: this.attackConfig.ac };
|
|
3110
|
+
return new _AlwaysCritBuilder(this, newConfig, this.fromAlwaysHit);
|
|
3032
3111
|
}
|
|
3033
3112
|
// Legacy expressions
|
|
3034
3113
|
toExpression() {
|
|
@@ -3043,8 +3122,8 @@ var AlwaysCritBuilder = class extends RollBuilder {
|
|
|
3043
3122
|
copy() {
|
|
3044
3123
|
const baseCopy = new RollBuilder(this.getSubRollConfigs());
|
|
3045
3124
|
const critThreshold = this.critThreshold;
|
|
3046
|
-
const newConfig = { critThreshold };
|
|
3047
|
-
return new
|
|
3125
|
+
const newConfig = { critThreshold, ac: this.attackConfig.ac };
|
|
3126
|
+
return new _AlwaysCritBuilder(baseCopy, newConfig, this.fromAlwaysHit);
|
|
3048
3127
|
}
|
|
3049
3128
|
};
|
|
3050
3129
|
|
|
@@ -3580,9 +3659,9 @@ var AttackBuilder = class _AttackBuilder {
|
|
|
3580
3659
|
crit = this.hitEffect?.copy().doubleDice() ?? RollBuilder.fromArgs(0);
|
|
3581
3660
|
}
|
|
3582
3661
|
const critThreshold = this.check.critThreshold;
|
|
3583
|
-
if (critThreshold <
|
|
3662
|
+
if (critThreshold < 1 || critThreshold > 20) {
|
|
3584
3663
|
throw new Error(
|
|
3585
|
-
`Invalid crit threshold: ${critThreshold}. Must be between
|
|
3664
|
+
`Invalid crit threshold: ${critThreshold}. Must be between 1 and 20.`
|
|
3586
3665
|
);
|
|
3587
3666
|
}
|
|
3588
3667
|
if (critThreshold === 20) {
|
|
@@ -3599,13 +3678,34 @@ var AttackBuilder = class _AttackBuilder {
|
|
|
3599
3678
|
return `${checkPart} * ${effectPart}`;
|
|
3600
3679
|
}
|
|
3601
3680
|
resolveProbabilities(check, eps = 0) {
|
|
3602
|
-
if (check instanceof AlwaysCritBuilder) {
|
|
3603
|
-
return { pSuccess: 1, pHit: 0, pCrit: 1, pMiss: 0 };
|
|
3604
|
-
}
|
|
3605
3681
|
const rollType = check.rollType;
|
|
3606
3682
|
const rerollOne = check.baseReroll > 0;
|
|
3607
3683
|
const critThreshold = check.critThreshold;
|
|
3608
3684
|
const d202 = d20RollPMF(rollType, rerollOne);
|
|
3685
|
+
if (check instanceof AlwaysCritBuilder) {
|
|
3686
|
+
if (check.fromAlwaysHit) {
|
|
3687
|
+
return { pSuccess: 1, pHit: 0, pCrit: 1, pMiss: 0 };
|
|
3688
|
+
}
|
|
3689
|
+
const ac2 = check.attackConfig.ac ?? 0;
|
|
3690
|
+
const staticMod2 = this.check.modifier;
|
|
3691
|
+
const bonusDicePMFs2 = this.check.getBonusDicePMFs(this.check, eps);
|
|
3692
|
+
const bonusPMF2 = bonusDicePMFs2.length ? PMF.convolveMany(bonusDicePMFs2, eps) : PMF.delta(0, eps);
|
|
3693
|
+
let pcrit2 = 0;
|
|
3694
|
+
let pmiss2 = 0;
|
|
3695
|
+
for (const [r, rec] of d202) {
|
|
3696
|
+
const pr = typeof rec === "number" ? rec : rec.p;
|
|
3697
|
+
if (pr <= 0) continue;
|
|
3698
|
+
if (r === 1) {
|
|
3699
|
+
pmiss2 += pr;
|
|
3700
|
+
continue;
|
|
3701
|
+
}
|
|
3702
|
+
const need = ac2 - staticMod2 - r;
|
|
3703
|
+
const pBonusHit = bonusPMF2.tailProbGE(need);
|
|
3704
|
+
pcrit2 += pr * pBonusHit;
|
|
3705
|
+
pmiss2 += pr * (1 - pBonusHit);
|
|
3706
|
+
}
|
|
3707
|
+
return { pSuccess: pcrit2, pHit: 0, pCrit: pcrit2, pMiss: pmiss2 };
|
|
3708
|
+
}
|
|
3609
3709
|
if (check instanceof AlwaysHitBuilder) {
|
|
3610
3710
|
let pCrit = 0;
|
|
3611
3711
|
for (const [r, rec] of d202) {
|
|
@@ -3627,14 +3727,14 @@ var AttackBuilder = class _AttackBuilder {
|
|
|
3627
3727
|
for (const [r, rec] of d202) {
|
|
3628
3728
|
const pr = typeof rec === "number" ? rec : rec.p;
|
|
3629
3729
|
if (pr <= 0) continue;
|
|
3630
|
-
if (r >= critThreshold) {
|
|
3631
|
-
pcrit += pr;
|
|
3632
|
-
continue;
|
|
3633
|
-
}
|
|
3634
3730
|
if (r === 1) {
|
|
3635
3731
|
pmiss += pr;
|
|
3636
3732
|
continue;
|
|
3637
3733
|
}
|
|
3734
|
+
if (r >= critThreshold) {
|
|
3735
|
+
pcrit += pr;
|
|
3736
|
+
continue;
|
|
3737
|
+
}
|
|
3638
3738
|
const need = ac - staticMod - r;
|
|
3639
3739
|
const pBonusHit = bonusPMF.tailProbGE(need);
|
|
3640
3740
|
phit += pr * pBonusHit;
|
|
@@ -3715,6 +3815,16 @@ var ACBuilder = class _ACBuilder extends RollBuilder {
|
|
|
3715
3815
|
};
|
|
3716
3816
|
return new _ACBuilder(this, this.attackConfig.ac, newConfig);
|
|
3717
3817
|
}
|
|
3818
|
+
alwaysCrits() {
|
|
3819
|
+
return new AlwaysCritBuilder(
|
|
3820
|
+
this,
|
|
3821
|
+
{
|
|
3822
|
+
critThreshold: this.attackConfig.critThreshold,
|
|
3823
|
+
ac: this.attackConfig.ac
|
|
3824
|
+
},
|
|
3825
|
+
false
|
|
3826
|
+
);
|
|
3827
|
+
}
|
|
3718
3828
|
// Legacy expressions
|
|
3719
3829
|
toExpression() {
|
|
3720
3830
|
const configs = this.getSubRollConfigs();
|