@yipe/dice 0.2.13 → 0.2.16

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.
@@ -2659,14 +2659,6 @@ var RollBuilder = class _RollBuilder {
2659
2659
  const scaleInt = Math.floor(scale);
2660
2660
  if (scaleInt !== scale) throw new Error("Scale must be an integer");
2661
2661
  if (scaleInt <= 0) throw new Error("Scale must be > 0");
2662
- if (scaleInt > 1) {
2663
- const configs = this.getSubRollConfigs();
2664
- if (configs.length === 1 && configs[0].keep && configs[0].count === 1) {
2665
- const config = configs[0];
2666
- const newConfig = { ...config, count: scaleInt };
2667
- return new _RollBuilder([newConfig]);
2668
- }
2669
- }
2670
2662
  const newConfigs = this.getSubRollConfigs().map((config) => {
2671
2663
  if (!config.sides || config.sides <= 0) return config;
2672
2664
  return { ...config, count: config.count * scaleInt };
@@ -2676,6 +2668,12 @@ var RollBuilder = class _RollBuilder {
2676
2668
  doubleDice() {
2677
2669
  return this.scaleDice(2);
2678
2670
  }
2671
+ alwaysHits() {
2672
+ return new AlwaysHitBuilder(this);
2673
+ }
2674
+ alwaysCrits() {
2675
+ return new AlwaysCritBuilder(this);
2676
+ }
2679
2677
  copy() {
2680
2678
  return new this.constructor(this.getSubRollConfigs());
2681
2679
  }
@@ -2939,17 +2937,6 @@ var MaxOfRollBuilder = class _MaxOfRollBuilder extends RollBuilder {
2939
2937
  }
2940
2938
  return `max${this.count}(?d?)`;
2941
2939
  }
2942
- findDieInAST(node) {
2943
- if (node.type === "die") return node;
2944
- if (node.child) return this.findDieInAST(node.child);
2945
- if (node.children) {
2946
- for (const child of node.children) {
2947
- const result = this.findDieInAST(child.node || child);
2948
- if (result) return result;
2949
- }
2950
- }
2951
- return null;
2952
- }
2953
2940
  toAST() {
2954
2941
  if (this.diceCount && this.diceSides) {
2955
2942
  const sumChild = {
@@ -2978,7 +2965,7 @@ var MaxOfRollBuilder = class _MaxOfRollBuilder extends RollBuilder {
2978
2965
  child: sumChild
2979
2966
  };
2980
2967
  }
2981
- } catch (e) {
2968
+ } catch {
2982
2969
  }
2983
2970
  throw new Error(
2984
2971
  `MaxOfRollBuilder.toAST(): Unsupported innerRoll configuration`
@@ -2991,6 +2978,88 @@ var MaxOfRollBuilder = class _MaxOfRollBuilder extends RollBuilder {
2991
2978
  return new _MaxOfRollBuilder(this.innerRoll.copy(), this.count);
2992
2979
  }
2993
2980
  };
2981
+ var AlwaysHitBuilder = class _AlwaysHitBuilder extends RollBuilder {
2982
+ constructor(baseRoll, attackConfig) {
2983
+ super(baseRoll.getSubRollConfigs());
2984
+ __publicField(this, "attackConfig");
2985
+ if (attackConfig) {
2986
+ this.attackConfig = { ...attackConfig };
2987
+ } else {
2988
+ this.attackConfig = { critThreshold: 20 };
2989
+ }
2990
+ }
2991
+ onHit(...args) {
2992
+ const damageRoll = RollBuilder.fromArgs(...args);
2993
+ return new AttackBuilder(this, damageRoll);
2994
+ }
2995
+ get critThreshold() {
2996
+ return this.attackConfig.critThreshold;
2997
+ }
2998
+ // TODO - move this to AC Builder… or if we create a DC builder that has critOn, throw an error?
2999
+ critOn(critThreshold) {
3000
+ const newConfig = { critThreshold };
3001
+ return new _AlwaysHitBuilder(this, newConfig);
3002
+ }
3003
+ alwaysCrits() {
3004
+ return new AlwaysCritBuilder(this, void 0, true);
3005
+ }
3006
+ // Legacy expressions
3007
+ toExpression() {
3008
+ const configs = this.getSubRollConfigs();
3009
+ return new RollBuilder(configs).toExpression();
3010
+ }
3011
+ toPMF() {
3012
+ const rollType = this.rollType;
3013
+ const rerollOne = this.baseReroll > 0;
3014
+ return d20RollPMF(rollType, rerollOne);
3015
+ }
3016
+ copy() {
3017
+ const baseCopy = new RollBuilder(this.getSubRollConfigs());
3018
+ const critThreshold = this.critThreshold;
3019
+ const newConfig = { critThreshold };
3020
+ return new _AlwaysHitBuilder(baseCopy, newConfig);
3021
+ }
3022
+ };
3023
+ var AlwaysCritBuilder = class _AlwaysCritBuilder extends RollBuilder {
3024
+ constructor(baseRoll, attackConfig, fromAlwaysHit = false) {
3025
+ super(baseRoll.getSubRollConfigs());
3026
+ __publicField(this, "attackConfig");
3027
+ __publicField(this, "fromAlwaysHit");
3028
+ if (attackConfig) {
3029
+ this.attackConfig = { ...attackConfig };
3030
+ } else {
3031
+ this.attackConfig = { critThreshold: 20 };
3032
+ }
3033
+ this.fromAlwaysHit = fromAlwaysHit || baseRoll instanceof AlwaysHitBuilder;
3034
+ }
3035
+ onHit(...args) {
3036
+ const damageRoll = RollBuilder.fromArgs(...args);
3037
+ return new AttackBuilder(this, damageRoll);
3038
+ }
3039
+ get critThreshold() {
3040
+ return this.attackConfig.critThreshold;
3041
+ }
3042
+ critOn(critThreshold) {
3043
+ const newConfig = { critThreshold, ac: this.attackConfig.ac };
3044
+ return new _AlwaysCritBuilder(this, newConfig, this.fromAlwaysHit);
3045
+ }
3046
+ // Legacy expressions
3047
+ toExpression() {
3048
+ const configs = this.getSubRollConfigs();
3049
+ return new RollBuilder(configs).toExpression();
3050
+ }
3051
+ toPMF() {
3052
+ const rollType = this.rollType;
3053
+ const rerollOne = this.baseReroll > 0;
3054
+ return d20RollPMF(rollType, rerollOne);
3055
+ }
3056
+ copy() {
3057
+ const baseCopy = new RollBuilder(this.getSubRollConfigs());
3058
+ const critThreshold = this.critThreshold;
3059
+ const newConfig = { critThreshold, ac: this.attackConfig.ac };
3060
+ return new _AlwaysCritBuilder(baseCopy, newConfig, this.fromAlwaysHit);
3061
+ }
3062
+ };
2994
3063
 
2995
3064
  // src/builder/factory.ts
2996
3065
  var rollFn = (count, sidesOrDie, modifier) => {
@@ -3524,9 +3593,9 @@ var AttackBuilder = class _AttackBuilder {
3524
3593
  crit = this.hitEffect?.copy().doubleDice() ?? RollBuilder.fromArgs(0);
3525
3594
  }
3526
3595
  const critThreshold = this.check.critThreshold;
3527
- if (critThreshold < 15 || critThreshold > 20) {
3596
+ if (critThreshold < 1 || critThreshold > 20) {
3528
3597
  throw new Error(
3529
- `Invalid crit threshold: ${critThreshold}. Must be between 15 and 20.`
3598
+ `Invalid crit threshold: ${critThreshold}. Must be between 1 and 20.`
3530
3599
  );
3531
3600
  }
3532
3601
  if (critThreshold === 20) {
@@ -3543,11 +3612,46 @@ var AttackBuilder = class _AttackBuilder {
3543
3612
  return `${checkPart} * ${effectPart}`;
3544
3613
  }
3545
3614
  resolveProbabilities(check, eps = 0) {
3546
- const ac = check.attackConfig.ac;
3547
3615
  const rollType = check.rollType;
3548
3616
  const rerollOne = check.baseReroll > 0;
3549
3617
  const critThreshold = check.critThreshold;
3550
3618
  const d202 = d20RollPMF(rollType, rerollOne);
3619
+ if (check instanceof AlwaysCritBuilder) {
3620
+ if (check.fromAlwaysHit) {
3621
+ return { pSuccess: 1, pHit: 0, pCrit: 1, pMiss: 0 };
3622
+ }
3623
+ const ac2 = check.attackConfig.ac ?? 0;
3624
+ const staticMod2 = this.check.modifier;
3625
+ const bonusDicePMFs2 = this.check.getBonusDicePMFs(this.check, eps);
3626
+ const bonusPMF2 = bonusDicePMFs2.length ? PMF.convolveMany(bonusDicePMFs2, eps) : PMF.delta(0, eps);
3627
+ let pcrit2 = 0;
3628
+ let pmiss2 = 0;
3629
+ for (const [r, rec] of d202) {
3630
+ const pr = typeof rec === "number" ? rec : rec.p;
3631
+ if (pr <= 0) continue;
3632
+ if (r === 1) {
3633
+ pmiss2 += pr;
3634
+ continue;
3635
+ }
3636
+ const need = ac2 - staticMod2 - r;
3637
+ const pBonusHit = bonusPMF2.tailProbGE(need);
3638
+ pcrit2 += pr * pBonusHit;
3639
+ pmiss2 += pr * (1 - pBonusHit);
3640
+ }
3641
+ return { pSuccess: pcrit2, pHit: 0, pCrit: pcrit2, pMiss: pmiss2 };
3642
+ }
3643
+ if (check instanceof AlwaysHitBuilder) {
3644
+ let pCrit = 0;
3645
+ for (const [r, rec] of d202) {
3646
+ const pr = typeof rec === "number" ? rec : rec.p;
3647
+ if (pr <= 0) continue;
3648
+ if (r >= critThreshold) pCrit += pr;
3649
+ }
3650
+ const pHit = 1 - pCrit;
3651
+ const pMiss = 0;
3652
+ return { pSuccess: 1, pHit, pCrit, pMiss };
3653
+ }
3654
+ const ac = check.attackConfig.ac;
3551
3655
  const staticMod = this.check.modifier;
3552
3656
  const bonusDicePMFs = this.check.getBonusDicePMFs(this.check, eps);
3553
3657
  const bonusPMF = bonusDicePMFs.length ? PMF.convolveMany(bonusDicePMFs, eps) : PMF.delta(0, eps);
@@ -3557,14 +3661,14 @@ var AttackBuilder = class _AttackBuilder {
3557
3661
  for (const [r, rec] of d202) {
3558
3662
  const pr = typeof rec === "number" ? rec : rec.p;
3559
3663
  if (pr <= 0) continue;
3560
- if (r >= critThreshold) {
3561
- pcrit += pr;
3562
- continue;
3563
- }
3564
3664
  if (r === 1) {
3565
3665
  pmiss += pr;
3566
3666
  continue;
3567
3667
  }
3668
+ if (r >= critThreshold) {
3669
+ pcrit += pr;
3670
+ continue;
3671
+ }
3568
3672
  const need = ac - staticMod - r;
3569
3673
  const pBonusHit = bonusPMF.tailProbGE(need);
3570
3674
  phit += pr * pBonusHit;
@@ -3645,6 +3749,16 @@ var ACBuilder = class _ACBuilder extends RollBuilder {
3645
3749
  };
3646
3750
  return new _ACBuilder(this, this.attackConfig.ac, newConfig);
3647
3751
  }
3752
+ alwaysCrits() {
3753
+ return new AlwaysCritBuilder(
3754
+ this,
3755
+ {
3756
+ critThreshold: this.attackConfig.critThreshold,
3757
+ ac: this.attackConfig.ac
3758
+ },
3759
+ false
3760
+ );
3761
+ }
3648
3762
  // Legacy expressions
3649
3763
  toExpression() {
3650
3764
  const configs = this.getSubRollConfigs();
@@ -3833,6 +3947,8 @@ RollBuilder.prototype.dc = function(saveDC) {
3833
3947
  };
3834
3948
 
3835
3949
  exports.ACBuilder = ACBuilder;
3950
+ exports.AlwaysCritBuilder = AlwaysCritBuilder;
3951
+ exports.AlwaysHitBuilder = AlwaysHitBuilder;
3836
3952
  exports.DCBuilder = DCBuilder;
3837
3953
  exports.HalfRollBuilder = HalfRollBuilder;
3838
3954
  exports.MaxOfRollBuilder = MaxOfRollBuilder;