@yipe/dice 0.2.10 → 0.2.12
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 +311 -32
- package/dist/builder/index.cjs.map +1 -1
- package/dist/builder/index.d.cts +45 -10
- package/dist/builder/index.d.ts +45 -10
- package/dist/builder/index.js +310 -33
- package/dist/builder/index.js.map +1 -1
- package/package.json +1 -1
package/dist/builder/index.d.cts
CHANGED
|
@@ -1,38 +1,47 @@
|
|
|
1
1
|
import { P as PMF, i as DiceQuery, L as LRUCache } from '../pmf-D1p2Re-J.cjs';
|
|
2
2
|
|
|
3
|
-
type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode;
|
|
3
|
+
type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode | HalfNode | MaxOfNode;
|
|
4
4
|
type DieNode = {
|
|
5
|
-
type:
|
|
5
|
+
type: "die";
|
|
6
6
|
sides: number;
|
|
7
7
|
reroll?: number;
|
|
8
8
|
minimum?: number;
|
|
9
9
|
explode?: number;
|
|
10
10
|
};
|
|
11
11
|
type ConstantNode = {
|
|
12
|
-
type:
|
|
12
|
+
type: "constant";
|
|
13
13
|
value: number;
|
|
14
14
|
};
|
|
15
15
|
type SumNode = {
|
|
16
|
-
type:
|
|
16
|
+
type: "sum";
|
|
17
17
|
count: number;
|
|
18
18
|
child: ExpressionNode;
|
|
19
19
|
};
|
|
20
20
|
type AddNode = {
|
|
21
|
-
type:
|
|
21
|
+
type: "add";
|
|
22
22
|
children: {
|
|
23
23
|
node: ExpressionNode;
|
|
24
24
|
sign: 1 | -1;
|
|
25
25
|
}[];
|
|
26
26
|
};
|
|
27
27
|
type KeepNode = {
|
|
28
|
-
type:
|
|
29
|
-
mode:
|
|
28
|
+
type: "keep";
|
|
29
|
+
mode: "highest" | "lowest";
|
|
30
30
|
count: number;
|
|
31
31
|
child: SumNode | KeepNode;
|
|
32
32
|
};
|
|
33
33
|
type D20RollNode = {
|
|
34
|
-
type:
|
|
35
|
-
rollType:
|
|
34
|
+
type: "d20Roll";
|
|
35
|
+
rollType: "advantage" | "disadvantage" | "elven accuracy";
|
|
36
|
+
child: ExpressionNode;
|
|
37
|
+
};
|
|
38
|
+
type HalfNode = {
|
|
39
|
+
type: "half";
|
|
40
|
+
child: ExpressionNode;
|
|
41
|
+
};
|
|
42
|
+
type MaxOfNode = {
|
|
43
|
+
type: "maxOf";
|
|
44
|
+
count: number;
|
|
36
45
|
child: ExpressionNode;
|
|
37
46
|
};
|
|
38
47
|
|
|
@@ -151,6 +160,32 @@ declare class RollBuilder {
|
|
|
151
160
|
get modifier(): number;
|
|
152
161
|
get rollType(): RollType;
|
|
153
162
|
get baseReroll(): number;
|
|
163
|
+
half(): HalfRollBuilder;
|
|
164
|
+
maxOf(count: number): MaxOfRollBuilder;
|
|
165
|
+
}
|
|
166
|
+
declare class HalfRollBuilder extends RollBuilder {
|
|
167
|
+
private readonly innerRoll;
|
|
168
|
+
constructor(innerRoll: RollBuilder);
|
|
169
|
+
get lastConfig(): any;
|
|
170
|
+
getSubRollConfigs(): readonly RollConfig[];
|
|
171
|
+
toExpression(): string;
|
|
172
|
+
toAST(): ExpressionNode;
|
|
173
|
+
toPMF(eps?: number): PMF;
|
|
174
|
+
copy(): HalfRollBuilder;
|
|
175
|
+
}
|
|
176
|
+
declare class MaxOfRollBuilder extends RollBuilder {
|
|
177
|
+
private readonly innerRoll;
|
|
178
|
+
private readonly count;
|
|
179
|
+
private readonly diceCount?;
|
|
180
|
+
private readonly diceSides?;
|
|
181
|
+
constructor(innerRoll: RollBuilder, count: number, diceCount?: number, diceSides?: number);
|
|
182
|
+
get lastConfig(): any;
|
|
183
|
+
getSubRollConfigs(): readonly RollConfig[];
|
|
184
|
+
toExpression(): string;
|
|
185
|
+
private findDieInAST;
|
|
186
|
+
toAST(): ExpressionNode;
|
|
187
|
+
toPMF(eps?: number): PMF;
|
|
188
|
+
copy(): MaxOfRollBuilder;
|
|
154
189
|
}
|
|
155
190
|
|
|
156
191
|
type ActionEffect = RollBuilder;
|
|
@@ -264,4 +299,4 @@ declare const flat: (n: number) => RollBuilder;
|
|
|
264
299
|
declare const roll: RollFactory;
|
|
265
300
|
declare const builderPMFCache: LRUCache<string, PMF>;
|
|
266
301
|
|
|
267
|
-
export { ACBuilder, type AttackConfig, type AttackResolution, type CheckBuilder, DCBuilder, type KeepMode, type Resolution, RollBuilder, type RollConfig, type RollFactory, type RollType, SaveBuilder, type SaveOutcome, type SaveResolution, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll };
|
|
302
|
+
export { ACBuilder, type AttackConfig, type AttackResolution, type CheckBuilder, DCBuilder, HalfRollBuilder, type KeepMode, MaxOfRollBuilder, type Resolution, RollBuilder, type RollConfig, type RollFactory, type RollType, SaveBuilder, type SaveOutcome, type SaveResolution, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll };
|
package/dist/builder/index.d.ts
CHANGED
|
@@ -1,38 +1,47 @@
|
|
|
1
1
|
import { P as PMF, i as DiceQuery, L as LRUCache } from '../pmf-D1p2Re-J.js';
|
|
2
2
|
|
|
3
|
-
type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode;
|
|
3
|
+
type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode | HalfNode | MaxOfNode;
|
|
4
4
|
type DieNode = {
|
|
5
|
-
type:
|
|
5
|
+
type: "die";
|
|
6
6
|
sides: number;
|
|
7
7
|
reroll?: number;
|
|
8
8
|
minimum?: number;
|
|
9
9
|
explode?: number;
|
|
10
10
|
};
|
|
11
11
|
type ConstantNode = {
|
|
12
|
-
type:
|
|
12
|
+
type: "constant";
|
|
13
13
|
value: number;
|
|
14
14
|
};
|
|
15
15
|
type SumNode = {
|
|
16
|
-
type:
|
|
16
|
+
type: "sum";
|
|
17
17
|
count: number;
|
|
18
18
|
child: ExpressionNode;
|
|
19
19
|
};
|
|
20
20
|
type AddNode = {
|
|
21
|
-
type:
|
|
21
|
+
type: "add";
|
|
22
22
|
children: {
|
|
23
23
|
node: ExpressionNode;
|
|
24
24
|
sign: 1 | -1;
|
|
25
25
|
}[];
|
|
26
26
|
};
|
|
27
27
|
type KeepNode = {
|
|
28
|
-
type:
|
|
29
|
-
mode:
|
|
28
|
+
type: "keep";
|
|
29
|
+
mode: "highest" | "lowest";
|
|
30
30
|
count: number;
|
|
31
31
|
child: SumNode | KeepNode;
|
|
32
32
|
};
|
|
33
33
|
type D20RollNode = {
|
|
34
|
-
type:
|
|
35
|
-
rollType:
|
|
34
|
+
type: "d20Roll";
|
|
35
|
+
rollType: "advantage" | "disadvantage" | "elven accuracy";
|
|
36
|
+
child: ExpressionNode;
|
|
37
|
+
};
|
|
38
|
+
type HalfNode = {
|
|
39
|
+
type: "half";
|
|
40
|
+
child: ExpressionNode;
|
|
41
|
+
};
|
|
42
|
+
type MaxOfNode = {
|
|
43
|
+
type: "maxOf";
|
|
44
|
+
count: number;
|
|
36
45
|
child: ExpressionNode;
|
|
37
46
|
};
|
|
38
47
|
|
|
@@ -151,6 +160,32 @@ declare class RollBuilder {
|
|
|
151
160
|
get modifier(): number;
|
|
152
161
|
get rollType(): RollType;
|
|
153
162
|
get baseReroll(): number;
|
|
163
|
+
half(): HalfRollBuilder;
|
|
164
|
+
maxOf(count: number): MaxOfRollBuilder;
|
|
165
|
+
}
|
|
166
|
+
declare class HalfRollBuilder extends RollBuilder {
|
|
167
|
+
private readonly innerRoll;
|
|
168
|
+
constructor(innerRoll: RollBuilder);
|
|
169
|
+
get lastConfig(): any;
|
|
170
|
+
getSubRollConfigs(): readonly RollConfig[];
|
|
171
|
+
toExpression(): string;
|
|
172
|
+
toAST(): ExpressionNode;
|
|
173
|
+
toPMF(eps?: number): PMF;
|
|
174
|
+
copy(): HalfRollBuilder;
|
|
175
|
+
}
|
|
176
|
+
declare class MaxOfRollBuilder extends RollBuilder {
|
|
177
|
+
private readonly innerRoll;
|
|
178
|
+
private readonly count;
|
|
179
|
+
private readonly diceCount?;
|
|
180
|
+
private readonly diceSides?;
|
|
181
|
+
constructor(innerRoll: RollBuilder, count: number, diceCount?: number, diceSides?: number);
|
|
182
|
+
get lastConfig(): any;
|
|
183
|
+
getSubRollConfigs(): readonly RollConfig[];
|
|
184
|
+
toExpression(): string;
|
|
185
|
+
private findDieInAST;
|
|
186
|
+
toAST(): ExpressionNode;
|
|
187
|
+
toPMF(eps?: number): PMF;
|
|
188
|
+
copy(): MaxOfRollBuilder;
|
|
154
189
|
}
|
|
155
190
|
|
|
156
191
|
type ActionEffect = RollBuilder;
|
|
@@ -264,4 +299,4 @@ declare const flat: (n: number) => RollBuilder;
|
|
|
264
299
|
declare const roll: RollFactory;
|
|
265
300
|
declare const builderPMFCache: LRUCache<string, PMF>;
|
|
266
301
|
|
|
267
|
-
export { ACBuilder, type AttackConfig, type AttackResolution, type CheckBuilder, DCBuilder, type KeepMode, type Resolution, RollBuilder, type RollConfig, type RollFactory, type RollType, SaveBuilder, type SaveOutcome, type SaveResolution, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll };
|
|
302
|
+
export { ACBuilder, type AttackConfig, type AttackResolution, type CheckBuilder, DCBuilder, HalfRollBuilder, type KeepMode, MaxOfRollBuilder, type Resolution, RollBuilder, type RollConfig, type RollFactory, type RollType, SaveBuilder, type SaveOutcome, type SaveResolution, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll };
|
package/dist/builder/index.js
CHANGED
|
@@ -2657,6 +2657,14 @@ var RollBuilder = class _RollBuilder {
|
|
|
2657
2657
|
const scaleInt = Math.floor(scale);
|
|
2658
2658
|
if (scaleInt !== scale) throw new Error("Scale must be an integer");
|
|
2659
2659
|
if (scaleInt <= 0) throw new Error("Scale must be > 0");
|
|
2660
|
+
if (scaleInt > 1) {
|
|
2661
|
+
const configs = this.getSubRollConfigs();
|
|
2662
|
+
if (configs.length === 1 && configs[0].keep && configs[0].count === 1) {
|
|
2663
|
+
const config = configs[0];
|
|
2664
|
+
const newConfig = { ...config, count: scaleInt };
|
|
2665
|
+
return new _RollBuilder([newConfig]);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2660
2668
|
const newConfigs = this.getSubRollConfigs().map((config) => {
|
|
2661
2669
|
if (!config.sides || config.sides <= 0) return config;
|
|
2662
2670
|
return { ...config, count: config.count * scaleInt };
|
|
@@ -2873,6 +2881,113 @@ var RollBuilder = class _RollBuilder {
|
|
|
2873
2881
|
const rootConfig = this.getRootDieConfig();
|
|
2874
2882
|
return rootConfig?.reroll || 0;
|
|
2875
2883
|
}
|
|
2884
|
+
half() {
|
|
2885
|
+
return new HalfRollBuilder(this);
|
|
2886
|
+
}
|
|
2887
|
+
// Create a "max of N rolls" version of this roll for crit damage with keep operations
|
|
2888
|
+
maxOf(count) {
|
|
2889
|
+
return new MaxOfRollBuilder(this, count);
|
|
2890
|
+
}
|
|
2891
|
+
};
|
|
2892
|
+
var HalfRollBuilder = class _HalfRollBuilder extends RollBuilder {
|
|
2893
|
+
constructor(innerRoll) {
|
|
2894
|
+
super(0);
|
|
2895
|
+
this.innerRoll = innerRoll;
|
|
2896
|
+
}
|
|
2897
|
+
get lastConfig() {
|
|
2898
|
+
return this.innerRoll.lastConfig;
|
|
2899
|
+
}
|
|
2900
|
+
getSubRollConfigs() {
|
|
2901
|
+
return this.innerRoll.getSubRollConfigs();
|
|
2902
|
+
}
|
|
2903
|
+
toExpression() {
|
|
2904
|
+
const innerExpression = this.innerRoll.toExpression();
|
|
2905
|
+
return `(${innerExpression}) // 2`;
|
|
2906
|
+
}
|
|
2907
|
+
toAST() {
|
|
2908
|
+
return {
|
|
2909
|
+
type: "half",
|
|
2910
|
+
child: this.innerRoll.toAST()
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
toPMF(eps = 0) {
|
|
2914
|
+
return pmfFromRollBuilder(this, eps);
|
|
2915
|
+
}
|
|
2916
|
+
copy() {
|
|
2917
|
+
return new _HalfRollBuilder(this.innerRoll.copy());
|
|
2918
|
+
}
|
|
2919
|
+
};
|
|
2920
|
+
var MaxOfRollBuilder = class _MaxOfRollBuilder extends RollBuilder {
|
|
2921
|
+
constructor(innerRoll, count, diceCount, diceSides) {
|
|
2922
|
+
super(0);
|
|
2923
|
+
this.innerRoll = innerRoll;
|
|
2924
|
+
this.count = count;
|
|
2925
|
+
this.diceCount = diceCount;
|
|
2926
|
+
this.diceSides = diceSides;
|
|
2927
|
+
}
|
|
2928
|
+
get lastConfig() {
|
|
2929
|
+
return this.innerRoll.lastConfig;
|
|
2930
|
+
}
|
|
2931
|
+
getSubRollConfigs() {
|
|
2932
|
+
return this.innerRoll.getSubRollConfigs();
|
|
2933
|
+
}
|
|
2934
|
+
toExpression() {
|
|
2935
|
+
if (this.diceCount && this.diceSides) {
|
|
2936
|
+
return `max${this.count}(${this.diceCount}d${this.diceSides})`;
|
|
2937
|
+
}
|
|
2938
|
+
return `max${this.count}(?d?)`;
|
|
2939
|
+
}
|
|
2940
|
+
findDieInAST(node) {
|
|
2941
|
+
if (node.type === "die") return node;
|
|
2942
|
+
if (node.child) return this.findDieInAST(node.child);
|
|
2943
|
+
if (node.children) {
|
|
2944
|
+
for (const child of node.children) {
|
|
2945
|
+
const result = this.findDieInAST(child.node || child);
|
|
2946
|
+
if (result) return result;
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
return null;
|
|
2950
|
+
}
|
|
2951
|
+
toAST() {
|
|
2952
|
+
if (this.diceCount && this.diceSides) {
|
|
2953
|
+
const sumChild = {
|
|
2954
|
+
type: "sum",
|
|
2955
|
+
count: this.diceCount,
|
|
2956
|
+
child: { type: "die", sides: this.diceSides }
|
|
2957
|
+
};
|
|
2958
|
+
return {
|
|
2959
|
+
type: "maxOf",
|
|
2960
|
+
count: this.count,
|
|
2961
|
+
child: sumChild
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
try {
|
|
2965
|
+
const configs = this.innerRoll.getSubRollConfigs();
|
|
2966
|
+
if (configs.length === 1 && configs[0].sides) {
|
|
2967
|
+
const config = configs[0];
|
|
2968
|
+
const sumChild = {
|
|
2969
|
+
type: "sum",
|
|
2970
|
+
count: config.count,
|
|
2971
|
+
child: { type: "die", sides: config.sides }
|
|
2972
|
+
};
|
|
2973
|
+
return {
|
|
2974
|
+
type: "maxOf",
|
|
2975
|
+
count: this.count,
|
|
2976
|
+
child: sumChild
|
|
2977
|
+
};
|
|
2978
|
+
}
|
|
2979
|
+
} catch (e) {
|
|
2980
|
+
}
|
|
2981
|
+
throw new Error(
|
|
2982
|
+
`MaxOfRollBuilder.toAST(): Unsupported innerRoll configuration`
|
|
2983
|
+
);
|
|
2984
|
+
}
|
|
2985
|
+
toPMF(eps = 0) {
|
|
2986
|
+
return pmfFromRollBuilder(this, eps);
|
|
2987
|
+
}
|
|
2988
|
+
copy() {
|
|
2989
|
+
return new _MaxOfRollBuilder(this.innerRoll.copy(), this.count);
|
|
2990
|
+
}
|
|
2876
2991
|
};
|
|
2877
2992
|
|
|
2878
2993
|
// src/builder/factory.ts
|
|
@@ -2962,13 +3077,41 @@ function astFromRollConfigs(configs) {
|
|
|
2962
3077
|
appliedRollType = true;
|
|
2963
3078
|
}
|
|
2964
3079
|
if (cfg.rollType === "flat" && cfg.keep && cfg.keep.total > 0) {
|
|
2965
|
-
const
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
count:
|
|
2970
|
-
|
|
2971
|
-
|
|
3080
|
+
const baseCount = Math.max(1, Math.floor(Math.abs(count || 1)));
|
|
3081
|
+
const trials = Math.max(1, Math.floor(cfg.keep.total));
|
|
3082
|
+
const k = Math.max(0, Math.floor(cfg.keep.count));
|
|
3083
|
+
if (trials === baseCount) {
|
|
3084
|
+
const base = { type: "sum", count: trials, child: node };
|
|
3085
|
+
node = {
|
|
3086
|
+
type: "keep",
|
|
3087
|
+
mode: cfg.keep.mode,
|
|
3088
|
+
count: k,
|
|
3089
|
+
child: base
|
|
3090
|
+
};
|
|
3091
|
+
} else {
|
|
3092
|
+
const perTrial = {
|
|
3093
|
+
type: "sum",
|
|
3094
|
+
count: baseCount,
|
|
3095
|
+
child: node
|
|
3096
|
+
};
|
|
3097
|
+
if (trials === 1) {
|
|
3098
|
+
node = perTrial;
|
|
3099
|
+
} else if (k === 1 && cfg.keep.mode === "highest") {
|
|
3100
|
+
node = { type: "maxOf", count: trials, child: perTrial };
|
|
3101
|
+
} else {
|
|
3102
|
+
const trialPool = {
|
|
3103
|
+
type: "sum",
|
|
3104
|
+
count: trials,
|
|
3105
|
+
child: perTrial
|
|
3106
|
+
};
|
|
3107
|
+
node = {
|
|
3108
|
+
type: "keep",
|
|
3109
|
+
mode: cfg.keep.mode,
|
|
3110
|
+
count: k,
|
|
3111
|
+
child: trialPool
|
|
3112
|
+
};
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
2972
3115
|
} else {
|
|
2973
3116
|
const c = appliedRollType ? 1 : Math.max(1, count || 1);
|
|
2974
3117
|
node = { type: "sum", count: c, child: node };
|
|
@@ -3022,14 +3165,14 @@ function resolve(node, eps = defaultEps) {
|
|
|
3022
3165
|
return res;
|
|
3023
3166
|
}
|
|
3024
3167
|
case "keep": {
|
|
3025
|
-
const
|
|
3026
|
-
const
|
|
3027
|
-
|
|
3028
|
-
const
|
|
3029
|
-
|
|
3168
|
+
const totalTrials = getTotalCount(node);
|
|
3169
|
+
const keepCount = Math.max(0, Math.min(node.count, totalTrials));
|
|
3170
|
+
if (keepCount === 0 || totalTrials === 0) return PMF.delta(0, eps);
|
|
3171
|
+
const perTrialNode = node.child.child;
|
|
3172
|
+
const perTrialPMF = resolve(perTrialNode, eps);
|
|
3030
3173
|
return keepSumPMF(
|
|
3031
|
-
|
|
3032
|
-
|
|
3174
|
+
perTrialPMF,
|
|
3175
|
+
totalTrials,
|
|
3033
3176
|
keepCount,
|
|
3034
3177
|
node.mode === "highest",
|
|
3035
3178
|
eps
|
|
@@ -3040,14 +3183,23 @@ function resolve(node, eps = defaultEps) {
|
|
|
3040
3183
|
const rerollOne = !!childDie && (childDie.reroll || 0) >= 1;
|
|
3041
3184
|
return d20RollPMF(node.rollType, rerollOne);
|
|
3042
3185
|
}
|
|
3186
|
+
case "half": {
|
|
3187
|
+
const childPMF = resolve(node.child, eps);
|
|
3188
|
+
return childPMF.scaleDamage(0.5, "floor");
|
|
3189
|
+
}
|
|
3190
|
+
case "maxOf": {
|
|
3191
|
+
const childPMF = resolve(node.child, eps);
|
|
3192
|
+
const count = Math.max(1, Math.floor(node.count));
|
|
3193
|
+
if (count === 1) return childPMF;
|
|
3194
|
+
return computeMaxOfPMF(childPMF, count, eps);
|
|
3195
|
+
}
|
|
3043
3196
|
}
|
|
3044
3197
|
})();
|
|
3045
3198
|
builderPMFCache.set(cacheKey, result);
|
|
3046
3199
|
return result;
|
|
3047
3200
|
}
|
|
3048
3201
|
function pmfFromRollBuilder(rb, eps = defaultEps) {
|
|
3049
|
-
const
|
|
3050
|
-
const ast = astFromRollConfigs(configs) || { type: "constant", value: 0 };
|
|
3202
|
+
const ast = rb.toAST();
|
|
3051
3203
|
return resolve(ast, eps);
|
|
3052
3204
|
}
|
|
3053
3205
|
function resolveSingleDie(die, eps = defaultEps) {
|
|
@@ -3110,6 +3262,8 @@ function findDie(node) {
|
|
|
3110
3262
|
return void 0;
|
|
3111
3263
|
case "sum":
|
|
3112
3264
|
case "d20Roll":
|
|
3265
|
+
case "half":
|
|
3266
|
+
case "maxOf":
|
|
3113
3267
|
return findDie(node.child);
|
|
3114
3268
|
case "keep":
|
|
3115
3269
|
return findDie(node.child.child);
|
|
@@ -3126,6 +3280,38 @@ function getTotalCount(node) {
|
|
|
3126
3280
|
while (cur.type === "keep") cur = cur.child;
|
|
3127
3281
|
return cur.type === "sum" ? Math.max(0, Math.floor(cur.count)) : 0;
|
|
3128
3282
|
}
|
|
3283
|
+
function computeMaxOfPMF(pmf, count, eps = defaultEps) {
|
|
3284
|
+
if (count <= 1) return pmf;
|
|
3285
|
+
const support = pmf.support();
|
|
3286
|
+
const out = /* @__PURE__ */ new Map();
|
|
3287
|
+
if (count <= 6 && support.length <= 20) {
|
|
3288
|
+
let dfs = function(rollsLeft, currentMax, probability) {
|
|
3289
|
+
if (rollsLeft === 0) {
|
|
3290
|
+
out.set(currentMax, (out.get(currentMax) || 0) + probability);
|
|
3291
|
+
return;
|
|
3292
|
+
}
|
|
3293
|
+
for (const value of support) {
|
|
3294
|
+
const p = pmf.pAt(value);
|
|
3295
|
+
if (p > 0) {
|
|
3296
|
+
const newMax = Math.max(currentMax, value);
|
|
3297
|
+
dfs(rollsLeft - 1, newMax, probability * p);
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
};
|
|
3301
|
+
dfs(count, -Infinity, 1);
|
|
3302
|
+
} else {
|
|
3303
|
+
const sortedSupport = [...support].sort((a, b) => a - b);
|
|
3304
|
+
for (const value of sortedSupport) {
|
|
3305
|
+
const cdfAtValue = pmf.cdfAt(value);
|
|
3306
|
+
const cdfAtValueMinus1 = value > sortedSupport[0] ? pmf.cdfAt(value - 1) : 0;
|
|
3307
|
+
const probMax = Math.pow(cdfAtValue, count) - Math.pow(cdfAtValueMinus1, count);
|
|
3308
|
+
if (probMax > eps) {
|
|
3309
|
+
out.set(value, probMax);
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
return PMF.fromMap(out, eps);
|
|
3314
|
+
}
|
|
3129
3315
|
function keepSumPMF(single, total, keep, highest, eps = defaultEps) {
|
|
3130
3316
|
if (keep >= total) return single.power(total, eps);
|
|
3131
3317
|
if (keep <= 0) return PMF.delta(0, eps);
|
|
@@ -3134,24 +3320,106 @@ function keepSumPMF(single, total, keep, highest, eps = defaultEps) {
|
|
|
3134
3320
|
const cacheKey = `keep|${pmfSig}|t:${total}|k:${keep}|h:${highest ? 1 : 0}|e:${eps}`;
|
|
3135
3321
|
const cached = builderPMFCache.get(cacheKey);
|
|
3136
3322
|
if (cached) return cached;
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
const
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3323
|
+
if (keep === 1) {
|
|
3324
|
+
if (highest) {
|
|
3325
|
+
return computeMaxOfPMF(single, total, eps);
|
|
3326
|
+
} else {
|
|
3327
|
+
const neg = single.mapDamage((v) => -v);
|
|
3328
|
+
const minPMF = computeMaxOfPMF(neg, total, eps).mapDamage((v) => -v);
|
|
3329
|
+
builderPMFCache.set(cacheKey, minPMF);
|
|
3330
|
+
return minPMF;
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
let state = /* @__PURE__ */ new Map();
|
|
3334
|
+
const keyOf = (used, r) => `${used}|${r}`;
|
|
3335
|
+
state.set(keyOf(0, total), /* @__PURE__ */ new Map([[0, 1]]));
|
|
3336
|
+
const valuesDesc = highest ? [...sortedSupport].sort((a, b) => b - a) : [...sortedSupport].sort((a, b) => a - b);
|
|
3337
|
+
const binomPMF = (r, p) => {
|
|
3338
|
+
if (r <= 0) return [1];
|
|
3339
|
+
if (p <= eps) {
|
|
3340
|
+
const arr2 = new Array(r + 1).fill(0);
|
|
3341
|
+
arr2[0] = 1;
|
|
3342
|
+
return arr2;
|
|
3343
|
+
}
|
|
3344
|
+
if (1 - p <= eps) {
|
|
3345
|
+
const arr2 = new Array(r + 1).fill(0);
|
|
3346
|
+
arr2[r] = 1;
|
|
3347
|
+
return arr2;
|
|
3147
3348
|
}
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3349
|
+
const q = 1 - p;
|
|
3350
|
+
const arr = new Array(r + 1).fill(0);
|
|
3351
|
+
arr[0] = Math.pow(q, r);
|
|
3352
|
+
const ratio = p / q;
|
|
3353
|
+
for (let x = 1; x <= r; x++)
|
|
3354
|
+
arr[x] = arr[x - 1] * (r - x + 1) / x * ratio;
|
|
3355
|
+
let s = 0;
|
|
3356
|
+
for (let x = 0; x <= r; x++) s += arr[x];
|
|
3357
|
+
if (Math.abs(1 - s) > 1e-12) for (let x = 0; x <= r; x++) arr[x] /= s;
|
|
3358
|
+
return arr;
|
|
3359
|
+
};
|
|
3360
|
+
const pruneMap = (m, threshold) => {
|
|
3361
|
+
if (threshold <= 0) return m;
|
|
3362
|
+
const out = /* @__PURE__ */ new Map();
|
|
3363
|
+
for (const [sum, pr] of m) if (pr >= threshold) out.set(sum, pr);
|
|
3364
|
+
return out.size === m.size ? m : out;
|
|
3365
|
+
};
|
|
3366
|
+
const pruneState = (st, threshold) => {
|
|
3367
|
+
if (threshold <= 0) return st;
|
|
3368
|
+
const out = /* @__PURE__ */ new Map();
|
|
3369
|
+
for (const [k, m] of st) {
|
|
3370
|
+
const mm = pruneMap(m, threshold);
|
|
3371
|
+
if (mm.size > 0) out.set(k, mm);
|
|
3151
3372
|
}
|
|
3373
|
+
return out;
|
|
3374
|
+
};
|
|
3375
|
+
let processedMass = 0;
|
|
3376
|
+
for (const v of valuesDesc) {
|
|
3377
|
+
const p = single.pAt(v);
|
|
3378
|
+
if (p <= 0) continue;
|
|
3379
|
+
const q = Math.max(eps, 1 - processedMass);
|
|
3380
|
+
const pCond = Math.min(1, p / q);
|
|
3381
|
+
const next = /* @__PURE__ */ new Map();
|
|
3382
|
+
for (const [k, m] of state) {
|
|
3383
|
+
const [usedStr, rStr] = k.split("|");
|
|
3384
|
+
const used = parseInt(usedStr, 10);
|
|
3385
|
+
const r = parseInt(rStr, 10);
|
|
3386
|
+
if (r === 0) {
|
|
3387
|
+
const destKey = keyOf(used, 0);
|
|
3388
|
+
const dest = next.get(destKey) ?? /* @__PURE__ */ new Map();
|
|
3389
|
+
for (const [sum, pr] of m) dest.set(sum, (dest.get(sum) || 0) + pr);
|
|
3390
|
+
next.set(destKey, dest);
|
|
3391
|
+
continue;
|
|
3392
|
+
}
|
|
3393
|
+
const bin = binomPMF(r, pCond);
|
|
3394
|
+
const remainingCapacity = keep - used;
|
|
3395
|
+
for (let x = 0; x <= r; x++) {
|
|
3396
|
+
const px = bin[x];
|
|
3397
|
+
if (px <= eps) continue;
|
|
3398
|
+
const t = Math.min(x, remainingCapacity);
|
|
3399
|
+
const used2 = used + t;
|
|
3400
|
+
const r2 = r - x;
|
|
3401
|
+
const add = t * v;
|
|
3402
|
+
const destKey = keyOf(used2, r2);
|
|
3403
|
+
const dest = next.get(destKey) ?? /* @__PURE__ */ new Map();
|
|
3404
|
+
for (const [sum, pr] of m) {
|
|
3405
|
+
const s2 = sum + add;
|
|
3406
|
+
const prob = pr * px;
|
|
3407
|
+
const cur = dest.get(s2) || 0;
|
|
3408
|
+
const nv = cur + prob;
|
|
3409
|
+
if (nv >= eps) dest.set(s2, nv);
|
|
3410
|
+
}
|
|
3411
|
+
if (dest.size > 0) next.set(destKey, dest);
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
state = pruneState(next, eps * 1e-6);
|
|
3415
|
+
processedMass += p;
|
|
3416
|
+
}
|
|
3417
|
+
const finalKey = keyOf(keep, 0);
|
|
3418
|
+
const dist = state.get(finalKey) ?? /* @__PURE__ */ new Map();
|
|
3419
|
+
if (dist.size === 0) {
|
|
3420
|
+
return PMF.emptyMass();
|
|
3152
3421
|
}
|
|
3153
|
-
|
|
3154
|
-
const result = PMF.fromMap(out, eps);
|
|
3422
|
+
const result = PMF.fromMap(dist, eps);
|
|
3155
3423
|
builderPMFCache.set(cacheKey, result);
|
|
3156
3424
|
return result;
|
|
3157
3425
|
}
|
|
@@ -3175,6 +3443,10 @@ function getASTSignature(node) {
|
|
|
3175
3443
|
return `keep{c:${node.count},m:${node.mode},ch:${getASTSignature(
|
|
3176
3444
|
node.child
|
|
3177
3445
|
)}}`;
|
|
3446
|
+
case "half":
|
|
3447
|
+
return `half{ch:${getASTSignature(node.child)}}`;
|
|
3448
|
+
case "maxOf":
|
|
3449
|
+
return `maxOf{c:${node.count},ch:${getASTSignature(node.child)}}`;
|
|
3178
3450
|
case "add": {
|
|
3179
3451
|
let constantValue = 0;
|
|
3180
3452
|
const otherChildrenSigs = [];
|
|
@@ -3234,7 +3506,12 @@ var AttackBuilder = class _AttackBuilder {
|
|
|
3234
3506
|
if (this.hitEffect) {
|
|
3235
3507
|
effectPart = `(${this.hitEffect.toExpression()})`;
|
|
3236
3508
|
if (this.critEffect !== null) {
|
|
3237
|
-
|
|
3509
|
+
let crit;
|
|
3510
|
+
if (this.critEffect) {
|
|
3511
|
+
crit = this.critEffect;
|
|
3512
|
+
} else {
|
|
3513
|
+
crit = this.hitEffect?.copy().doubleDice() ?? RollBuilder.fromArgs(0);
|
|
3514
|
+
}
|
|
3238
3515
|
const critThreshold = this.check.critThreshold;
|
|
3239
3516
|
if (critThreshold < 15 || critThreshold > 20) {
|
|
3240
3517
|
throw new Error(
|
|
@@ -3544,6 +3821,6 @@ RollBuilder.prototype.dc = function(saveDC) {
|
|
|
3544
3821
|
return new DCBuilder(this).dc(saveDC);
|
|
3545
3822
|
};
|
|
3546
3823
|
|
|
3547
|
-
export { ACBuilder, DCBuilder, RollBuilder, SaveBuilder, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll };
|
|
3824
|
+
export { ACBuilder, DCBuilder, HalfRollBuilder, MaxOfRollBuilder, RollBuilder, SaveBuilder, builderPMFCache, d, d10, d100, d12, d20, d4, d6, d8, defaultConfig, flat, hd20, roll };
|
|
3548
3825
|
//# sourceMappingURL=index.js.map
|
|
3549
3826
|
//# sourceMappingURL=index.js.map
|