@bct-app/game-engine 0.1.4 → 0.1.6-beta.0
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.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +297 -108
- package/dist/index.mjs +297 -108
- package/package.json +10 -10
package/dist/index.d.mts
CHANGED
|
@@ -21,7 +21,7 @@ declare const getSourceValue: (source: TEffect["source"], payloads: unknown[]) =
|
|
|
21
21
|
/**
|
|
22
22
|
* Resolve a value from effect source, handling PAYLOAD (with player-seat indirection), CONSTANT, and EFFECTOR.
|
|
23
23
|
*/
|
|
24
|
-
declare const resolveSourceValue: <TResolved>(source: TEffect["source"], writableInputs: TAbility["inputs"], payloads: unknown[], effector: TPlayer | undefined, snapshotSeatMap: Map<number, TPlayer>, resolveFromPlayer: (player: TPlayer) => TResolved, isResolvedValue?: (value: unknown) => value is TResolved) => TResolved | undefined;
|
|
24
|
+
declare const resolveSourceValue: <TResolved>(source: TEffect["source"], writableInputs: TAbility["inputs"], payloads: unknown[], effector: TPlayer | undefined, snapshotSeatMap: Map<number, TPlayer>, resolveFromPlayer: (player: TPlayer) => TResolved, isResolvedValue?: (value: unknown) => value is TResolved, resolveFromCharacter?: (characterId: string) => TResolved) => TResolved | undefined;
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Type guard to check if a value is a number or an array of numbers.
|
package/dist/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ declare const getSourceValue: (source: TEffect["source"], payloads: unknown[]) =
|
|
|
21
21
|
/**
|
|
22
22
|
* Resolve a value from effect source, handling PAYLOAD (with player-seat indirection), CONSTANT, and EFFECTOR.
|
|
23
23
|
*/
|
|
24
|
-
declare const resolveSourceValue: <TResolved>(source: TEffect["source"], writableInputs: TAbility["inputs"], payloads: unknown[], effector: TPlayer | undefined, snapshotSeatMap: Map<number, TPlayer>, resolveFromPlayer: (player: TPlayer) => TResolved, isResolvedValue?: (value: unknown) => value is TResolved) => TResolved | undefined;
|
|
24
|
+
declare const resolveSourceValue: <TResolved>(source: TEffect["source"], writableInputs: TAbility["inputs"], payloads: unknown[], effector: TPlayer | undefined, snapshotSeatMap: Map<number, TPlayer>, resolveFromPlayer: (player: TPlayer) => TResolved, isResolvedValue?: (value: unknown) => value is TResolved, resolveFromCharacter?: (characterId: string) => TResolved) => TResolved | undefined;
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Type guard to check if a value is a number or an array of numbers.
|
package/dist/index.js
CHANGED
|
@@ -35,25 +35,6 @@ __export(index_exports, {
|
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
37
|
|
|
38
|
-
// src/effects/handler-types.ts
|
|
39
|
-
var createEffectHandler = (type, handler) => {
|
|
40
|
-
return (context) => {
|
|
41
|
-
if (context.effect.type !== type) {
|
|
42
|
-
console.warn("Invalid effect type for handler:", context.effect.type, "expected:", type);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
handler({
|
|
46
|
-
...context,
|
|
47
|
-
effect: context.effect
|
|
48
|
-
});
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// src/effects/handlers/ability-change.ts
|
|
53
|
-
var applyAbilityChange = createEffectHandler("ABILITY_CHANGE", () => {
|
|
54
|
-
console.warn("ABILITY_CHANGE effect handling not implemented yet.");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
38
|
// src/guards.ts
|
|
58
39
|
var isNumberOrNumberArray = (value) => {
|
|
59
40
|
if (typeof value === "number") {
|
|
@@ -151,7 +132,7 @@ var getSourceValue = (source, payloads) => {
|
|
|
151
132
|
}
|
|
152
133
|
return null;
|
|
153
134
|
};
|
|
154
|
-
var resolveSourceValue = (source, writableInputs, payloads, effector, snapshotSeatMap, resolveFromPlayer, isResolvedValue) => {
|
|
135
|
+
var resolveSourceValue = (source, writableInputs, payloads, effector, snapshotSeatMap, resolveFromPlayer, isResolvedValue, resolveFromCharacter) => {
|
|
155
136
|
const castOrValidate = (value) => {
|
|
156
137
|
if (!isResolvedValue) {
|
|
157
138
|
return value;
|
|
@@ -167,6 +148,10 @@ var resolveSourceValue = (source, writableInputs, payloads, effector, snapshotSe
|
|
|
167
148
|
const player = typeof seat === "number" ? snapshotSeatMap.get(seat) : void 0;
|
|
168
149
|
return player ? resolveFromPlayer(player) : void 0;
|
|
169
150
|
}
|
|
151
|
+
if (input?.type === "CHARACTER") {
|
|
152
|
+
const characterId = typeof payloadValue === "string" ? payloadValue : void 0;
|
|
153
|
+
return characterId && resolveFromCharacter ? resolveFromCharacter(characterId) : void 0;
|
|
154
|
+
}
|
|
170
155
|
return castOrValidate(payloadValue);
|
|
171
156
|
}
|
|
172
157
|
if (source?.from === "CONSTANT") {
|
|
@@ -182,6 +167,90 @@ var resolveSourceValue = (source, writableInputs, payloads, effector, snapshotSe
|
|
|
182
167
|
return void 0;
|
|
183
168
|
};
|
|
184
169
|
|
|
170
|
+
// src/effects/handler-types.ts
|
|
171
|
+
var createEffectHandler = (type, handler) => {
|
|
172
|
+
return (context) => {
|
|
173
|
+
if (context.effect.type !== type) {
|
|
174
|
+
console.warn("Invalid effect type for handler:", context.effect.type, "expected:", type);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
handler({
|
|
178
|
+
...context,
|
|
179
|
+
effect: context.effect
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// src/effects/handlers/ability-change.ts
|
|
185
|
+
var toAbilityIdList = (value) => {
|
|
186
|
+
if (typeof value === "string") {
|
|
187
|
+
return value.length > 0 ? [value] : void 0;
|
|
188
|
+
}
|
|
189
|
+
if (Array.isArray(value)) {
|
|
190
|
+
const ids = value.filter((item) => typeof item === "string" && item.length > 0);
|
|
191
|
+
return ids.length > 0 ? ids : void 0;
|
|
192
|
+
}
|
|
193
|
+
return void 0;
|
|
194
|
+
};
|
|
195
|
+
var applyAbilityChange = createEffectHandler("ABILITY_CHANGE", ({
|
|
196
|
+
effect,
|
|
197
|
+
operation,
|
|
198
|
+
payloads,
|
|
199
|
+
effector,
|
|
200
|
+
writableInputs,
|
|
201
|
+
getSnapshotSeatMap,
|
|
202
|
+
abilityMap,
|
|
203
|
+
makePlayersEffect,
|
|
204
|
+
characterMap
|
|
205
|
+
}) => {
|
|
206
|
+
const resolvedRaw = resolveSourceValue(
|
|
207
|
+
effect.source,
|
|
208
|
+
writableInputs,
|
|
209
|
+
payloads,
|
|
210
|
+
effector,
|
|
211
|
+
getSnapshotSeatMap(),
|
|
212
|
+
(player) => player.abilities,
|
|
213
|
+
(value) => typeof value === "string" && value.length > 0 || Array.isArray(value) && value.every((item) => typeof item === "string"),
|
|
214
|
+
(characterId) => {
|
|
215
|
+
const character = characterMap.get(characterId);
|
|
216
|
+
if (!character) {
|
|
217
|
+
console.warn("Character not found for ABILITY_CHANGE effect source:", characterId);
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
return character.abilities.map((a) => a.id);
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
const resolvedAbilityIds = toAbilityIdList(resolvedRaw);
|
|
224
|
+
if (!resolvedAbilityIds) {
|
|
225
|
+
console.warn("Ability ID not found for ABILITY_CHANGE effect:", effect.source);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
makePlayersEffect.forEach((player) => {
|
|
229
|
+
const overrideRaw = operation.abilityChangeOverrides?.[String(player.seat)];
|
|
230
|
+
const overrideIds = toAbilityIdList(overrideRaw);
|
|
231
|
+
const finalAbilityIds = overrideIds ?? resolvedAbilityIds;
|
|
232
|
+
finalAbilityIds.forEach((finalAbilityId) => {
|
|
233
|
+
const newAbility = abilityMap.get(finalAbilityId);
|
|
234
|
+
if (!newAbility) {
|
|
235
|
+
console.warn("Ability not found for ABILITY_CHANGE effect:", finalAbilityId);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const replaceAtSameStage = (abilities) => {
|
|
239
|
+
const idx = abilities.findIndex((aid) => abilityMap.get(aid)?.stage === newAbility.stage);
|
|
240
|
+
if (idx >= 0) {
|
|
241
|
+
abilities[idx] = finalAbilityId;
|
|
242
|
+
} else {
|
|
243
|
+
abilities.push(finalAbilityId);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
replaceAtSameStage(player.abilities);
|
|
247
|
+
if (player.perceivedCharacter?.asCharacter) {
|
|
248
|
+
replaceAtSameStage(player.perceivedCharacter.abilities);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
185
254
|
// src/effects/handlers/alignment-change.ts
|
|
186
255
|
var applyAlignmentChange = createEffectHandler("ALIGNMENT_CHANGE", ({
|
|
187
256
|
effect,
|
|
@@ -594,11 +663,111 @@ var resolveEffectTargets = ({
|
|
|
594
663
|
return [];
|
|
595
664
|
};
|
|
596
665
|
|
|
666
|
+
// src/effects/evaluate-lifetime.ts
|
|
667
|
+
var getLifetime = (effect) => {
|
|
668
|
+
if ("lifetime" in effect) {
|
|
669
|
+
return effect.lifetime;
|
|
670
|
+
}
|
|
671
|
+
return void 0;
|
|
672
|
+
};
|
|
673
|
+
var matchesLifetimeCondition = (player, condition, characterMap) => {
|
|
674
|
+
const { field, operator } = condition;
|
|
675
|
+
const value = condition.value;
|
|
676
|
+
if (field === "IS_DEAD") {
|
|
677
|
+
return Boolean(player.isDead) === Boolean(value);
|
|
678
|
+
}
|
|
679
|
+
if (field === "CHARACTER_ID") {
|
|
680
|
+
if (operator === "IN" && Array.isArray(value)) {
|
|
681
|
+
return value.includes(player.characterId);
|
|
682
|
+
}
|
|
683
|
+
return typeof value === "string" && player.characterId === value;
|
|
684
|
+
}
|
|
685
|
+
if (field === "CHARACTER_KIND") {
|
|
686
|
+
const kind = characterMap.get(player.characterId)?.kind;
|
|
687
|
+
if (!kind) return false;
|
|
688
|
+
if (operator === "IN" && Array.isArray(value)) {
|
|
689
|
+
return value.includes(kind);
|
|
690
|
+
}
|
|
691
|
+
return typeof value === "string" && kind === value;
|
|
692
|
+
}
|
|
693
|
+
if (field === "HAS_ABILITY") {
|
|
694
|
+
if (operator === "IN" && Array.isArray(value)) {
|
|
695
|
+
return value.some((id) => player.abilities.includes(id));
|
|
696
|
+
}
|
|
697
|
+
return typeof value === "string" && player.abilities.includes(value);
|
|
698
|
+
}
|
|
699
|
+
return false;
|
|
700
|
+
};
|
|
701
|
+
var evaluateConditions = (player, conditions, mode, characterMap) => {
|
|
702
|
+
if (!player) return false;
|
|
703
|
+
if (conditions.length === 0) return true;
|
|
704
|
+
const results = conditions.map((c) => matchesLifetimeCondition(player, c, characterMap));
|
|
705
|
+
return mode === "ALL" ? results.every(Boolean) : results.some(Boolean);
|
|
706
|
+
};
|
|
707
|
+
var evaluateLifetime = ({
|
|
708
|
+
effect,
|
|
709
|
+
operationTimelineIdx,
|
|
710
|
+
nowTimelineIndex,
|
|
711
|
+
timelines,
|
|
712
|
+
effector,
|
|
713
|
+
targets,
|
|
714
|
+
snapshotSeatMap,
|
|
715
|
+
characterMap
|
|
716
|
+
}) => {
|
|
717
|
+
const lifetime = getLifetime(effect);
|
|
718
|
+
if (!lifetime || lifetime.kind === "PERMANENT") {
|
|
719
|
+
return targets;
|
|
720
|
+
}
|
|
721
|
+
if (lifetime.kind === "TURNS") {
|
|
722
|
+
const opTurn = operationTimelineIdx >= 0 ? timelines[operationTimelineIdx]?.turn : void 0;
|
|
723
|
+
const nowTurn = nowTimelineIndex >= 0 ? timelines[nowTimelineIndex]?.turn : void 0;
|
|
724
|
+
if (typeof opTurn !== "number" || typeof nowTurn !== "number") {
|
|
725
|
+
return targets;
|
|
726
|
+
}
|
|
727
|
+
return nowTurn - opTurn < lifetime.count ? targets : [];
|
|
728
|
+
}
|
|
729
|
+
if (lifetime.kind === "UNTIL_EVENT") {
|
|
730
|
+
if (operationTimelineIdx < 0) return targets;
|
|
731
|
+
for (let idx = operationTimelineIdx + 1; idx <= nowTimelineIndex; idx += 1) {
|
|
732
|
+
const tl = timelines[idx];
|
|
733
|
+
if (!tl) continue;
|
|
734
|
+
if (lifetime.event === "NEXT_DAY" && tl.time === "day") return [];
|
|
735
|
+
if (lifetime.event === "NEXT_NIGHT" && tl.time === "night") return [];
|
|
736
|
+
if (lifetime.event === "NEXT_EXECUTION" && (tl.nominations?.length ?? 0) > 0) return [];
|
|
737
|
+
}
|
|
738
|
+
return targets;
|
|
739
|
+
}
|
|
740
|
+
if (lifetime.kind === "WHILE") {
|
|
741
|
+
if (!snapshotSeatMap) {
|
|
742
|
+
return targets;
|
|
743
|
+
}
|
|
744
|
+
const subject = lifetime.subject ?? "EFFECTOR";
|
|
745
|
+
const mode = lifetime.mode ?? "ALL";
|
|
746
|
+
const conditions = lifetime.conditions ?? [];
|
|
747
|
+
if (subject === "EFFECTOR") {
|
|
748
|
+
const current = effector ? snapshotSeatMap.get(effector.seat) : void 0;
|
|
749
|
+
return evaluateConditions(current, conditions, mode, characterMap) ? targets : [];
|
|
750
|
+
}
|
|
751
|
+
return targets.filter((target) => {
|
|
752
|
+
const current = snapshotSeatMap.get(target.seat);
|
|
753
|
+
return evaluateConditions(current, conditions, mode, characterMap);
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
return targets;
|
|
757
|
+
};
|
|
758
|
+
|
|
597
759
|
// src/apply-operation.ts
|
|
598
760
|
var isDeferredOperation = (operation, abilityMap) => {
|
|
599
761
|
const ability = abilityMap.get(operation.abilityId);
|
|
600
762
|
return ability?.executionTiming === "DEFER_TO_END";
|
|
601
763
|
};
|
|
764
|
+
var hasGatedLifetime = (allAbilities) => {
|
|
765
|
+
return allAbilities.some((ability) => ability.effects.some((effect) => {
|
|
766
|
+
if (!("lifetime" in effect)) return false;
|
|
767
|
+
const lifetime = effect.lifetime;
|
|
768
|
+
return Boolean(lifetime) && lifetime?.kind !== "PERMANENT";
|
|
769
|
+
}));
|
|
770
|
+
};
|
|
602
771
|
var applyOperationToPlayers = ({
|
|
603
772
|
players,
|
|
604
773
|
operations,
|
|
@@ -614,7 +783,6 @@ var applyOperationToPlayers = ({
|
|
|
614
783
|
const characterMap = new Map(characters.map((character) => [character.id, character]));
|
|
615
784
|
const operationTimelineMap = /* @__PURE__ */ new Map();
|
|
616
785
|
timelines.forEach((timeline, idx) => timeline.operations?.forEach((op) => operationTimelineMap.set(op, idx)));
|
|
617
|
-
const playersWithStatus = copyPlayers(players);
|
|
618
786
|
const nowTimelineIndex = typeof timelineIndexAtNow === "number" ? timelineIndexAtNow : timelines.length - 1;
|
|
619
787
|
const operationsByTimeline = /* @__PURE__ */ new Map();
|
|
620
788
|
operations.forEach((operation) => {
|
|
@@ -624,103 +792,124 @@ var applyOperationToPlayers = ({
|
|
|
624
792
|
}
|
|
625
793
|
operationsByTimeline.get(timelineIndex)?.push(operation);
|
|
626
794
|
});
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
const playerSeatMap = buildPlayerSeatMap(playersWithStatus);
|
|
636
|
-
const effector = playerSeatMap.get(operation.effector);
|
|
637
|
-
const payloads = operation.payloads || [];
|
|
638
|
-
const writableInputs = ability.inputs.filter((part) => !part.readonly);
|
|
639
|
-
let snapshotSeatMap = null;
|
|
640
|
-
const getSnapshotSeatMap = () => {
|
|
641
|
-
if (!snapshotSeatMap) {
|
|
642
|
-
snapshotSeatMap = buildPlayerSeatMap(copyPlayers(playersWithStatus));
|
|
643
|
-
}
|
|
644
|
-
return snapshotSeatMap;
|
|
645
|
-
};
|
|
646
|
-
ability.effects.forEach((effect) => {
|
|
647
|
-
const makePlayersEffect = resolveEffectTargets({
|
|
648
|
-
effect,
|
|
649
|
-
operation,
|
|
650
|
-
payloads,
|
|
651
|
-
effector,
|
|
652
|
-
playerSeatMap,
|
|
653
|
-
characterMap
|
|
654
|
-
});
|
|
655
|
-
if (!makePlayersEffect) {
|
|
795
|
+
const runReplay = (lifetimeSnapshotSeatMap) => {
|
|
796
|
+
const playersWithStatus = copyPlayers(players);
|
|
797
|
+
const applyOpsSequence = (ops) => {
|
|
798
|
+
ops.forEach((operation) => {
|
|
799
|
+
const operationInTimelineIdx = operationTimelineMap.get(operation) ?? -1;
|
|
800
|
+
const ability = abilityMap.get(operation.abilityId);
|
|
801
|
+
if (!ability) {
|
|
802
|
+
console.warn("Ability not found for operation:", operation);
|
|
656
803
|
return;
|
|
657
804
|
}
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
writableInputs,
|
|
669
|
-
playerSeatMap,
|
|
670
|
-
getSnapshotSeatMap,
|
|
671
|
-
characterMap,
|
|
672
|
-
nowTimelineIndex,
|
|
673
|
-
operationInTimelineIdx,
|
|
674
|
-
makePlayersEffect
|
|
805
|
+
const playerSeatMap = buildPlayerSeatMap(playersWithStatus);
|
|
806
|
+
const effector = playerSeatMap.get(operation.effector);
|
|
807
|
+
const payloads = operation.payloads || [];
|
|
808
|
+
const writableInputs = ability.inputs.filter((part) => !part.readonly);
|
|
809
|
+
let snapshotSeatMap = null;
|
|
810
|
+
const getSnapshotSeatMap = () => {
|
|
811
|
+
if (!snapshotSeatMap) {
|
|
812
|
+
snapshotSeatMap = buildPlayerSeatMap(copyPlayers(playersWithStatus));
|
|
813
|
+
}
|
|
814
|
+
return snapshotSeatMap;
|
|
675
815
|
};
|
|
676
|
-
|
|
816
|
+
ability.effects.forEach((effect) => {
|
|
817
|
+
const resolvedTargets = resolveEffectTargets({
|
|
818
|
+
effect,
|
|
819
|
+
operation,
|
|
820
|
+
payloads,
|
|
821
|
+
effector,
|
|
822
|
+
playerSeatMap,
|
|
823
|
+
characterMap
|
|
824
|
+
});
|
|
825
|
+
if (!resolvedTargets) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
const makePlayersEffect = evaluateLifetime({
|
|
829
|
+
effect,
|
|
830
|
+
operationTimelineIdx: operationInTimelineIdx,
|
|
831
|
+
nowTimelineIndex,
|
|
832
|
+
timelines,
|
|
833
|
+
effector,
|
|
834
|
+
targets: resolvedTargets,
|
|
835
|
+
snapshotSeatMap: lifetimeSnapshotSeatMap,
|
|
836
|
+
characterMap
|
|
837
|
+
});
|
|
838
|
+
const handler = effectHandlers[effect.type];
|
|
839
|
+
if (!handler) {
|
|
840
|
+
console.warn("Unknown effect type:", effect.type);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
const context = {
|
|
844
|
+
effect,
|
|
845
|
+
operation,
|
|
846
|
+
payloads,
|
|
847
|
+
effector,
|
|
848
|
+
writableInputs,
|
|
849
|
+
playerSeatMap,
|
|
850
|
+
getSnapshotSeatMap,
|
|
851
|
+
characterMap,
|
|
852
|
+
abilityMap,
|
|
853
|
+
nowTimelineIndex,
|
|
854
|
+
operationInTimelineIdx,
|
|
855
|
+
makePlayersEffect
|
|
856
|
+
};
|
|
857
|
+
handler(context);
|
|
858
|
+
});
|
|
677
859
|
});
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
deferredOps.push(operation);
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
normalOps.push(operation);
|
|
689
|
-
});
|
|
690
|
-
applyOpsSequence(normalOps);
|
|
691
|
-
applyOpsSequence(deferredOps);
|
|
692
|
-
};
|
|
693
|
-
const settingsOps = operationsByTimeline.get(-1);
|
|
694
|
-
if (settingsOps) {
|
|
695
|
-
applyOps(settingsOps);
|
|
696
|
-
}
|
|
697
|
-
for (let i = 0; i <= nowTimelineIndex; i += 1) {
|
|
698
|
-
const timeline = timelines[i];
|
|
699
|
-
if (!timeline) {
|
|
700
|
-
continue;
|
|
701
|
-
}
|
|
702
|
-
const nominations = timeline.nominations;
|
|
703
|
-
if (nominations && nominations.length > 0) {
|
|
704
|
-
const playerSeatMap = buildPlayerSeatMap(playersWithStatus);
|
|
705
|
-
nominations.forEach((nomination) => {
|
|
706
|
-
const voterSeats = nomination.voterSeats;
|
|
707
|
-
if (!voterSeats || voterSeats.length === 0) {
|
|
860
|
+
};
|
|
861
|
+
const applyOps = (ops) => {
|
|
862
|
+
const normalOps = [];
|
|
863
|
+
const deferredOps = [];
|
|
864
|
+
ops.forEach((operation) => {
|
|
865
|
+
if (isDeferredOperation(operation, abilityMap)) {
|
|
866
|
+
deferredOps.push(operation);
|
|
708
867
|
return;
|
|
709
868
|
}
|
|
710
|
-
|
|
711
|
-
const player = playerSeatMap.get(seat);
|
|
712
|
-
if (player && player.isDead && !player.hasUsedDeadVote) {
|
|
713
|
-
player.hasUsedDeadVote = true;
|
|
714
|
-
}
|
|
715
|
-
});
|
|
869
|
+
normalOps.push(operation);
|
|
716
870
|
});
|
|
871
|
+
applyOpsSequence(normalOps);
|
|
872
|
+
applyOpsSequence(deferredOps);
|
|
873
|
+
};
|
|
874
|
+
const settingsOps = operationsByTimeline.get(-1);
|
|
875
|
+
if (settingsOps) {
|
|
876
|
+
applyOps(settingsOps);
|
|
717
877
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
878
|
+
for (let i = 0; i <= nowTimelineIndex; i += 1) {
|
|
879
|
+
const timeline = timelines[i];
|
|
880
|
+
if (!timeline) {
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
const nominations = timeline.nominations;
|
|
884
|
+
if (nominations && nominations.length > 0) {
|
|
885
|
+
const playerSeatMap = buildPlayerSeatMap(playersWithStatus);
|
|
886
|
+
nominations.forEach((nomination) => {
|
|
887
|
+
const voterSeats = nomination.voterSeats;
|
|
888
|
+
if (!voterSeats || voterSeats.length === 0) {
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
voterSeats.forEach((seat) => {
|
|
892
|
+
const player = playerSeatMap.get(seat);
|
|
893
|
+
if (player && player.isDead && !player.hasUsedDeadVote) {
|
|
894
|
+
player.hasUsedDeadVote = true;
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
const timelineOps = operationsByTimeline.get(i);
|
|
900
|
+
if (timelineOps) {
|
|
901
|
+
applyOps(timelineOps);
|
|
902
|
+
}
|
|
721
903
|
}
|
|
904
|
+
return playersWithStatus;
|
|
905
|
+
};
|
|
906
|
+
if (!hasGatedLifetime(allAbilities)) {
|
|
907
|
+
return transformEmptyArray(runReplay(null));
|
|
722
908
|
}
|
|
723
|
-
|
|
909
|
+
const groundTruth = runReplay(null);
|
|
910
|
+
const groundTruthSeatMap = buildPlayerSeatMap(groundTruth);
|
|
911
|
+
const final = runReplay(groundTruthSeatMap);
|
|
912
|
+
return transformEmptyArray(final);
|
|
724
913
|
};
|
|
725
914
|
// Annotate the CommonJS export names for ESM import in node:
|
|
726
915
|
0 && (module.exports = {
|
package/dist/index.mjs
CHANGED
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
// src/effects/handler-types.ts
|
|
2
|
-
var createEffectHandler = (type, handler) => {
|
|
3
|
-
return (context) => {
|
|
4
|
-
if (context.effect.type !== type) {
|
|
5
|
-
console.warn("Invalid effect type for handler:", context.effect.type, "expected:", type);
|
|
6
|
-
return;
|
|
7
|
-
}
|
|
8
|
-
handler({
|
|
9
|
-
...context,
|
|
10
|
-
effect: context.effect
|
|
11
|
-
});
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
// src/effects/handlers/ability-change.ts
|
|
16
|
-
var applyAbilityChange = createEffectHandler("ABILITY_CHANGE", () => {
|
|
17
|
-
console.warn("ABILITY_CHANGE effect handling not implemented yet.");
|
|
18
|
-
});
|
|
19
|
-
|
|
20
1
|
// src/guards.ts
|
|
21
2
|
var isNumberOrNumberArray = (value) => {
|
|
22
3
|
if (typeof value === "number") {
|
|
@@ -114,7 +95,7 @@ var getSourceValue = (source, payloads) => {
|
|
|
114
95
|
}
|
|
115
96
|
return null;
|
|
116
97
|
};
|
|
117
|
-
var resolveSourceValue = (source, writableInputs, payloads, effector, snapshotSeatMap, resolveFromPlayer, isResolvedValue) => {
|
|
98
|
+
var resolveSourceValue = (source, writableInputs, payloads, effector, snapshotSeatMap, resolveFromPlayer, isResolvedValue, resolveFromCharacter) => {
|
|
118
99
|
const castOrValidate = (value) => {
|
|
119
100
|
if (!isResolvedValue) {
|
|
120
101
|
return value;
|
|
@@ -130,6 +111,10 @@ var resolveSourceValue = (source, writableInputs, payloads, effector, snapshotSe
|
|
|
130
111
|
const player = typeof seat === "number" ? snapshotSeatMap.get(seat) : void 0;
|
|
131
112
|
return player ? resolveFromPlayer(player) : void 0;
|
|
132
113
|
}
|
|
114
|
+
if (input?.type === "CHARACTER") {
|
|
115
|
+
const characterId = typeof payloadValue === "string" ? payloadValue : void 0;
|
|
116
|
+
return characterId && resolveFromCharacter ? resolveFromCharacter(characterId) : void 0;
|
|
117
|
+
}
|
|
133
118
|
return castOrValidate(payloadValue);
|
|
134
119
|
}
|
|
135
120
|
if (source?.from === "CONSTANT") {
|
|
@@ -145,6 +130,90 @@ var resolveSourceValue = (source, writableInputs, payloads, effector, snapshotSe
|
|
|
145
130
|
return void 0;
|
|
146
131
|
};
|
|
147
132
|
|
|
133
|
+
// src/effects/handler-types.ts
|
|
134
|
+
var createEffectHandler = (type, handler) => {
|
|
135
|
+
return (context) => {
|
|
136
|
+
if (context.effect.type !== type) {
|
|
137
|
+
console.warn("Invalid effect type for handler:", context.effect.type, "expected:", type);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
handler({
|
|
141
|
+
...context,
|
|
142
|
+
effect: context.effect
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// src/effects/handlers/ability-change.ts
|
|
148
|
+
var toAbilityIdList = (value) => {
|
|
149
|
+
if (typeof value === "string") {
|
|
150
|
+
return value.length > 0 ? [value] : void 0;
|
|
151
|
+
}
|
|
152
|
+
if (Array.isArray(value)) {
|
|
153
|
+
const ids = value.filter((item) => typeof item === "string" && item.length > 0);
|
|
154
|
+
return ids.length > 0 ? ids : void 0;
|
|
155
|
+
}
|
|
156
|
+
return void 0;
|
|
157
|
+
};
|
|
158
|
+
var applyAbilityChange = createEffectHandler("ABILITY_CHANGE", ({
|
|
159
|
+
effect,
|
|
160
|
+
operation,
|
|
161
|
+
payloads,
|
|
162
|
+
effector,
|
|
163
|
+
writableInputs,
|
|
164
|
+
getSnapshotSeatMap,
|
|
165
|
+
abilityMap,
|
|
166
|
+
makePlayersEffect,
|
|
167
|
+
characterMap
|
|
168
|
+
}) => {
|
|
169
|
+
const resolvedRaw = resolveSourceValue(
|
|
170
|
+
effect.source,
|
|
171
|
+
writableInputs,
|
|
172
|
+
payloads,
|
|
173
|
+
effector,
|
|
174
|
+
getSnapshotSeatMap(),
|
|
175
|
+
(player) => player.abilities,
|
|
176
|
+
(value) => typeof value === "string" && value.length > 0 || Array.isArray(value) && value.every((item) => typeof item === "string"),
|
|
177
|
+
(characterId) => {
|
|
178
|
+
const character = characterMap.get(characterId);
|
|
179
|
+
if (!character) {
|
|
180
|
+
console.warn("Character not found for ABILITY_CHANGE effect source:", characterId);
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
return character.abilities.map((a) => a.id);
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
const resolvedAbilityIds = toAbilityIdList(resolvedRaw);
|
|
187
|
+
if (!resolvedAbilityIds) {
|
|
188
|
+
console.warn("Ability ID not found for ABILITY_CHANGE effect:", effect.source);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
makePlayersEffect.forEach((player) => {
|
|
192
|
+
const overrideRaw = operation.abilityChangeOverrides?.[String(player.seat)];
|
|
193
|
+
const overrideIds = toAbilityIdList(overrideRaw);
|
|
194
|
+
const finalAbilityIds = overrideIds ?? resolvedAbilityIds;
|
|
195
|
+
finalAbilityIds.forEach((finalAbilityId) => {
|
|
196
|
+
const newAbility = abilityMap.get(finalAbilityId);
|
|
197
|
+
if (!newAbility) {
|
|
198
|
+
console.warn("Ability not found for ABILITY_CHANGE effect:", finalAbilityId);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const replaceAtSameStage = (abilities) => {
|
|
202
|
+
const idx = abilities.findIndex((aid) => abilityMap.get(aid)?.stage === newAbility.stage);
|
|
203
|
+
if (idx >= 0) {
|
|
204
|
+
abilities[idx] = finalAbilityId;
|
|
205
|
+
} else {
|
|
206
|
+
abilities.push(finalAbilityId);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
replaceAtSameStage(player.abilities);
|
|
210
|
+
if (player.perceivedCharacter?.asCharacter) {
|
|
211
|
+
replaceAtSameStage(player.perceivedCharacter.abilities);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
148
217
|
// src/effects/handlers/alignment-change.ts
|
|
149
218
|
var applyAlignmentChange = createEffectHandler("ALIGNMENT_CHANGE", ({
|
|
150
219
|
effect,
|
|
@@ -557,11 +626,111 @@ var resolveEffectTargets = ({
|
|
|
557
626
|
return [];
|
|
558
627
|
};
|
|
559
628
|
|
|
629
|
+
// src/effects/evaluate-lifetime.ts
|
|
630
|
+
var getLifetime = (effect) => {
|
|
631
|
+
if ("lifetime" in effect) {
|
|
632
|
+
return effect.lifetime;
|
|
633
|
+
}
|
|
634
|
+
return void 0;
|
|
635
|
+
};
|
|
636
|
+
var matchesLifetimeCondition = (player, condition, characterMap) => {
|
|
637
|
+
const { field, operator } = condition;
|
|
638
|
+
const value = condition.value;
|
|
639
|
+
if (field === "IS_DEAD") {
|
|
640
|
+
return Boolean(player.isDead) === Boolean(value);
|
|
641
|
+
}
|
|
642
|
+
if (field === "CHARACTER_ID") {
|
|
643
|
+
if (operator === "IN" && Array.isArray(value)) {
|
|
644
|
+
return value.includes(player.characterId);
|
|
645
|
+
}
|
|
646
|
+
return typeof value === "string" && player.characterId === value;
|
|
647
|
+
}
|
|
648
|
+
if (field === "CHARACTER_KIND") {
|
|
649
|
+
const kind = characterMap.get(player.characterId)?.kind;
|
|
650
|
+
if (!kind) return false;
|
|
651
|
+
if (operator === "IN" && Array.isArray(value)) {
|
|
652
|
+
return value.includes(kind);
|
|
653
|
+
}
|
|
654
|
+
return typeof value === "string" && kind === value;
|
|
655
|
+
}
|
|
656
|
+
if (field === "HAS_ABILITY") {
|
|
657
|
+
if (operator === "IN" && Array.isArray(value)) {
|
|
658
|
+
return value.some((id) => player.abilities.includes(id));
|
|
659
|
+
}
|
|
660
|
+
return typeof value === "string" && player.abilities.includes(value);
|
|
661
|
+
}
|
|
662
|
+
return false;
|
|
663
|
+
};
|
|
664
|
+
var evaluateConditions = (player, conditions, mode, characterMap) => {
|
|
665
|
+
if (!player) return false;
|
|
666
|
+
if (conditions.length === 0) return true;
|
|
667
|
+
const results = conditions.map((c) => matchesLifetimeCondition(player, c, characterMap));
|
|
668
|
+
return mode === "ALL" ? results.every(Boolean) : results.some(Boolean);
|
|
669
|
+
};
|
|
670
|
+
var evaluateLifetime = ({
|
|
671
|
+
effect,
|
|
672
|
+
operationTimelineIdx,
|
|
673
|
+
nowTimelineIndex,
|
|
674
|
+
timelines,
|
|
675
|
+
effector,
|
|
676
|
+
targets,
|
|
677
|
+
snapshotSeatMap,
|
|
678
|
+
characterMap
|
|
679
|
+
}) => {
|
|
680
|
+
const lifetime = getLifetime(effect);
|
|
681
|
+
if (!lifetime || lifetime.kind === "PERMANENT") {
|
|
682
|
+
return targets;
|
|
683
|
+
}
|
|
684
|
+
if (lifetime.kind === "TURNS") {
|
|
685
|
+
const opTurn = operationTimelineIdx >= 0 ? timelines[operationTimelineIdx]?.turn : void 0;
|
|
686
|
+
const nowTurn = nowTimelineIndex >= 0 ? timelines[nowTimelineIndex]?.turn : void 0;
|
|
687
|
+
if (typeof opTurn !== "number" || typeof nowTurn !== "number") {
|
|
688
|
+
return targets;
|
|
689
|
+
}
|
|
690
|
+
return nowTurn - opTurn < lifetime.count ? targets : [];
|
|
691
|
+
}
|
|
692
|
+
if (lifetime.kind === "UNTIL_EVENT") {
|
|
693
|
+
if (operationTimelineIdx < 0) return targets;
|
|
694
|
+
for (let idx = operationTimelineIdx + 1; idx <= nowTimelineIndex; idx += 1) {
|
|
695
|
+
const tl = timelines[idx];
|
|
696
|
+
if (!tl) continue;
|
|
697
|
+
if (lifetime.event === "NEXT_DAY" && tl.time === "day") return [];
|
|
698
|
+
if (lifetime.event === "NEXT_NIGHT" && tl.time === "night") return [];
|
|
699
|
+
if (lifetime.event === "NEXT_EXECUTION" && (tl.nominations?.length ?? 0) > 0) return [];
|
|
700
|
+
}
|
|
701
|
+
return targets;
|
|
702
|
+
}
|
|
703
|
+
if (lifetime.kind === "WHILE") {
|
|
704
|
+
if (!snapshotSeatMap) {
|
|
705
|
+
return targets;
|
|
706
|
+
}
|
|
707
|
+
const subject = lifetime.subject ?? "EFFECTOR";
|
|
708
|
+
const mode = lifetime.mode ?? "ALL";
|
|
709
|
+
const conditions = lifetime.conditions ?? [];
|
|
710
|
+
if (subject === "EFFECTOR") {
|
|
711
|
+
const current = effector ? snapshotSeatMap.get(effector.seat) : void 0;
|
|
712
|
+
return evaluateConditions(current, conditions, mode, characterMap) ? targets : [];
|
|
713
|
+
}
|
|
714
|
+
return targets.filter((target) => {
|
|
715
|
+
const current = snapshotSeatMap.get(target.seat);
|
|
716
|
+
return evaluateConditions(current, conditions, mode, characterMap);
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
return targets;
|
|
720
|
+
};
|
|
721
|
+
|
|
560
722
|
// src/apply-operation.ts
|
|
561
723
|
var isDeferredOperation = (operation, abilityMap) => {
|
|
562
724
|
const ability = abilityMap.get(operation.abilityId);
|
|
563
725
|
return ability?.executionTiming === "DEFER_TO_END";
|
|
564
726
|
};
|
|
727
|
+
var hasGatedLifetime = (allAbilities) => {
|
|
728
|
+
return allAbilities.some((ability) => ability.effects.some((effect) => {
|
|
729
|
+
if (!("lifetime" in effect)) return false;
|
|
730
|
+
const lifetime = effect.lifetime;
|
|
731
|
+
return Boolean(lifetime) && lifetime?.kind !== "PERMANENT";
|
|
732
|
+
}));
|
|
733
|
+
};
|
|
565
734
|
var applyOperationToPlayers = ({
|
|
566
735
|
players,
|
|
567
736
|
operations,
|
|
@@ -577,7 +746,6 @@ var applyOperationToPlayers = ({
|
|
|
577
746
|
const characterMap = new Map(characters.map((character) => [character.id, character]));
|
|
578
747
|
const operationTimelineMap = /* @__PURE__ */ new Map();
|
|
579
748
|
timelines.forEach((timeline, idx) => timeline.operations?.forEach((op) => operationTimelineMap.set(op, idx)));
|
|
580
|
-
const playersWithStatus = copyPlayers(players);
|
|
581
749
|
const nowTimelineIndex = typeof timelineIndexAtNow === "number" ? timelineIndexAtNow : timelines.length - 1;
|
|
582
750
|
const operationsByTimeline = /* @__PURE__ */ new Map();
|
|
583
751
|
operations.forEach((operation) => {
|
|
@@ -587,103 +755,124 @@ var applyOperationToPlayers = ({
|
|
|
587
755
|
}
|
|
588
756
|
operationsByTimeline.get(timelineIndex)?.push(operation);
|
|
589
757
|
});
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
const playerSeatMap = buildPlayerSeatMap(playersWithStatus);
|
|
599
|
-
const effector = playerSeatMap.get(operation.effector);
|
|
600
|
-
const payloads = operation.payloads || [];
|
|
601
|
-
const writableInputs = ability.inputs.filter((part) => !part.readonly);
|
|
602
|
-
let snapshotSeatMap = null;
|
|
603
|
-
const getSnapshotSeatMap = () => {
|
|
604
|
-
if (!snapshotSeatMap) {
|
|
605
|
-
snapshotSeatMap = buildPlayerSeatMap(copyPlayers(playersWithStatus));
|
|
606
|
-
}
|
|
607
|
-
return snapshotSeatMap;
|
|
608
|
-
};
|
|
609
|
-
ability.effects.forEach((effect) => {
|
|
610
|
-
const makePlayersEffect = resolveEffectTargets({
|
|
611
|
-
effect,
|
|
612
|
-
operation,
|
|
613
|
-
payloads,
|
|
614
|
-
effector,
|
|
615
|
-
playerSeatMap,
|
|
616
|
-
characterMap
|
|
617
|
-
});
|
|
618
|
-
if (!makePlayersEffect) {
|
|
758
|
+
const runReplay = (lifetimeSnapshotSeatMap) => {
|
|
759
|
+
const playersWithStatus = copyPlayers(players);
|
|
760
|
+
const applyOpsSequence = (ops) => {
|
|
761
|
+
ops.forEach((operation) => {
|
|
762
|
+
const operationInTimelineIdx = operationTimelineMap.get(operation) ?? -1;
|
|
763
|
+
const ability = abilityMap.get(operation.abilityId);
|
|
764
|
+
if (!ability) {
|
|
765
|
+
console.warn("Ability not found for operation:", operation);
|
|
619
766
|
return;
|
|
620
767
|
}
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
writableInputs,
|
|
632
|
-
playerSeatMap,
|
|
633
|
-
getSnapshotSeatMap,
|
|
634
|
-
characterMap,
|
|
635
|
-
nowTimelineIndex,
|
|
636
|
-
operationInTimelineIdx,
|
|
637
|
-
makePlayersEffect
|
|
768
|
+
const playerSeatMap = buildPlayerSeatMap(playersWithStatus);
|
|
769
|
+
const effector = playerSeatMap.get(operation.effector);
|
|
770
|
+
const payloads = operation.payloads || [];
|
|
771
|
+
const writableInputs = ability.inputs.filter((part) => !part.readonly);
|
|
772
|
+
let snapshotSeatMap = null;
|
|
773
|
+
const getSnapshotSeatMap = () => {
|
|
774
|
+
if (!snapshotSeatMap) {
|
|
775
|
+
snapshotSeatMap = buildPlayerSeatMap(copyPlayers(playersWithStatus));
|
|
776
|
+
}
|
|
777
|
+
return snapshotSeatMap;
|
|
638
778
|
};
|
|
639
|
-
|
|
779
|
+
ability.effects.forEach((effect) => {
|
|
780
|
+
const resolvedTargets = resolveEffectTargets({
|
|
781
|
+
effect,
|
|
782
|
+
operation,
|
|
783
|
+
payloads,
|
|
784
|
+
effector,
|
|
785
|
+
playerSeatMap,
|
|
786
|
+
characterMap
|
|
787
|
+
});
|
|
788
|
+
if (!resolvedTargets) {
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
const makePlayersEffect = evaluateLifetime({
|
|
792
|
+
effect,
|
|
793
|
+
operationTimelineIdx: operationInTimelineIdx,
|
|
794
|
+
nowTimelineIndex,
|
|
795
|
+
timelines,
|
|
796
|
+
effector,
|
|
797
|
+
targets: resolvedTargets,
|
|
798
|
+
snapshotSeatMap: lifetimeSnapshotSeatMap,
|
|
799
|
+
characterMap
|
|
800
|
+
});
|
|
801
|
+
const handler = effectHandlers[effect.type];
|
|
802
|
+
if (!handler) {
|
|
803
|
+
console.warn("Unknown effect type:", effect.type);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
const context = {
|
|
807
|
+
effect,
|
|
808
|
+
operation,
|
|
809
|
+
payloads,
|
|
810
|
+
effector,
|
|
811
|
+
writableInputs,
|
|
812
|
+
playerSeatMap,
|
|
813
|
+
getSnapshotSeatMap,
|
|
814
|
+
characterMap,
|
|
815
|
+
abilityMap,
|
|
816
|
+
nowTimelineIndex,
|
|
817
|
+
operationInTimelineIdx,
|
|
818
|
+
makePlayersEffect
|
|
819
|
+
};
|
|
820
|
+
handler(context);
|
|
821
|
+
});
|
|
640
822
|
});
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
deferredOps.push(operation);
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
normalOps.push(operation);
|
|
652
|
-
});
|
|
653
|
-
applyOpsSequence(normalOps);
|
|
654
|
-
applyOpsSequence(deferredOps);
|
|
655
|
-
};
|
|
656
|
-
const settingsOps = operationsByTimeline.get(-1);
|
|
657
|
-
if (settingsOps) {
|
|
658
|
-
applyOps(settingsOps);
|
|
659
|
-
}
|
|
660
|
-
for (let i = 0; i <= nowTimelineIndex; i += 1) {
|
|
661
|
-
const timeline = timelines[i];
|
|
662
|
-
if (!timeline) {
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
const nominations = timeline.nominations;
|
|
666
|
-
if (nominations && nominations.length > 0) {
|
|
667
|
-
const playerSeatMap = buildPlayerSeatMap(playersWithStatus);
|
|
668
|
-
nominations.forEach((nomination) => {
|
|
669
|
-
const voterSeats = nomination.voterSeats;
|
|
670
|
-
if (!voterSeats || voterSeats.length === 0) {
|
|
823
|
+
};
|
|
824
|
+
const applyOps = (ops) => {
|
|
825
|
+
const normalOps = [];
|
|
826
|
+
const deferredOps = [];
|
|
827
|
+
ops.forEach((operation) => {
|
|
828
|
+
if (isDeferredOperation(operation, abilityMap)) {
|
|
829
|
+
deferredOps.push(operation);
|
|
671
830
|
return;
|
|
672
831
|
}
|
|
673
|
-
|
|
674
|
-
const player = playerSeatMap.get(seat);
|
|
675
|
-
if (player && player.isDead && !player.hasUsedDeadVote) {
|
|
676
|
-
player.hasUsedDeadVote = true;
|
|
677
|
-
}
|
|
678
|
-
});
|
|
832
|
+
normalOps.push(operation);
|
|
679
833
|
});
|
|
834
|
+
applyOpsSequence(normalOps);
|
|
835
|
+
applyOpsSequence(deferredOps);
|
|
836
|
+
};
|
|
837
|
+
const settingsOps = operationsByTimeline.get(-1);
|
|
838
|
+
if (settingsOps) {
|
|
839
|
+
applyOps(settingsOps);
|
|
680
840
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
841
|
+
for (let i = 0; i <= nowTimelineIndex; i += 1) {
|
|
842
|
+
const timeline = timelines[i];
|
|
843
|
+
if (!timeline) {
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
const nominations = timeline.nominations;
|
|
847
|
+
if (nominations && nominations.length > 0) {
|
|
848
|
+
const playerSeatMap = buildPlayerSeatMap(playersWithStatus);
|
|
849
|
+
nominations.forEach((nomination) => {
|
|
850
|
+
const voterSeats = nomination.voterSeats;
|
|
851
|
+
if (!voterSeats || voterSeats.length === 0) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
voterSeats.forEach((seat) => {
|
|
855
|
+
const player = playerSeatMap.get(seat);
|
|
856
|
+
if (player && player.isDead && !player.hasUsedDeadVote) {
|
|
857
|
+
player.hasUsedDeadVote = true;
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
const timelineOps = operationsByTimeline.get(i);
|
|
863
|
+
if (timelineOps) {
|
|
864
|
+
applyOps(timelineOps);
|
|
865
|
+
}
|
|
684
866
|
}
|
|
867
|
+
return playersWithStatus;
|
|
868
|
+
};
|
|
869
|
+
if (!hasGatedLifetime(allAbilities)) {
|
|
870
|
+
return transformEmptyArray(runReplay(null));
|
|
685
871
|
}
|
|
686
|
-
|
|
872
|
+
const groundTruth = runReplay(null);
|
|
873
|
+
const groundTruthSeatMap = buildPlayerSeatMap(groundTruth);
|
|
874
|
+
const final = runReplay(groundTruthSeatMap);
|
|
875
|
+
return transformEmptyArray(final);
|
|
687
876
|
};
|
|
688
877
|
export {
|
|
689
878
|
adjustValueAsNumber,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bct-app/game-engine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6-beta.0",
|
|
4
4
|
"description": "Game engine utilities for BCT",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
"dist",
|
|
16
16
|
"README.md"
|
|
17
17
|
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"clean": "rm -rf dist",
|
|
20
|
+
"build": "tsup src/index.ts --dts --format cjs,esm",
|
|
21
|
+
"dev": "tsup src/index.ts --dts --format cjs,esm --watch",
|
|
22
|
+
"test": "vitest",
|
|
23
|
+
"test:run": "vitest run"
|
|
24
|
+
},
|
|
18
25
|
"keywords": [
|
|
19
26
|
"game-engine",
|
|
20
27
|
"bct"
|
|
@@ -30,19 +37,12 @@
|
|
|
30
37
|
"access": "public"
|
|
31
38
|
},
|
|
32
39
|
"dependencies": {
|
|
33
|
-
"@bct-app/game-model": "
|
|
40
|
+
"@bct-app/game-model": "workspace:*"
|
|
34
41
|
},
|
|
35
42
|
"devDependencies": {
|
|
36
43
|
"@vitest/ui": "^4.1.3",
|
|
37
44
|
"tsup": "^8.0.2",
|
|
38
45
|
"typescript": "^5.9.3",
|
|
39
46
|
"vitest": "^4.1.3"
|
|
40
|
-
},
|
|
41
|
-
"scripts": {
|
|
42
|
-
"clean": "rm -rf dist",
|
|
43
|
-
"build": "tsup src/index.ts --dts --format cjs,esm",
|
|
44
|
-
"dev": "tsup src/index.ts --dts --format cjs,esm --watch",
|
|
45
|
-
"test": "vitest",
|
|
46
|
-
"test:run": "vitest run"
|
|
47
47
|
}
|
|
48
|
-
}
|
|
48
|
+
}
|