@yipe/dice 0.2.11 → 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 +271 -30
- package/dist/builder/index.cjs.map +1 -1
- package/dist/builder/index.d.cts +22 -2
- package/dist/builder/index.d.ts +22 -2
- package/dist/builder/index.js +271 -31
- package/dist/builder/index.js.map +1 -1
- package/package.json +1 -1
package/dist/builder/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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 | HalfNode;
|
|
3
|
+
type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode | HalfNode | MaxOfNode;
|
|
4
4
|
type DieNode = {
|
|
5
5
|
type: "die";
|
|
6
6
|
sides: number;
|
|
@@ -39,6 +39,11 @@ type HalfNode = {
|
|
|
39
39
|
type: "half";
|
|
40
40
|
child: ExpressionNode;
|
|
41
41
|
};
|
|
42
|
+
type MaxOfNode = {
|
|
43
|
+
type: "maxOf";
|
|
44
|
+
count: number;
|
|
45
|
+
child: ExpressionNode;
|
|
46
|
+
};
|
|
42
47
|
|
|
43
48
|
type RollFactory = {
|
|
44
49
|
(count: number, sides?: number, modifier?: number): RollBuilder;
|
|
@@ -156,6 +161,7 @@ declare class RollBuilder {
|
|
|
156
161
|
get rollType(): RollType;
|
|
157
162
|
get baseReroll(): number;
|
|
158
163
|
half(): HalfRollBuilder;
|
|
164
|
+
maxOf(count: number): MaxOfRollBuilder;
|
|
159
165
|
}
|
|
160
166
|
declare class HalfRollBuilder extends RollBuilder {
|
|
161
167
|
private readonly innerRoll;
|
|
@@ -167,6 +173,20 @@ declare class HalfRollBuilder extends RollBuilder {
|
|
|
167
173
|
toPMF(eps?: number): PMF;
|
|
168
174
|
copy(): HalfRollBuilder;
|
|
169
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;
|
|
189
|
+
}
|
|
170
190
|
|
|
171
191
|
type ActionEffect = RollBuilder;
|
|
172
192
|
declare class AttackBuilder implements CheckBuilder {
|
|
@@ -279,4 +299,4 @@ declare const flat: (n: number) => RollBuilder;
|
|
|
279
299
|
declare const roll: RollFactory;
|
|
280
300
|
declare const builderPMFCache: LRUCache<string, PMF>;
|
|
281
301
|
|
|
282
|
-
export { ACBuilder, type AttackConfig, type AttackResolution, type CheckBuilder, DCBuilder, HalfRollBuilder, 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,6 +1,6 @@
|
|
|
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 | HalfNode;
|
|
3
|
+
type ExpressionNode = DieNode | ConstantNode | SumNode | AddNode | KeepNode | D20RollNode | HalfNode | MaxOfNode;
|
|
4
4
|
type DieNode = {
|
|
5
5
|
type: "die";
|
|
6
6
|
sides: number;
|
|
@@ -39,6 +39,11 @@ type HalfNode = {
|
|
|
39
39
|
type: "half";
|
|
40
40
|
child: ExpressionNode;
|
|
41
41
|
};
|
|
42
|
+
type MaxOfNode = {
|
|
43
|
+
type: "maxOf";
|
|
44
|
+
count: number;
|
|
45
|
+
child: ExpressionNode;
|
|
46
|
+
};
|
|
42
47
|
|
|
43
48
|
type RollFactory = {
|
|
44
49
|
(count: number, sides?: number, modifier?: number): RollBuilder;
|
|
@@ -156,6 +161,7 @@ declare class RollBuilder {
|
|
|
156
161
|
get rollType(): RollType;
|
|
157
162
|
get baseReroll(): number;
|
|
158
163
|
half(): HalfRollBuilder;
|
|
164
|
+
maxOf(count: number): MaxOfRollBuilder;
|
|
159
165
|
}
|
|
160
166
|
declare class HalfRollBuilder extends RollBuilder {
|
|
161
167
|
private readonly innerRoll;
|
|
@@ -167,6 +173,20 @@ declare class HalfRollBuilder extends RollBuilder {
|
|
|
167
173
|
toPMF(eps?: number): PMF;
|
|
168
174
|
copy(): HalfRollBuilder;
|
|
169
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;
|
|
189
|
+
}
|
|
170
190
|
|
|
171
191
|
type ActionEffect = RollBuilder;
|
|
172
192
|
declare class AttackBuilder implements CheckBuilder {
|
|
@@ -279,4 +299,4 @@ declare const flat: (n: number) => RollBuilder;
|
|
|
279
299
|
declare const roll: RollFactory;
|
|
280
300
|
declare const builderPMFCache: LRUCache<string, PMF>;
|
|
281
301
|
|
|
282
|
-
export { ACBuilder, type AttackConfig, type AttackResolution, type CheckBuilder, DCBuilder, HalfRollBuilder, 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 };
|
|
@@ -2876,6 +2884,10 @@ var RollBuilder = class _RollBuilder {
|
|
|
2876
2884
|
half() {
|
|
2877
2885
|
return new HalfRollBuilder(this);
|
|
2878
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
|
+
}
|
|
2879
2891
|
};
|
|
2880
2892
|
var HalfRollBuilder = class _HalfRollBuilder extends RollBuilder {
|
|
2881
2893
|
constructor(innerRoll) {
|
|
@@ -2905,6 +2917,78 @@ var HalfRollBuilder = class _HalfRollBuilder extends RollBuilder {
|
|
|
2905
2917
|
return new _HalfRollBuilder(this.innerRoll.copy());
|
|
2906
2918
|
}
|
|
2907
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
|
+
}
|
|
2991
|
+
};
|
|
2908
2992
|
|
|
2909
2993
|
// src/builder/factory.ts
|
|
2910
2994
|
var rollFn = (count, sidesOrDie, modifier) => {
|
|
@@ -2993,13 +3077,41 @@ function astFromRollConfigs(configs) {
|
|
|
2993
3077
|
appliedRollType = true;
|
|
2994
3078
|
}
|
|
2995
3079
|
if (cfg.rollType === "flat" && cfg.keep && cfg.keep.total > 0) {
|
|
2996
|
-
const
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
count:
|
|
3001
|
-
|
|
3002
|
-
|
|
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
|
+
}
|
|
3003
3115
|
} else {
|
|
3004
3116
|
const c = appliedRollType ? 1 : Math.max(1, count || 1);
|
|
3005
3117
|
node = { type: "sum", count: c, child: node };
|
|
@@ -3053,14 +3165,14 @@ function resolve(node, eps = defaultEps) {
|
|
|
3053
3165
|
return res;
|
|
3054
3166
|
}
|
|
3055
3167
|
case "keep": {
|
|
3056
|
-
const
|
|
3057
|
-
const
|
|
3058
|
-
|
|
3059
|
-
const
|
|
3060
|
-
|
|
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);
|
|
3061
3173
|
return keepSumPMF(
|
|
3062
|
-
|
|
3063
|
-
|
|
3174
|
+
perTrialPMF,
|
|
3175
|
+
totalTrials,
|
|
3064
3176
|
keepCount,
|
|
3065
3177
|
node.mode === "highest",
|
|
3066
3178
|
eps
|
|
@@ -3075,6 +3187,12 @@ function resolve(node, eps = defaultEps) {
|
|
|
3075
3187
|
const childPMF = resolve(node.child, eps);
|
|
3076
3188
|
return childPMF.scaleDamage(0.5, "floor");
|
|
3077
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
|
+
}
|
|
3078
3196
|
}
|
|
3079
3197
|
})();
|
|
3080
3198
|
builderPMFCache.set(cacheKey, result);
|
|
@@ -3145,6 +3263,7 @@ function findDie(node) {
|
|
|
3145
3263
|
case "sum":
|
|
3146
3264
|
case "d20Roll":
|
|
3147
3265
|
case "half":
|
|
3266
|
+
case "maxOf":
|
|
3148
3267
|
return findDie(node.child);
|
|
3149
3268
|
case "keep":
|
|
3150
3269
|
return findDie(node.child.child);
|
|
@@ -3161,6 +3280,38 @@ function getTotalCount(node) {
|
|
|
3161
3280
|
while (cur.type === "keep") cur = cur.child;
|
|
3162
3281
|
return cur.type === "sum" ? Math.max(0, Math.floor(cur.count)) : 0;
|
|
3163
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
|
+
}
|
|
3164
3315
|
function keepSumPMF(single, total, keep, highest, eps = defaultEps) {
|
|
3165
3316
|
if (keep >= total) return single.power(total, eps);
|
|
3166
3317
|
if (keep <= 0) return PMF.delta(0, eps);
|
|
@@ -3169,24 +3320,106 @@ function keepSumPMF(single, total, keep, highest, eps = defaultEps) {
|
|
|
3169
3320
|
const cacheKey = `keep|${pmfSig}|t:${total}|k:${keep}|h:${highest ? 1 : 0}|e:${eps}`;
|
|
3170
3321
|
const cached = builderPMFCache.get(cacheKey);
|
|
3171
3322
|
if (cached) return cached;
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
const
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
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;
|
|
3182
3348
|
}
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
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);
|
|
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
|
+
}
|
|
3186
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();
|
|
3187
3421
|
}
|
|
3188
|
-
|
|
3189
|
-
const result = PMF.fromMap(out, eps);
|
|
3422
|
+
const result = PMF.fromMap(dist, eps);
|
|
3190
3423
|
builderPMFCache.set(cacheKey, result);
|
|
3191
3424
|
return result;
|
|
3192
3425
|
}
|
|
@@ -3212,6 +3445,8 @@ function getASTSignature(node) {
|
|
|
3212
3445
|
)}}`;
|
|
3213
3446
|
case "half":
|
|
3214
3447
|
return `half{ch:${getASTSignature(node.child)}}`;
|
|
3448
|
+
case "maxOf":
|
|
3449
|
+
return `maxOf{c:${node.count},ch:${getASTSignature(node.child)}}`;
|
|
3215
3450
|
case "add": {
|
|
3216
3451
|
let constantValue = 0;
|
|
3217
3452
|
const otherChildrenSigs = [];
|
|
@@ -3271,7 +3506,12 @@ var AttackBuilder = class _AttackBuilder {
|
|
|
3271
3506
|
if (this.hitEffect) {
|
|
3272
3507
|
effectPart = `(${this.hitEffect.toExpression()})`;
|
|
3273
3508
|
if (this.critEffect !== null) {
|
|
3274
|
-
|
|
3509
|
+
let crit;
|
|
3510
|
+
if (this.critEffect) {
|
|
3511
|
+
crit = this.critEffect;
|
|
3512
|
+
} else {
|
|
3513
|
+
crit = this.hitEffect?.copy().doubleDice() ?? RollBuilder.fromArgs(0);
|
|
3514
|
+
}
|
|
3275
3515
|
const critThreshold = this.check.critThreshold;
|
|
3276
3516
|
if (critThreshold < 15 || critThreshold > 20) {
|
|
3277
3517
|
throw new Error(
|
|
@@ -3581,6 +3821,6 @@ RollBuilder.prototype.dc = function(saveDC) {
|
|
|
3581
3821
|
return new DCBuilder(this).dc(saveDC);
|
|
3582
3822
|
};
|
|
3583
3823
|
|
|
3584
|
-
export { ACBuilder, DCBuilder, HalfRollBuilder, 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 };
|
|
3585
3825
|
//# sourceMappingURL=index.js.map
|
|
3586
3826
|
//# sourceMappingURL=index.js.map
|