@yipe/dice 0.12.0 → 0.12.1
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 +15 -11
- package/dist/builder/index.cjs +104 -35
- package/dist/builder/index.cjs.map +1 -1
- package/dist/builder/index.js +104 -35
- package/dist/builder/index.js.map +1 -1
- package/dist/index.cjs +104 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +104 -35
- package/dist/index.js.map +1 -1
- package/dist/parser/dice.d.ts +9 -2
- package/dist/parser/dice.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3187,9 +3187,23 @@ var Dice = class _Dice {
|
|
|
3187
3187
|
result.privateData.isDCCheck = true;
|
|
3188
3188
|
return result;
|
|
3189
3189
|
}
|
|
3190
|
+
/**
|
|
3191
|
+
* An attack check: a total that meets the target lands at its own value, and a miss is 0. A total
|
|
3192
|
+
* of exactly 0 that meets the target (a target of 0 or less) lands at 0 too, where the misses sit:
|
|
3193
|
+
* its count is recorded under `hit` at 0, like a payload's 0-damage hit, so the ops after the
|
|
3194
|
+
* check tell it from a miss.
|
|
3195
|
+
*/
|
|
3190
3196
|
ac(other) {
|
|
3191
3197
|
const acCheck = (a, b) => a >= b ? a : 0;
|
|
3192
|
-
|
|
3198
|
+
const result = this.checkTarget(other, acCheck);
|
|
3199
|
+
const zero = this.get(0);
|
|
3200
|
+
if (zero > 0) {
|
|
3201
|
+
let met = 0;
|
|
3202
|
+
if (typeof other === "number") met = other <= 0 ? 1 : 0;
|
|
3203
|
+
else for (const [target, count] of other.getFaceEntries()) if (target <= 0) met += count;
|
|
3204
|
+
if (met > 0) result.setOutcomeDistribution("hit", { 0: zero * met });
|
|
3205
|
+
}
|
|
3206
|
+
return result;
|
|
3193
3207
|
}
|
|
3194
3208
|
deleteFace(face) {
|
|
3195
3209
|
const result = new _Dice();
|
|
@@ -3690,6 +3704,8 @@ function parseExpression(arr, n, inCheck = false) {
|
|
|
3690
3704
|
const hitText = pending?.slice(0, pending.length - arr.length);
|
|
3691
3705
|
const termText = opText?.slice(0, opText.length - arr.length);
|
|
3692
3706
|
const before = finalResult;
|
|
3707
|
+
const attack = isAttack(finalResult);
|
|
3708
|
+
const acCheck = finalResult.privateData.isACCheck === true;
|
|
3693
3709
|
if (op === Dice.prototype.combine) assertMixable(before, arg, arr);
|
|
3694
3710
|
let crit;
|
|
3695
3711
|
let critNorm = 1;
|
|
@@ -3711,7 +3727,8 @@ function parseExpression(arr, n, inCheck = false) {
|
|
|
3711
3727
|
} else {
|
|
3712
3728
|
({ crit, rest: finalResult } = splitCrit(finalResult, count));
|
|
3713
3729
|
critNorm = crit.total();
|
|
3714
|
-
|
|
3730
|
+
const critArg = critClause ? parseBinaryArgument(arg, arr, n) : critPayload(hitText, n);
|
|
3731
|
+
crit = HIT_ONLY_OPS.has(op) ? applyToLanded(crit, op, critArg, crit.get(0), acCheck) : op.call(crit, critArg);
|
|
3715
3732
|
critNorm = crit && critNorm ? crit.total() / critNorm : 1;
|
|
3716
3733
|
}
|
|
3717
3734
|
}
|
|
@@ -3722,12 +3739,10 @@ function parseExpression(arr, n, inCheck = false) {
|
|
|
3722
3739
|
assertToken(arr, "a");
|
|
3723
3740
|
assertToken(arr, "v");
|
|
3724
3741
|
assertToken(arr, "e");
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
finalResult = finalResult.deleteFace(min);
|
|
3730
|
-
save = op.call(save, parseBinaryArgument(arg, arr, n));
|
|
3742
|
+
const { miss: missed, rest } = splitMiss(finalResult, attack);
|
|
3743
|
+
finalResult = rest;
|
|
3744
|
+
saveNorm = missed.total();
|
|
3745
|
+
save = op.call(missed, parseBinaryArgument(arg, arr, n));
|
|
3731
3746
|
saveNorm = save && saveNorm ? save.total() / saveNorm : 1;
|
|
3732
3747
|
}
|
|
3733
3748
|
let pc;
|
|
@@ -3735,12 +3750,10 @@ function parseExpression(arr, n, inCheck = false) {
|
|
|
3735
3750
|
if (arr.length >= 2 && arr[0] === "p" && arr[1] === "c") {
|
|
3736
3751
|
assertToken(arr, "p");
|
|
3737
3752
|
assertToken(arr, "c");
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
finalResult = finalResult.deleteFace(min);
|
|
3743
|
-
pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2);
|
|
3753
|
+
const { miss: missed, rest } = splitMiss(finalResult, attack);
|
|
3754
|
+
finalResult = rest;
|
|
3755
|
+
const missBefore = missed.total();
|
|
3756
|
+
pc = op.call(missed, parseBinaryArgument(arg, arr, n)).divideRoundDown(2);
|
|
3744
3757
|
const missAfter = pc ? pc.total() : 0;
|
|
3745
3758
|
pcNorm = missBefore ? missAfter / missBefore : 1;
|
|
3746
3759
|
}
|
|
@@ -3751,12 +3764,10 @@ function parseExpression(arr, n, inCheck = false) {
|
|
|
3751
3764
|
assertToken(arr, "i");
|
|
3752
3765
|
assertToken(arr, "s");
|
|
3753
3766
|
assertToken(arr, "s");
|
|
3754
|
-
miss =
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
finalResult = finalResult.deleteFace(min);
|
|
3759
|
-
miss = op.call(miss, parseBinaryArgument(arg, arr, n));
|
|
3767
|
+
const { miss: missed, rest } = splitMiss(finalResult, attack);
|
|
3768
|
+
finalResult = rest;
|
|
3769
|
+
missNorm = missed.total();
|
|
3770
|
+
miss = op.call(missed, parseBinaryArgument(arg, arr, n));
|
|
3760
3771
|
missNorm = miss && missNorm ? miss.total() / missNorm : 1;
|
|
3761
3772
|
}
|
|
3762
3773
|
let norm = finalResult.total();
|
|
@@ -3765,19 +3776,21 @@ function parseExpression(arr, n, inCheck = false) {
|
|
|
3765
3776
|
const operand = finalResult;
|
|
3766
3777
|
if (!clause && labelled && HIT_ONLY_OPS.has(op)) {
|
|
3767
3778
|
finalResult = applyByOutcome(finalResult, op, arg, termText, n);
|
|
3779
|
+
} else if (HIT_ONLY_OPS.has(op)) {
|
|
3780
|
+
finalResult = applyToLanded(finalResult, op, arg, landedAtZero(finalResult), acCheck);
|
|
3768
3781
|
} else {
|
|
3769
3782
|
finalResult = op.call(finalResult, arg);
|
|
3770
3783
|
}
|
|
3771
3784
|
if (operand.privateData.isDCCheck && HIT_ONLY_OPS.has(op)) {
|
|
3772
3785
|
finalResult.setOutcomeDistribution("saveFail", op.call(operand.deleteFace(0), arg).getFaceMap());
|
|
3773
3786
|
}
|
|
3774
|
-
if (
|
|
3787
|
+
if (attack && HIT_ONLY_OPS.has(op)) {
|
|
3775
3788
|
finalResult.privateData.attackPayload = true;
|
|
3776
|
-
const landed = landedHitsAtZero(operand, op, arg);
|
|
3789
|
+
const landed = landedHitsAtZero(operand, op, arg, acCheck);
|
|
3777
3790
|
if (landed > 0) finalResult.setOutcomeDistribution("hit", { 0: landed });
|
|
3778
|
-
} else if (op === Dice.prototype.combine && typeof arg !== "number"
|
|
3779
|
-
finalResult.privateData.attackPayload = true;
|
|
3780
|
-
const landed = operand
|
|
3791
|
+
} else if (op === Dice.prototype.combine && typeof arg !== "number") {
|
|
3792
|
+
if (arg.privateData.attackPayload) finalResult.privateData.attackPayload = true;
|
|
3793
|
+
const landed = landedAtZero(operand) + landedAtZero(arg);
|
|
3781
3794
|
if (landed > 0) finalResult.setOutcomeDistribution("hit", { 0: landed });
|
|
3782
3795
|
}
|
|
3783
3796
|
const gated = op === Dice.prototype.combine && typeof arg !== "number" && arg.privateData.isACCheck;
|
|
@@ -3867,6 +3880,34 @@ function branchesOf(value) {
|
|
|
3867
3880
|
}
|
|
3868
3881
|
var hasOutcomeLabels = (value) => typeof value !== "number" && Object.keys(value.getFullOutcomeDistribution()).some((label) => label !== "hit");
|
|
3869
3882
|
var isSave = (value) => typeof value !== "number" && value.privateData.isDCCheck === true;
|
|
3883
|
+
var isAttack = (value) => value.privateData.isDCCheck !== true && (value.privateData.isACCheck === true || value.privateData.attackPayload === true);
|
|
3884
|
+
var landedAtZero = (value) => typeof value === "number" ? 0 : value.getOutcomeCount("hit", 0);
|
|
3885
|
+
function applyToLanded(value, op, arg, landed, gate) {
|
|
3886
|
+
const lands = op === Dice.prototype.addNonZero || gate && op === Dice.prototype.conditionalApply;
|
|
3887
|
+
if (!(landed > 0) || !lands) return op.call(value, arg);
|
|
3888
|
+
const misses = value.deleteFace(0);
|
|
3889
|
+
const missed = value.get(0) - landed;
|
|
3890
|
+
if (missed > 0) misses.setFace(0, missed);
|
|
3891
|
+
const result = op.call(misses, arg);
|
|
3892
|
+
result.combineInPlace(asValue(arg).normalize(landed));
|
|
3893
|
+
return result;
|
|
3894
|
+
}
|
|
3895
|
+
function applyToAttack(value, op, arg, gate) {
|
|
3896
|
+
const result = applyToLanded(value, op, arg, landedAtZero(value), gate);
|
|
3897
|
+
result.privateData.attackPayload = true;
|
|
3898
|
+
const landed = landedHitsAtZero(value, op, arg, gate);
|
|
3899
|
+
if (landed > 0) result.setOutcomeDistribution("hit", { 0: landed });
|
|
3900
|
+
return result;
|
|
3901
|
+
}
|
|
3902
|
+
function splitMiss(check, attack) {
|
|
3903
|
+
const face = attack ? 0 : check.minFace();
|
|
3904
|
+
const landed = face === 0 ? landedAtZero(check) : 0;
|
|
3905
|
+
const miss = new Dice();
|
|
3906
|
+
miss.increment(face > 0 ? face : 1, check.get(face) - landed);
|
|
3907
|
+
const rest = check.deleteFace(face);
|
|
3908
|
+
if (landed > 0) rest.setFace(0, landed);
|
|
3909
|
+
return { miss, rest };
|
|
3910
|
+
}
|
|
3870
3911
|
function assertMixable(left, right, rest) {
|
|
3871
3912
|
if (hasOutcomeLabels(left) || hasOutcomeLabels(right)) {
|
|
3872
3913
|
throw new Error(
|
|
@@ -3901,7 +3942,7 @@ function followNaturalRoll(before, op, arg, after, clause) {
|
|
|
3901
3942
|
const rights = advantage ? lefts : gate ? [asValue(arg)] : branchesOf(arg);
|
|
3902
3943
|
data.branches = lefts.flatMap(
|
|
3903
3944
|
(left) => rights.map((right) => {
|
|
3904
|
-
const part = pairOp.call(left, right);
|
|
3945
|
+
const part = HIT_ONLY_OPS.has(pairOp) && isAttack(left) ? applyToAttack(left, pairOp, right, left.privateData.isACCheck === true) : pairOp.call(left, right);
|
|
3905
3946
|
followNaturalRoll(left, pairOp, right, part, false);
|
|
3906
3947
|
return part;
|
|
3907
3948
|
})
|
|
@@ -3923,7 +3964,10 @@ function followNaturalRoll(before, op, arg, after, clause) {
|
|
|
3923
3964
|
} else if (extremum && track?.bare && argTrack?.bare && sides === argSides) {
|
|
3924
3965
|
data.critTrack = bareTrack(after);
|
|
3925
3966
|
} else if (followed) {
|
|
3926
|
-
const
|
|
3967
|
+
const attack = HIT_ONLY_OPS.has(op) && isAttack(before);
|
|
3968
|
+
const gated = before.privateData.isACCheck === true;
|
|
3969
|
+
const call = (value, other) => attack ? applyToAttack(value, op, other, gated) : op.call(value, other);
|
|
3970
|
+
const apply = own ? (value) => call(value, arg) : (value) => call(before, value);
|
|
3927
3971
|
const step = extremum ? (slice) => keptPart(slice, apply) : apply;
|
|
3928
3972
|
data.critTrack = { sides: followed.sides, bare: false, slice: (face) => step(followed.slice(face)) };
|
|
3929
3973
|
}
|
|
@@ -3950,7 +3994,13 @@ function mixNaturalRolls(after, branches) {
|
|
|
3950
3994
|
bare: tracks.length === branches.length && tracks.every((track) => track.bare),
|
|
3951
3995
|
slice: (face) => {
|
|
3952
3996
|
const slice = new Dice();
|
|
3953
|
-
|
|
3997
|
+
let landed = 0;
|
|
3998
|
+
for (const track of tracks) {
|
|
3999
|
+
const part = track.slice(face);
|
|
4000
|
+
slice.combineInPlace(part);
|
|
4001
|
+
landed += landedAtZero(part);
|
|
4002
|
+
}
|
|
4003
|
+
if (landed > 0) slice.setOutcomeDistribution("hit", { 0: landed });
|
|
3954
4004
|
return slice;
|
|
3955
4005
|
}
|
|
3956
4006
|
};
|
|
@@ -3968,7 +4018,9 @@ function keptPart(slice, apply) {
|
|
|
3968
4018
|
function splitCrit(check, count) {
|
|
3969
4019
|
if (count === 0) {
|
|
3970
4020
|
const none = new Dice();
|
|
3971
|
-
|
|
4021
|
+
const rest2 = subtractCounts(check, none);
|
|
4022
|
+
if (landedAtZero(check) > 0) rest2.setOutcomeDistribution("hit", { 0: landedAtZero(check) });
|
|
4023
|
+
return { crit: none, rest: rest2 };
|
|
3972
4024
|
}
|
|
3973
4025
|
const track = check.privateData.critTrack;
|
|
3974
4026
|
if (!track) {
|
|
@@ -3981,13 +4033,21 @@ function splitCrit(check, count) {
|
|
|
3981
4033
|
throw new Error(`xcrit${count} is wider than the d${sides} it reads its natural roll from`);
|
|
3982
4034
|
}
|
|
3983
4035
|
let crit = new Dice();
|
|
3984
|
-
|
|
4036
|
+
let landed = 0;
|
|
4037
|
+
for (let face = sides; face > sides - count; face--) {
|
|
4038
|
+
const slice = track.slice(face);
|
|
4039
|
+
crit.combineInPlace(slice);
|
|
4040
|
+
landed += landedAtZero(slice);
|
|
4041
|
+
}
|
|
3985
4042
|
const rest = subtractCounts(check, crit);
|
|
3986
|
-
const missed = crit.get(0);
|
|
4043
|
+
const missed = crit.get(0) - landed;
|
|
3987
4044
|
if (missed) {
|
|
3988
4045
|
crit = crit.deleteFace(0);
|
|
4046
|
+
if (landed > 0) crit.setFace(0, landed);
|
|
3989
4047
|
rest.increment(0, missed);
|
|
3990
4048
|
}
|
|
4049
|
+
const restLanded = landedAtZero(check) - landed;
|
|
4050
|
+
if (restLanded > 0) rest.setOutcomeDistribution("hit", { 0: restLanded });
|
|
3991
4051
|
return { crit, rest };
|
|
3992
4052
|
}
|
|
3993
4053
|
function critPayload(text, n) {
|
|
@@ -4004,6 +4064,13 @@ function critPayload(text, n) {
|
|
|
4004
4064
|
}
|
|
4005
4065
|
return payload;
|
|
4006
4066
|
}
|
|
4067
|
+
var PAYLOAD_OUTCOMES = {
|
|
4068
|
+
crit: true,
|
|
4069
|
+
missDamage: true,
|
|
4070
|
+
pc: true,
|
|
4071
|
+
saveFail: true,
|
|
4072
|
+
saveHalf: true
|
|
4073
|
+
};
|
|
4007
4074
|
function applyByOutcome(labelled, op, arg, termText, n) {
|
|
4008
4075
|
const implicit = labelled.privateData.implicitCrit;
|
|
4009
4076
|
const argTotal = typeof arg === "number" ? 1 : arg.total();
|
|
@@ -4017,27 +4084,29 @@ function applyByOutcome(labelled, op, arg, termText, n) {
|
|
|
4017
4084
|
rest = subtractCounts(rest, part);
|
|
4018
4085
|
let applied;
|
|
4019
4086
|
if (label === "crit" && implicit && termText !== void 0) {
|
|
4020
|
-
payload = implicit.payload + termText;
|
|
4087
|
+
payload = implicit.payload + (op === Dice.prototype.addNonZero ? "~" : "") + termText;
|
|
4021
4088
|
const doubled = critPayload(payload, n);
|
|
4022
4089
|
applied = doubled.normalize(part.total() * argTotal / doubled.total());
|
|
4090
|
+
} else if (PAYLOAD_OUTCOMES[label] === true) {
|
|
4091
|
+
applied = applyToLanded(part, op, arg, part.get(0), false);
|
|
4023
4092
|
} else {
|
|
4024
4093
|
applied = op.call(part, arg);
|
|
4025
4094
|
}
|
|
4026
4095
|
result.combineInPlace(applied);
|
|
4027
4096
|
result.setOutcomeDistribution(label, applied.getFaceMap());
|
|
4028
4097
|
}
|
|
4029
|
-
result.combineInPlace(
|
|
4098
|
+
result.combineInPlace(applyToLanded(rest, op, arg, landedAtZero(labelled), false));
|
|
4030
4099
|
if (labelled.privateData.isDCCheck) result.privateData.isDCCheck = true;
|
|
4031
4100
|
if (payload !== void 0) result.privateData.implicitCrit = { payload };
|
|
4032
4101
|
return result;
|
|
4033
4102
|
}
|
|
4034
|
-
function landedHitsAtZero(operand, op, arg) {
|
|
4103
|
+
function landedHitsAtZero(operand, op, arg, gate) {
|
|
4035
4104
|
let landed = 0;
|
|
4036
4105
|
for (const [face, count] of Object.entries(operand.calculateHitDistribution())) {
|
|
4037
4106
|
if (!(count > 0)) continue;
|
|
4038
4107
|
const hit = new Dice();
|
|
4039
4108
|
hit.setFace(Number(face), count);
|
|
4040
|
-
landed +=
|
|
4109
|
+
landed += applyToLanded(hit, op, arg, Number(face) === 0 ? count : 0, gate).get(0);
|
|
4041
4110
|
}
|
|
4042
4111
|
return landed;
|
|
4043
4112
|
}
|