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