@bct-app/game-engine 0.1.6-beta.2 → 0.1.6-beta.3
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/index.js +91 -13
- package/dist/index.mjs +90 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ __export(index_exports, {
|
|
|
24
24
|
adjustValueAsNumberArray: () => adjustValueAsNumberArray,
|
|
25
25
|
applyOperationToPlayers: () => applyOperationToPlayers,
|
|
26
26
|
buildPlayerSeatMap: () => buildPlayerSeatMap,
|
|
27
|
+
canInvokeAbility: () => canInvokeAbility,
|
|
27
28
|
copyPlayers: () => copyPlayers,
|
|
28
29
|
getSourceValue: () => getSourceValue,
|
|
29
30
|
getValueFromPayloads: () => getValueFromPayloads,
|
|
@@ -159,8 +160,7 @@ var resolveSourceValue = (source, writableInputs, payloads, effector, snapshotSe
|
|
|
159
160
|
}
|
|
160
161
|
const characterIds = typeof payloadValue === "string" ? [payloadValue] : payloadValue;
|
|
161
162
|
return characterIds.flatMap((id) => {
|
|
162
|
-
|
|
163
|
-
return resolved !== void 0 ? [resolved] : [];
|
|
163
|
+
return resolveFromCharacter ? resolveFromCharacter(id) : [];
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
166
|
return castOrValidate(payloadValue);
|
|
@@ -393,8 +393,6 @@ var applyPerceivedCharacterChange = createEffectHandler("PERCEIVED_CHARACTER_CHA
|
|
|
393
393
|
var applyReminderAdd = createEffectHandler("REMINDER_ADD", ({
|
|
394
394
|
effect,
|
|
395
395
|
payloads,
|
|
396
|
-
nowTimelineIndex,
|
|
397
|
-
operationInTimelineIdx,
|
|
398
396
|
makePlayersEffect
|
|
399
397
|
}) => {
|
|
400
398
|
const maybeReminder = getSourceValue(effect.source, payloads);
|
|
@@ -402,10 +400,6 @@ var applyReminderAdd = createEffectHandler("REMINDER_ADD", ({
|
|
|
402
400
|
console.warn("Invalid reminder data for REMINDER_ADD effect:", maybeReminder);
|
|
403
401
|
return;
|
|
404
402
|
}
|
|
405
|
-
const timelineDistance = nowTimelineIndex - operationInTimelineIdx;
|
|
406
|
-
if (typeof maybeReminder.duration === "number" && maybeReminder.duration > 0 && timelineDistance >= maybeReminder.duration) {
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
403
|
makePlayersEffect.forEach((player) => {
|
|
410
404
|
if (!player.reminders) {
|
|
411
405
|
player.reminders = [];
|
|
@@ -675,10 +669,16 @@ var resolveEffectTargets = ({
|
|
|
675
669
|
};
|
|
676
670
|
|
|
677
671
|
// src/effects/evaluate-lifetime.ts
|
|
678
|
-
var getLifetime = (effect) => {
|
|
679
|
-
if ("lifetime" in effect) {
|
|
672
|
+
var getLifetime = (effect, payloads) => {
|
|
673
|
+
if ("lifetime" in effect && effect.lifetime) {
|
|
680
674
|
return effect.lifetime;
|
|
681
675
|
}
|
|
676
|
+
if (effect.type === "REMINDER_ADD") {
|
|
677
|
+
const maybeReminder = getSourceValue(effect.source, payloads);
|
|
678
|
+
if (isReminder(maybeReminder) && typeof maybeReminder.duration === "number" && maybeReminder.duration > 0) {
|
|
679
|
+
return { kind: "PHASES", count: maybeReminder.duration };
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
682
|
return void 0;
|
|
683
683
|
};
|
|
684
684
|
var matchesLifetimeCondition = (player, condition, characterMap) => {
|
|
@@ -723,9 +723,10 @@ var evaluateLifetime = ({
|
|
|
723
723
|
effector,
|
|
724
724
|
targets,
|
|
725
725
|
snapshotSeatMap,
|
|
726
|
-
characterMap
|
|
726
|
+
characterMap,
|
|
727
|
+
payloads
|
|
727
728
|
}) => {
|
|
728
|
-
const lifetime = getLifetime(effect);
|
|
729
|
+
const lifetime = getLifetime(effect, payloads);
|
|
729
730
|
if (!lifetime || lifetime.kind === "PERMANENT") {
|
|
730
731
|
return targets;
|
|
731
732
|
}
|
|
@@ -737,6 +738,10 @@ var evaluateLifetime = ({
|
|
|
737
738
|
}
|
|
738
739
|
return nowTurn - opTurn < lifetime.count ? targets : [];
|
|
739
740
|
}
|
|
741
|
+
if (lifetime.kind === "PHASES") {
|
|
742
|
+
if (operationTimelineIdx < 0) return targets;
|
|
743
|
+
return nowTimelineIndex - operationTimelineIdx < lifetime.count ? targets : [];
|
|
744
|
+
}
|
|
740
745
|
if (lifetime.kind === "UNTIL_EVENT") {
|
|
741
746
|
if (operationTimelineIdx < 0) return targets;
|
|
742
747
|
for (let idx = operationTimelineIdx + 1; idx <= nowTimelineIndex; idx += 1) {
|
|
@@ -844,7 +849,8 @@ var applyOperationToPlayers = ({
|
|
|
844
849
|
effector,
|
|
845
850
|
targets: resolvedTargets,
|
|
846
851
|
snapshotSeatMap: lifetimeSnapshotSeatMap,
|
|
847
|
-
characterMap
|
|
852
|
+
characterMap,
|
|
853
|
+
payloads
|
|
848
854
|
});
|
|
849
855
|
const handler = effectHandlers[effect.type];
|
|
850
856
|
if (!handler) {
|
|
@@ -922,12 +928,84 @@ var applyOperationToPlayers = ({
|
|
|
922
928
|
const final = runReplay(groundTruthSeatMap);
|
|
923
929
|
return transformEmptyArray(final);
|
|
924
930
|
};
|
|
931
|
+
|
|
932
|
+
// src/can-invoke-ability.ts
|
|
933
|
+
var checkTurns = (turns, currentTurn) => {
|
|
934
|
+
if (turns.only && turns.only.length > 0 && !turns.only.includes(currentTurn)) {
|
|
935
|
+
return `turn ${currentTurn} not in allowed turns ${JSON.stringify(turns.only)}`;
|
|
936
|
+
}
|
|
937
|
+
if (typeof turns.from === "number" && currentTurn < turns.from) {
|
|
938
|
+
return `turn ${currentTurn} is before allowed start turn ${turns.from}`;
|
|
939
|
+
}
|
|
940
|
+
if (typeof turns.to === "number" && currentTurn > turns.to) {
|
|
941
|
+
return `turn ${currentTurn} is after allowed end turn ${turns.to}`;
|
|
942
|
+
}
|
|
943
|
+
return null;
|
|
944
|
+
};
|
|
945
|
+
var countMatchingPriorOps = (priorOperations, abilityId, effectorSeat, operationTimelineMap, matchTurn, matchTime, timelines) => {
|
|
946
|
+
return priorOperations.filter((op) => {
|
|
947
|
+
if (op.abilityId !== abilityId) return false;
|
|
948
|
+
if (typeof effectorSeat === "number" && op.effector !== effectorSeat) return false;
|
|
949
|
+
if (matchTurn === null && matchTime === null) return true;
|
|
950
|
+
const idx = operationTimelineMap.get(op);
|
|
951
|
+
if (typeof idx !== "number") return false;
|
|
952
|
+
const tl = timelines[idx];
|
|
953
|
+
if (!tl) return false;
|
|
954
|
+
if (matchTurn !== null && tl.turn !== matchTurn) return false;
|
|
955
|
+
if (matchTime !== null && tl.time !== matchTime) return false;
|
|
956
|
+
return true;
|
|
957
|
+
}).length;
|
|
958
|
+
};
|
|
959
|
+
var canInvokeAbility = ({
|
|
960
|
+
ability,
|
|
961
|
+
effector,
|
|
962
|
+
currentTimelineIdx,
|
|
963
|
+
timelines,
|
|
964
|
+
priorOperations = []
|
|
965
|
+
}) => {
|
|
966
|
+
const trigger = ability.triggerWindow;
|
|
967
|
+
if (!trigger) return { allowed: true };
|
|
968
|
+
const currentTimeline = timelines[currentTimelineIdx];
|
|
969
|
+
if (!currentTimeline) {
|
|
970
|
+
return { allowed: false, reason: `no timeline at index ${currentTimelineIdx}` };
|
|
971
|
+
}
|
|
972
|
+
if (trigger.turns) {
|
|
973
|
+
const turnError = checkTurns(trigger.turns, currentTimeline.turn);
|
|
974
|
+
if (turnError) return { allowed: false, reason: turnError };
|
|
975
|
+
}
|
|
976
|
+
if (trigger.phases && trigger.phases.length > 0 && !trigger.phases.includes(currentTimeline.time)) {
|
|
977
|
+
return { allowed: false, reason: `phase ${currentTimeline.time} not in allowed phases ${JSON.stringify(trigger.phases)}` };
|
|
978
|
+
}
|
|
979
|
+
if (trigger.requireEffectorAlive && (!effector || effector.isDead)) {
|
|
980
|
+
return { allowed: false, reason: "effector is not alive" };
|
|
981
|
+
}
|
|
982
|
+
if (trigger.frequency && priorOperations.length > 0) {
|
|
983
|
+
const operationTimelineMap = /* @__PURE__ */ new Map();
|
|
984
|
+
timelines.forEach((tl, idx) => tl.operations?.forEach((op) => operationTimelineMap.set(op, idx)));
|
|
985
|
+
const matchTurn = trigger.frequency === "ONCE_PER_GAME" ? null : currentTimeline.turn;
|
|
986
|
+
const matchTime = trigger.frequency === "ONCE_PER_GAME" ? null : trigger.frequency === "ONCE_PER_NIGHT" ? "night" : "day";
|
|
987
|
+
const count = countMatchingPriorOps(
|
|
988
|
+
priorOperations,
|
|
989
|
+
ability.id,
|
|
990
|
+
effector?.seat,
|
|
991
|
+
operationTimelineMap,
|
|
992
|
+
matchTurn,
|
|
993
|
+
matchTime,
|
|
994
|
+
timelines
|
|
995
|
+
);
|
|
996
|
+
if (count > 0) {
|
|
997
|
+
return { allowed: false, reason: `frequency ${trigger.frequency} already exhausted (${count} prior op${count === 1 ? "" : "s"})` };
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return { allowed: true };
|
|
1001
|
+
};
|
|
925
1002
|
// Annotate the CommonJS export names for ESM import in node:
|
|
926
1003
|
0 && (module.exports = {
|
|
927
1004
|
adjustValueAsNumber,
|
|
928
1005
|
adjustValueAsNumberArray,
|
|
929
1006
|
applyOperationToPlayers,
|
|
930
1007
|
buildPlayerSeatMap,
|
|
1008
|
+
canInvokeAbility,
|
|
931
1009
|
copyPlayers,
|
|
932
1010
|
getSourceValue,
|
|
933
1011
|
getValueFromPayloads,
|
package/dist/index.mjs
CHANGED
|
@@ -122,8 +122,7 @@ var resolveSourceValue = (source, writableInputs, payloads, effector, snapshotSe
|
|
|
122
122
|
}
|
|
123
123
|
const characterIds = typeof payloadValue === "string" ? [payloadValue] : payloadValue;
|
|
124
124
|
return characterIds.flatMap((id) => {
|
|
125
|
-
|
|
126
|
-
return resolved !== void 0 ? [resolved] : [];
|
|
125
|
+
return resolveFromCharacter ? resolveFromCharacter(id) : [];
|
|
127
126
|
});
|
|
128
127
|
}
|
|
129
128
|
return castOrValidate(payloadValue);
|
|
@@ -356,8 +355,6 @@ var applyPerceivedCharacterChange = createEffectHandler("PERCEIVED_CHARACTER_CHA
|
|
|
356
355
|
var applyReminderAdd = createEffectHandler("REMINDER_ADD", ({
|
|
357
356
|
effect,
|
|
358
357
|
payloads,
|
|
359
|
-
nowTimelineIndex,
|
|
360
|
-
operationInTimelineIdx,
|
|
361
358
|
makePlayersEffect
|
|
362
359
|
}) => {
|
|
363
360
|
const maybeReminder = getSourceValue(effect.source, payloads);
|
|
@@ -365,10 +362,6 @@ var applyReminderAdd = createEffectHandler("REMINDER_ADD", ({
|
|
|
365
362
|
console.warn("Invalid reminder data for REMINDER_ADD effect:", maybeReminder);
|
|
366
363
|
return;
|
|
367
364
|
}
|
|
368
|
-
const timelineDistance = nowTimelineIndex - operationInTimelineIdx;
|
|
369
|
-
if (typeof maybeReminder.duration === "number" && maybeReminder.duration > 0 && timelineDistance >= maybeReminder.duration) {
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
365
|
makePlayersEffect.forEach((player) => {
|
|
373
366
|
if (!player.reminders) {
|
|
374
367
|
player.reminders = [];
|
|
@@ -638,10 +631,16 @@ var resolveEffectTargets = ({
|
|
|
638
631
|
};
|
|
639
632
|
|
|
640
633
|
// src/effects/evaluate-lifetime.ts
|
|
641
|
-
var getLifetime = (effect) => {
|
|
642
|
-
if ("lifetime" in effect) {
|
|
634
|
+
var getLifetime = (effect, payloads) => {
|
|
635
|
+
if ("lifetime" in effect && effect.lifetime) {
|
|
643
636
|
return effect.lifetime;
|
|
644
637
|
}
|
|
638
|
+
if (effect.type === "REMINDER_ADD") {
|
|
639
|
+
const maybeReminder = getSourceValue(effect.source, payloads);
|
|
640
|
+
if (isReminder(maybeReminder) && typeof maybeReminder.duration === "number" && maybeReminder.duration > 0) {
|
|
641
|
+
return { kind: "PHASES", count: maybeReminder.duration };
|
|
642
|
+
}
|
|
643
|
+
}
|
|
645
644
|
return void 0;
|
|
646
645
|
};
|
|
647
646
|
var matchesLifetimeCondition = (player, condition, characterMap) => {
|
|
@@ -686,9 +685,10 @@ var evaluateLifetime = ({
|
|
|
686
685
|
effector,
|
|
687
686
|
targets,
|
|
688
687
|
snapshotSeatMap,
|
|
689
|
-
characterMap
|
|
688
|
+
characterMap,
|
|
689
|
+
payloads
|
|
690
690
|
}) => {
|
|
691
|
-
const lifetime = getLifetime(effect);
|
|
691
|
+
const lifetime = getLifetime(effect, payloads);
|
|
692
692
|
if (!lifetime || lifetime.kind === "PERMANENT") {
|
|
693
693
|
return targets;
|
|
694
694
|
}
|
|
@@ -700,6 +700,10 @@ var evaluateLifetime = ({
|
|
|
700
700
|
}
|
|
701
701
|
return nowTurn - opTurn < lifetime.count ? targets : [];
|
|
702
702
|
}
|
|
703
|
+
if (lifetime.kind === "PHASES") {
|
|
704
|
+
if (operationTimelineIdx < 0) return targets;
|
|
705
|
+
return nowTimelineIndex - operationTimelineIdx < lifetime.count ? targets : [];
|
|
706
|
+
}
|
|
703
707
|
if (lifetime.kind === "UNTIL_EVENT") {
|
|
704
708
|
if (operationTimelineIdx < 0) return targets;
|
|
705
709
|
for (let idx = operationTimelineIdx + 1; idx <= nowTimelineIndex; idx += 1) {
|
|
@@ -807,7 +811,8 @@ var applyOperationToPlayers = ({
|
|
|
807
811
|
effector,
|
|
808
812
|
targets: resolvedTargets,
|
|
809
813
|
snapshotSeatMap: lifetimeSnapshotSeatMap,
|
|
810
|
-
characterMap
|
|
814
|
+
characterMap,
|
|
815
|
+
payloads
|
|
811
816
|
});
|
|
812
817
|
const handler = effectHandlers[effect.type];
|
|
813
818
|
if (!handler) {
|
|
@@ -885,11 +890,83 @@ var applyOperationToPlayers = ({
|
|
|
885
890
|
const final = runReplay(groundTruthSeatMap);
|
|
886
891
|
return transformEmptyArray(final);
|
|
887
892
|
};
|
|
893
|
+
|
|
894
|
+
// src/can-invoke-ability.ts
|
|
895
|
+
var checkTurns = (turns, currentTurn) => {
|
|
896
|
+
if (turns.only && turns.only.length > 0 && !turns.only.includes(currentTurn)) {
|
|
897
|
+
return `turn ${currentTurn} not in allowed turns ${JSON.stringify(turns.only)}`;
|
|
898
|
+
}
|
|
899
|
+
if (typeof turns.from === "number" && currentTurn < turns.from) {
|
|
900
|
+
return `turn ${currentTurn} is before allowed start turn ${turns.from}`;
|
|
901
|
+
}
|
|
902
|
+
if (typeof turns.to === "number" && currentTurn > turns.to) {
|
|
903
|
+
return `turn ${currentTurn} is after allowed end turn ${turns.to}`;
|
|
904
|
+
}
|
|
905
|
+
return null;
|
|
906
|
+
};
|
|
907
|
+
var countMatchingPriorOps = (priorOperations, abilityId, effectorSeat, operationTimelineMap, matchTurn, matchTime, timelines) => {
|
|
908
|
+
return priorOperations.filter((op) => {
|
|
909
|
+
if (op.abilityId !== abilityId) return false;
|
|
910
|
+
if (typeof effectorSeat === "number" && op.effector !== effectorSeat) return false;
|
|
911
|
+
if (matchTurn === null && matchTime === null) return true;
|
|
912
|
+
const idx = operationTimelineMap.get(op);
|
|
913
|
+
if (typeof idx !== "number") return false;
|
|
914
|
+
const tl = timelines[idx];
|
|
915
|
+
if (!tl) return false;
|
|
916
|
+
if (matchTurn !== null && tl.turn !== matchTurn) return false;
|
|
917
|
+
if (matchTime !== null && tl.time !== matchTime) return false;
|
|
918
|
+
return true;
|
|
919
|
+
}).length;
|
|
920
|
+
};
|
|
921
|
+
var canInvokeAbility = ({
|
|
922
|
+
ability,
|
|
923
|
+
effector,
|
|
924
|
+
currentTimelineIdx,
|
|
925
|
+
timelines,
|
|
926
|
+
priorOperations = []
|
|
927
|
+
}) => {
|
|
928
|
+
const trigger = ability.triggerWindow;
|
|
929
|
+
if (!trigger) return { allowed: true };
|
|
930
|
+
const currentTimeline = timelines[currentTimelineIdx];
|
|
931
|
+
if (!currentTimeline) {
|
|
932
|
+
return { allowed: false, reason: `no timeline at index ${currentTimelineIdx}` };
|
|
933
|
+
}
|
|
934
|
+
if (trigger.turns) {
|
|
935
|
+
const turnError = checkTurns(trigger.turns, currentTimeline.turn);
|
|
936
|
+
if (turnError) return { allowed: false, reason: turnError };
|
|
937
|
+
}
|
|
938
|
+
if (trigger.phases && trigger.phases.length > 0 && !trigger.phases.includes(currentTimeline.time)) {
|
|
939
|
+
return { allowed: false, reason: `phase ${currentTimeline.time} not in allowed phases ${JSON.stringify(trigger.phases)}` };
|
|
940
|
+
}
|
|
941
|
+
if (trigger.requireEffectorAlive && (!effector || effector.isDead)) {
|
|
942
|
+
return { allowed: false, reason: "effector is not alive" };
|
|
943
|
+
}
|
|
944
|
+
if (trigger.frequency && priorOperations.length > 0) {
|
|
945
|
+
const operationTimelineMap = /* @__PURE__ */ new Map();
|
|
946
|
+
timelines.forEach((tl, idx) => tl.operations?.forEach((op) => operationTimelineMap.set(op, idx)));
|
|
947
|
+
const matchTurn = trigger.frequency === "ONCE_PER_GAME" ? null : currentTimeline.turn;
|
|
948
|
+
const matchTime = trigger.frequency === "ONCE_PER_GAME" ? null : trigger.frequency === "ONCE_PER_NIGHT" ? "night" : "day";
|
|
949
|
+
const count = countMatchingPriorOps(
|
|
950
|
+
priorOperations,
|
|
951
|
+
ability.id,
|
|
952
|
+
effector?.seat,
|
|
953
|
+
operationTimelineMap,
|
|
954
|
+
matchTurn,
|
|
955
|
+
matchTime,
|
|
956
|
+
timelines
|
|
957
|
+
);
|
|
958
|
+
if (count > 0) {
|
|
959
|
+
return { allowed: false, reason: `frequency ${trigger.frequency} already exhausted (${count} prior op${count === 1 ? "" : "s"})` };
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
return { allowed: true };
|
|
963
|
+
};
|
|
888
964
|
export {
|
|
889
965
|
adjustValueAsNumber,
|
|
890
966
|
adjustValueAsNumberArray,
|
|
891
967
|
applyOperationToPlayers,
|
|
892
968
|
buildPlayerSeatMap,
|
|
969
|
+
canInvokeAbility,
|
|
893
970
|
copyPlayers,
|
|
894
971
|
getSourceValue,
|
|
895
972
|
getValueFromPayloads,
|