@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/builder/index.js
CHANGED
|
@@ -3375,9 +3375,23 @@ var Dice = class _Dice {
|
|
|
3375
3375
|
result.privateData.isDCCheck = true;
|
|
3376
3376
|
return result;
|
|
3377
3377
|
}
|
|
3378
|
+
/**
|
|
3379
|
+
* An attack check: a total that meets the target lands at its own value, and a miss is 0. A total
|
|
3380
|
+
* of exactly 0 that meets the target (a target of 0 or less) lands at 0 too, where the misses sit:
|
|
3381
|
+
* its count is recorded under `hit` at 0, like a payload's 0-damage hit, so the ops after the
|
|
3382
|
+
* check tell it from a miss.
|
|
3383
|
+
*/
|
|
3378
3384
|
ac(other) {
|
|
3379
3385
|
const acCheck = (a, b) => a >= b ? a : 0;
|
|
3380
|
-
|
|
3386
|
+
const result = this.checkTarget(other, acCheck);
|
|
3387
|
+
const zero = this.get(0);
|
|
3388
|
+
if (zero > 0) {
|
|
3389
|
+
let met = 0;
|
|
3390
|
+
if (typeof other === "number") met = other <= 0 ? 1 : 0;
|
|
3391
|
+
else for (const [target, count] of other.getFaceEntries()) if (target <= 0) met += count;
|
|
3392
|
+
if (met > 0) result.setOutcomeDistribution("hit", { 0: zero * met });
|
|
3393
|
+
}
|
|
3394
|
+
return result;
|
|
3381
3395
|
}
|
|
3382
3396
|
deleteFace(face) {
|
|
3383
3397
|
const result = new _Dice();
|
|
@@ -3655,6 +3669,8 @@ function parseExpression(arr, n, inCheck = false) {
|
|
|
3655
3669
|
const hitText = pending?.slice(0, pending.length - arr.length);
|
|
3656
3670
|
const termText = opText?.slice(0, opText.length - arr.length);
|
|
3657
3671
|
const before = finalResult;
|
|
3672
|
+
const attack = isAttack(finalResult);
|
|
3673
|
+
const acCheck = finalResult.privateData.isACCheck === true;
|
|
3658
3674
|
if (op === Dice.prototype.combine) assertMixable(before, arg, arr);
|
|
3659
3675
|
let crit;
|
|
3660
3676
|
let critNorm = 1;
|
|
@@ -3676,7 +3692,8 @@ function parseExpression(arr, n, inCheck = false) {
|
|
|
3676
3692
|
} else {
|
|
3677
3693
|
({ crit, rest: finalResult } = splitCrit(finalResult, count));
|
|
3678
3694
|
critNorm = crit.total();
|
|
3679
|
-
|
|
3695
|
+
const critArg = critClause ? parseBinaryArgument(arg, arr, n) : critPayload(hitText, n);
|
|
3696
|
+
crit = HIT_ONLY_OPS.has(op) ? applyToLanded(crit, op, critArg, crit.get(0), acCheck) : op.call(crit, critArg);
|
|
3680
3697
|
critNorm = crit && critNorm ? crit.total() / critNorm : 1;
|
|
3681
3698
|
}
|
|
3682
3699
|
}
|
|
@@ -3687,12 +3704,10 @@ function parseExpression(arr, n, inCheck = false) {
|
|
|
3687
3704
|
assertToken(arr, "a");
|
|
3688
3705
|
assertToken(arr, "v");
|
|
3689
3706
|
assertToken(arr, "e");
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
finalResult = finalResult.deleteFace(min);
|
|
3695
|
-
save = op.call(save, parseBinaryArgument(arg, arr, n));
|
|
3707
|
+
const { miss: missed, rest } = splitMiss(finalResult, attack);
|
|
3708
|
+
finalResult = rest;
|
|
3709
|
+
saveNorm = missed.total();
|
|
3710
|
+
save = op.call(missed, parseBinaryArgument(arg, arr, n));
|
|
3696
3711
|
saveNorm = save && saveNorm ? save.total() / saveNorm : 1;
|
|
3697
3712
|
}
|
|
3698
3713
|
let pc;
|
|
@@ -3700,12 +3715,10 @@ function parseExpression(arr, n, inCheck = false) {
|
|
|
3700
3715
|
if (arr.length >= 2 && arr[0] === "p" && arr[1] === "c") {
|
|
3701
3716
|
assertToken(arr, "p");
|
|
3702
3717
|
assertToken(arr, "c");
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
finalResult = finalResult.deleteFace(min);
|
|
3708
|
-
pc = op.call(pc, parseBinaryArgument(arg, arr, n)).divideRoundDown(2);
|
|
3718
|
+
const { miss: missed, rest } = splitMiss(finalResult, attack);
|
|
3719
|
+
finalResult = rest;
|
|
3720
|
+
const missBefore = missed.total();
|
|
3721
|
+
pc = op.call(missed, parseBinaryArgument(arg, arr, n)).divideRoundDown(2);
|
|
3709
3722
|
const missAfter = pc ? pc.total() : 0;
|
|
3710
3723
|
pcNorm = missBefore ? missAfter / missBefore : 1;
|
|
3711
3724
|
}
|
|
@@ -3716,12 +3729,10 @@ function parseExpression(arr, n, inCheck = false) {
|
|
|
3716
3729
|
assertToken(arr, "i");
|
|
3717
3730
|
assertToken(arr, "s");
|
|
3718
3731
|
assertToken(arr, "s");
|
|
3719
|
-
miss =
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
finalResult = finalResult.deleteFace(min);
|
|
3724
|
-
miss = op.call(miss, parseBinaryArgument(arg, arr, n));
|
|
3732
|
+
const { miss: missed, rest } = splitMiss(finalResult, attack);
|
|
3733
|
+
finalResult = rest;
|
|
3734
|
+
missNorm = missed.total();
|
|
3735
|
+
miss = op.call(missed, parseBinaryArgument(arg, arr, n));
|
|
3725
3736
|
missNorm = miss && missNorm ? miss.total() / missNorm : 1;
|
|
3726
3737
|
}
|
|
3727
3738
|
let norm = finalResult.total();
|
|
@@ -3730,19 +3741,21 @@ function parseExpression(arr, n, inCheck = false) {
|
|
|
3730
3741
|
const operand = finalResult;
|
|
3731
3742
|
if (!clause && labelled && HIT_ONLY_OPS.has(op)) {
|
|
3732
3743
|
finalResult = applyByOutcome(finalResult, op, arg, termText, n);
|
|
3744
|
+
} else if (HIT_ONLY_OPS.has(op)) {
|
|
3745
|
+
finalResult = applyToLanded(finalResult, op, arg, landedAtZero(finalResult), acCheck);
|
|
3733
3746
|
} else {
|
|
3734
3747
|
finalResult = op.call(finalResult, arg);
|
|
3735
3748
|
}
|
|
3736
3749
|
if (operand.privateData.isDCCheck && HIT_ONLY_OPS.has(op)) {
|
|
3737
3750
|
finalResult.setOutcomeDistribution("saveFail", op.call(operand.deleteFace(0), arg).getFaceMap());
|
|
3738
3751
|
}
|
|
3739
|
-
if (
|
|
3752
|
+
if (attack && HIT_ONLY_OPS.has(op)) {
|
|
3740
3753
|
finalResult.privateData.attackPayload = true;
|
|
3741
|
-
const landed = landedHitsAtZero(operand, op, arg);
|
|
3754
|
+
const landed = landedHitsAtZero(operand, op, arg, acCheck);
|
|
3742
3755
|
if (landed > 0) finalResult.setOutcomeDistribution("hit", { 0: landed });
|
|
3743
|
-
} else if (op === Dice.prototype.combine && typeof arg !== "number"
|
|
3744
|
-
finalResult.privateData.attackPayload = true;
|
|
3745
|
-
const landed = operand
|
|
3756
|
+
} else if (op === Dice.prototype.combine && typeof arg !== "number") {
|
|
3757
|
+
if (arg.privateData.attackPayload) finalResult.privateData.attackPayload = true;
|
|
3758
|
+
const landed = landedAtZero(operand) + landedAtZero(arg);
|
|
3746
3759
|
if (landed > 0) finalResult.setOutcomeDistribution("hit", { 0: landed });
|
|
3747
3760
|
}
|
|
3748
3761
|
const gated = op === Dice.prototype.combine && typeof arg !== "number" && arg.privateData.isACCheck;
|
|
@@ -3832,6 +3845,34 @@ function branchesOf(value) {
|
|
|
3832
3845
|
}
|
|
3833
3846
|
var hasOutcomeLabels = (value) => typeof value !== "number" && Object.keys(value.getFullOutcomeDistribution()).some((label) => label !== "hit");
|
|
3834
3847
|
var isSave = (value) => typeof value !== "number" && value.privateData.isDCCheck === true;
|
|
3848
|
+
var isAttack = (value) => value.privateData.isDCCheck !== true && (value.privateData.isACCheck === true || value.privateData.attackPayload === true);
|
|
3849
|
+
var landedAtZero = (value) => typeof value === "number" ? 0 : value.getOutcomeCount("hit", 0);
|
|
3850
|
+
function applyToLanded(value, op, arg, landed, gate) {
|
|
3851
|
+
const lands = op === Dice.prototype.addNonZero || gate && op === Dice.prototype.conditionalApply;
|
|
3852
|
+
if (!(landed > 0) || !lands) return op.call(value, arg);
|
|
3853
|
+
const misses = value.deleteFace(0);
|
|
3854
|
+
const missed = value.get(0) - landed;
|
|
3855
|
+
if (missed > 0) misses.setFace(0, missed);
|
|
3856
|
+
const result = op.call(misses, arg);
|
|
3857
|
+
result.combineInPlace(asValue(arg).normalize(landed));
|
|
3858
|
+
return result;
|
|
3859
|
+
}
|
|
3860
|
+
function applyToAttack(value, op, arg, gate) {
|
|
3861
|
+
const result = applyToLanded(value, op, arg, landedAtZero(value), gate);
|
|
3862
|
+
result.privateData.attackPayload = true;
|
|
3863
|
+
const landed = landedHitsAtZero(value, op, arg, gate);
|
|
3864
|
+
if (landed > 0) result.setOutcomeDistribution("hit", { 0: landed });
|
|
3865
|
+
return result;
|
|
3866
|
+
}
|
|
3867
|
+
function splitMiss(check, attack) {
|
|
3868
|
+
const face = attack ? 0 : check.minFace();
|
|
3869
|
+
const landed = face === 0 ? landedAtZero(check) : 0;
|
|
3870
|
+
const miss = new Dice();
|
|
3871
|
+
miss.increment(face > 0 ? face : 1, check.get(face) - landed);
|
|
3872
|
+
const rest = check.deleteFace(face);
|
|
3873
|
+
if (landed > 0) rest.setFace(0, landed);
|
|
3874
|
+
return { miss, rest };
|
|
3875
|
+
}
|
|
3835
3876
|
function assertMixable(left, right, rest) {
|
|
3836
3877
|
if (hasOutcomeLabels(left) || hasOutcomeLabels(right)) {
|
|
3837
3878
|
throw new Error(
|
|
@@ -3866,7 +3907,7 @@ function followNaturalRoll(before, op, arg, after, clause) {
|
|
|
3866
3907
|
const rights = advantage2 ? lefts : gate ? [asValue(arg)] : branchesOf(arg);
|
|
3867
3908
|
data.branches = lefts.flatMap(
|
|
3868
3909
|
(left) => rights.map((right) => {
|
|
3869
|
-
const part = pairOp.call(left, right);
|
|
3910
|
+
const part = HIT_ONLY_OPS.has(pairOp) && isAttack(left) ? applyToAttack(left, pairOp, right, left.privateData.isACCheck === true) : pairOp.call(left, right);
|
|
3870
3911
|
followNaturalRoll(left, pairOp, right, part, false);
|
|
3871
3912
|
return part;
|
|
3872
3913
|
})
|
|
@@ -3888,7 +3929,10 @@ function followNaturalRoll(before, op, arg, after, clause) {
|
|
|
3888
3929
|
} else if (extremum && track?.bare && argTrack?.bare && sides === argSides) {
|
|
3889
3930
|
data.critTrack = bareTrack(after);
|
|
3890
3931
|
} else if (followed) {
|
|
3891
|
-
const
|
|
3932
|
+
const attack = HIT_ONLY_OPS.has(op) && isAttack(before);
|
|
3933
|
+
const gated = before.privateData.isACCheck === true;
|
|
3934
|
+
const call = (value, other) => attack ? applyToAttack(value, op, other, gated) : op.call(value, other);
|
|
3935
|
+
const apply = own ? (value) => call(value, arg) : (value) => call(before, value);
|
|
3892
3936
|
const step = extremum ? (slice) => keptPart(slice, apply) : apply;
|
|
3893
3937
|
data.critTrack = { sides: followed.sides, bare: false, slice: (face) => step(followed.slice(face)) };
|
|
3894
3938
|
}
|
|
@@ -3915,7 +3959,13 @@ function mixNaturalRolls(after, branches) {
|
|
|
3915
3959
|
bare: tracks.length === branches.length && tracks.every((track) => track.bare),
|
|
3916
3960
|
slice: (face) => {
|
|
3917
3961
|
const slice = new Dice();
|
|
3918
|
-
|
|
3962
|
+
let landed = 0;
|
|
3963
|
+
for (const track of tracks) {
|
|
3964
|
+
const part = track.slice(face);
|
|
3965
|
+
slice.combineInPlace(part);
|
|
3966
|
+
landed += landedAtZero(part);
|
|
3967
|
+
}
|
|
3968
|
+
if (landed > 0) slice.setOutcomeDistribution("hit", { 0: landed });
|
|
3919
3969
|
return slice;
|
|
3920
3970
|
}
|
|
3921
3971
|
};
|
|
@@ -3933,7 +3983,9 @@ function keptPart(slice, apply) {
|
|
|
3933
3983
|
function splitCrit(check, count) {
|
|
3934
3984
|
if (count === 0) {
|
|
3935
3985
|
const none = new Dice();
|
|
3936
|
-
|
|
3986
|
+
const rest2 = subtractCounts(check, none);
|
|
3987
|
+
if (landedAtZero(check) > 0) rest2.setOutcomeDistribution("hit", { 0: landedAtZero(check) });
|
|
3988
|
+
return { crit: none, rest: rest2 };
|
|
3937
3989
|
}
|
|
3938
3990
|
const track = check.privateData.critTrack;
|
|
3939
3991
|
if (!track) {
|
|
@@ -3946,13 +3998,21 @@ function splitCrit(check, count) {
|
|
|
3946
3998
|
throw new Error(`xcrit${count} is wider than the d${sides} it reads its natural roll from`);
|
|
3947
3999
|
}
|
|
3948
4000
|
let crit = new Dice();
|
|
3949
|
-
|
|
4001
|
+
let landed = 0;
|
|
4002
|
+
for (let face = sides; face > sides - count; face--) {
|
|
4003
|
+
const slice = track.slice(face);
|
|
4004
|
+
crit.combineInPlace(slice);
|
|
4005
|
+
landed += landedAtZero(slice);
|
|
4006
|
+
}
|
|
3950
4007
|
const rest = subtractCounts(check, crit);
|
|
3951
|
-
const missed = crit.get(0);
|
|
4008
|
+
const missed = crit.get(0) - landed;
|
|
3952
4009
|
if (missed) {
|
|
3953
4010
|
crit = crit.deleteFace(0);
|
|
4011
|
+
if (landed > 0) crit.setFace(0, landed);
|
|
3954
4012
|
rest.increment(0, missed);
|
|
3955
4013
|
}
|
|
4014
|
+
const restLanded = landedAtZero(check) - landed;
|
|
4015
|
+
if (restLanded > 0) rest.setOutcomeDistribution("hit", { 0: restLanded });
|
|
3956
4016
|
return { crit, rest };
|
|
3957
4017
|
}
|
|
3958
4018
|
function critPayload(text, n) {
|
|
@@ -3969,6 +4029,13 @@ function critPayload(text, n) {
|
|
|
3969
4029
|
}
|
|
3970
4030
|
return payload;
|
|
3971
4031
|
}
|
|
4032
|
+
var PAYLOAD_OUTCOMES = {
|
|
4033
|
+
crit: true,
|
|
4034
|
+
missDamage: true,
|
|
4035
|
+
pc: true,
|
|
4036
|
+
saveFail: true,
|
|
4037
|
+
saveHalf: true
|
|
4038
|
+
};
|
|
3972
4039
|
function applyByOutcome(labelled, op, arg, termText, n) {
|
|
3973
4040
|
const implicit = labelled.privateData.implicitCrit;
|
|
3974
4041
|
const argTotal = typeof arg === "number" ? 1 : arg.total();
|
|
@@ -3982,27 +4049,29 @@ function applyByOutcome(labelled, op, arg, termText, n) {
|
|
|
3982
4049
|
rest = subtractCounts(rest, part);
|
|
3983
4050
|
let applied;
|
|
3984
4051
|
if (label === "crit" && implicit && termText !== void 0) {
|
|
3985
|
-
payload = implicit.payload + termText;
|
|
4052
|
+
payload = implicit.payload + (op === Dice.prototype.addNonZero ? "~" : "") + termText;
|
|
3986
4053
|
const doubled = critPayload(payload, n);
|
|
3987
4054
|
applied = doubled.normalize(part.total() * argTotal / doubled.total());
|
|
4055
|
+
} else if (PAYLOAD_OUTCOMES[label] === true) {
|
|
4056
|
+
applied = applyToLanded(part, op, arg, part.get(0), false);
|
|
3988
4057
|
} else {
|
|
3989
4058
|
applied = op.call(part, arg);
|
|
3990
4059
|
}
|
|
3991
4060
|
result.combineInPlace(applied);
|
|
3992
4061
|
result.setOutcomeDistribution(label, applied.getFaceMap());
|
|
3993
4062
|
}
|
|
3994
|
-
result.combineInPlace(
|
|
4063
|
+
result.combineInPlace(applyToLanded(rest, op, arg, landedAtZero(labelled), false));
|
|
3995
4064
|
if (labelled.privateData.isDCCheck) result.privateData.isDCCheck = true;
|
|
3996
4065
|
if (payload !== void 0) result.privateData.implicitCrit = { payload };
|
|
3997
4066
|
return result;
|
|
3998
4067
|
}
|
|
3999
|
-
function landedHitsAtZero(operand, op, arg) {
|
|
4068
|
+
function landedHitsAtZero(operand, op, arg, gate) {
|
|
4000
4069
|
let landed = 0;
|
|
4001
4070
|
for (const [face, count] of Object.entries(operand.calculateHitDistribution())) {
|
|
4002
4071
|
if (!(count > 0)) continue;
|
|
4003
4072
|
const hit = new Dice();
|
|
4004
4073
|
hit.setFace(Number(face), count);
|
|
4005
|
-
landed +=
|
|
4074
|
+
landed += applyToLanded(hit, op, arg, Number(face) === 0 ? count : 0, gate).get(0);
|
|
4006
4075
|
}
|
|
4007
4076
|
return landed;
|
|
4008
4077
|
}
|