@bct-app/game-engine 0.1.2 → 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 +9 -92
- package/dist/index.d.ts +9 -92
- package/dist/index.js +723 -206
- package/dist/index.mjs +723 -206
- package/package.json +10 -3
package/dist/index.js
CHANGED
|
@@ -121,33 +121,43 @@ var getValueFromPayloads = (payloads, idx) => {
|
|
|
121
121
|
return payloads[idx];
|
|
122
122
|
};
|
|
123
123
|
var getSourceValue = (source, payloads) => {
|
|
124
|
-
if (source
|
|
124
|
+
if (source?.from === "PAYLOAD") {
|
|
125
125
|
if (!isNumberOrNumberArray(source.value)) {
|
|
126
126
|
return void 0;
|
|
127
127
|
}
|
|
128
128
|
return getValueFromPayloads(payloads, source.value);
|
|
129
129
|
}
|
|
130
|
-
if (source
|
|
130
|
+
if (source?.from === "CONSTANT") {
|
|
131
131
|
return source.value;
|
|
132
132
|
}
|
|
133
133
|
return null;
|
|
134
134
|
};
|
|
135
|
-
var resolveSourceValue = (source,
|
|
136
|
-
|
|
135
|
+
var resolveSourceValue = (source, writableInputs, payloads, effector, snapshotSeatMap, resolveFromPlayer, isResolvedValue, resolveFromCharacter) => {
|
|
136
|
+
const castOrValidate = (value) => {
|
|
137
|
+
if (!isResolvedValue) {
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
return isResolvedValue(value) ? value : void 0;
|
|
141
|
+
};
|
|
142
|
+
if (source?.from === "PAYLOAD") {
|
|
137
143
|
const payloadValue = getSourceValue(source, payloads);
|
|
138
144
|
const idx = adjustValueAsNumberArray(source.value);
|
|
139
|
-
const
|
|
140
|
-
if (
|
|
145
|
+
const input = writableInputs[idx[0]];
|
|
146
|
+
if (input?.type === "PLAYER") {
|
|
141
147
|
const seat = adjustValueAsNumber(payloadValue);
|
|
142
148
|
const player = typeof seat === "number" ? snapshotSeatMap.get(seat) : void 0;
|
|
143
149
|
return player ? resolveFromPlayer(player) : void 0;
|
|
144
150
|
}
|
|
145
|
-
|
|
151
|
+
if (input?.type === "CHARACTER") {
|
|
152
|
+
const characterId = typeof payloadValue === "string" ? payloadValue : void 0;
|
|
153
|
+
return characterId && resolveFromCharacter ? resolveFromCharacter(characterId) : void 0;
|
|
154
|
+
}
|
|
155
|
+
return castOrValidate(payloadValue);
|
|
146
156
|
}
|
|
147
|
-
if (source
|
|
148
|
-
return source.value;
|
|
157
|
+
if (source?.from === "CONSTANT") {
|
|
158
|
+
return castOrValidate(source.value);
|
|
149
159
|
}
|
|
150
|
-
if (source
|
|
160
|
+
if (source?.from === "EFFECTOR") {
|
|
151
161
|
if (!effector) {
|
|
152
162
|
return void 0;
|
|
153
163
|
}
|
|
@@ -157,7 +167,607 @@ var resolveSourceValue = (source, writableParts, payloads, effector, snapshotSea
|
|
|
157
167
|
return void 0;
|
|
158
168
|
};
|
|
159
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
|
+
|
|
254
|
+
// src/effects/handlers/alignment-change.ts
|
|
255
|
+
var applyAlignmentChange = createEffectHandler("ALIGNMENT_CHANGE", ({
|
|
256
|
+
effect,
|
|
257
|
+
payloads,
|
|
258
|
+
effector,
|
|
259
|
+
writableInputs,
|
|
260
|
+
getSnapshotSeatMap,
|
|
261
|
+
makePlayersEffect
|
|
262
|
+
}) => {
|
|
263
|
+
const alignment = resolveSourceValue(
|
|
264
|
+
effect.source,
|
|
265
|
+
writableInputs,
|
|
266
|
+
payloads,
|
|
267
|
+
effector,
|
|
268
|
+
getSnapshotSeatMap(),
|
|
269
|
+
(player) => player.alignment,
|
|
270
|
+
(isResolvedValue) => typeof isResolvedValue === "string" && (isResolvedValue === "GOOD" || isResolvedValue === "EVIL")
|
|
271
|
+
);
|
|
272
|
+
if (!alignment) {
|
|
273
|
+
console.warn("Alignment not found for ALIGNMENT_CHANGE effect:", effect.source);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
makePlayersEffect.forEach((player) => {
|
|
277
|
+
player.alignment = alignment;
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// src/effects/handlers/character-change.ts
|
|
282
|
+
var resolveCharacterId = (value) => {
|
|
283
|
+
if (typeof value === "string") {
|
|
284
|
+
return value;
|
|
285
|
+
}
|
|
286
|
+
if (Array.isArray(value) && typeof value[0] === "string") {
|
|
287
|
+
return value[0];
|
|
288
|
+
}
|
|
289
|
+
return void 0;
|
|
290
|
+
};
|
|
291
|
+
var applyCharacterChange = createEffectHandler("CHARACTER_CHANGE", ({
|
|
292
|
+
effect,
|
|
293
|
+
payloads,
|
|
294
|
+
effector,
|
|
295
|
+
writableInputs,
|
|
296
|
+
getSnapshotSeatMap,
|
|
297
|
+
characterMap,
|
|
298
|
+
makePlayersEffect
|
|
299
|
+
}) => {
|
|
300
|
+
const resolvedCharacterId = resolveSourceValue(
|
|
301
|
+
effect.source,
|
|
302
|
+
writableInputs,
|
|
303
|
+
payloads,
|
|
304
|
+
effector,
|
|
305
|
+
getSnapshotSeatMap(),
|
|
306
|
+
(player) => player.characterId
|
|
307
|
+
);
|
|
308
|
+
const characterId = resolveCharacterId(resolvedCharacterId);
|
|
309
|
+
if (!characterId) {
|
|
310
|
+
console.warn("Character ID not found for CHARACTER_CHANGE effect:", effect.source);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const newCharacter = characterMap.get(characterId);
|
|
314
|
+
if (!newCharacter) {
|
|
315
|
+
console.warn("Character not found for CHARACTER_CHANGE effect:", characterId);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const abilityIds = newCharacter.abilities.map((ability) => ability.id);
|
|
319
|
+
makePlayersEffect.forEach((player) => {
|
|
320
|
+
player.characterId = newCharacter.id;
|
|
321
|
+
player.abilities = abilityIds;
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// src/effects/handlers/character-count-change.ts
|
|
326
|
+
var applyCharacterCountChange = createEffectHandler("CHARACTER_COUNT_CHANGE", () => {
|
|
327
|
+
console.warn("CHARACTER_COUNT_CHANGE effect handling not implemented yet.");
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// src/effects/handlers/game-change.ts
|
|
331
|
+
var applyGameChange = createEffectHandler("GAME_CHANGE", () => {
|
|
332
|
+
console.warn("GAME_CHANGE effect handling not implemented yet.");
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// src/effects/handlers/perceived-character-change.ts
|
|
336
|
+
var resolveCharacterId2 = (value) => {
|
|
337
|
+
if (typeof value === "string") {
|
|
338
|
+
return value;
|
|
339
|
+
}
|
|
340
|
+
if (Array.isArray(value) && typeof value[0] === "string") {
|
|
341
|
+
return value[0];
|
|
342
|
+
}
|
|
343
|
+
return void 0;
|
|
344
|
+
};
|
|
345
|
+
var applyPerceivedCharacterChange = createEffectHandler("PERCEIVED_CHARACTER_CHANGE", ({
|
|
346
|
+
effect,
|
|
347
|
+
payloads,
|
|
348
|
+
effector,
|
|
349
|
+
writableInputs,
|
|
350
|
+
getSnapshotSeatMap,
|
|
351
|
+
characterMap,
|
|
352
|
+
makePlayersEffect
|
|
353
|
+
}) => {
|
|
354
|
+
const resolvedCharacterId = resolveSourceValue(
|
|
355
|
+
effect.source,
|
|
356
|
+
writableInputs,
|
|
357
|
+
payloads,
|
|
358
|
+
effector,
|
|
359
|
+
getSnapshotSeatMap(),
|
|
360
|
+
(player) => player.characterId
|
|
361
|
+
);
|
|
362
|
+
const characterId = resolveCharacterId2(resolvedCharacterId);
|
|
363
|
+
if (!characterId) {
|
|
364
|
+
console.warn("Character ID not found for PERCEIVED_CHARACTER_CHANGE effect:", effect.source);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const newCharacter = characterMap.get(characterId);
|
|
368
|
+
if (!newCharacter) {
|
|
369
|
+
console.warn("Character not found for PERCEIVED_CHARACTER_CHANGE effect:", characterId);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
makePlayersEffect.forEach((player) => {
|
|
373
|
+
player.perceivedCharacter = {
|
|
374
|
+
characterId: newCharacter.id,
|
|
375
|
+
abilities: newCharacter.abilities.map((ability) => ability.id),
|
|
376
|
+
asCharacter: effect.followPriority || false
|
|
377
|
+
};
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// src/effects/handlers/reminder-add.ts
|
|
382
|
+
var applyReminderAdd = createEffectHandler("REMINDER_ADD", ({
|
|
383
|
+
effect,
|
|
384
|
+
payloads,
|
|
385
|
+
nowTimelineIndex,
|
|
386
|
+
operationInTimelineIdx,
|
|
387
|
+
makePlayersEffect
|
|
388
|
+
}) => {
|
|
389
|
+
const maybeReminder = getSourceValue(effect.source, payloads);
|
|
390
|
+
if (!isReminder(maybeReminder)) {
|
|
391
|
+
console.warn("Invalid reminder data for REMINDER_ADD effect:", maybeReminder);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const timelineDistance = nowTimelineIndex - operationInTimelineIdx;
|
|
395
|
+
if (typeof maybeReminder.duration === "number" && maybeReminder.duration > 0 && timelineDistance >= maybeReminder.duration) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
makePlayersEffect.forEach((player) => {
|
|
399
|
+
if (!player.reminders) {
|
|
400
|
+
player.reminders = [];
|
|
401
|
+
}
|
|
402
|
+
player.reminders.push(maybeReminder);
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// src/effects/handlers/reminder-remove.ts
|
|
407
|
+
var applyReminderRemove = createEffectHandler("REMINDER_REMOVE", ({ effect, payloads, playerSeatMap }) => {
|
|
408
|
+
const maybeReminder = getSourceValue(effect.source, payloads);
|
|
409
|
+
if (!isPlayerReminderArray(maybeReminder)) {
|
|
410
|
+
console.warn("Invalid reminder data for REMINDER_REMOVE effect:", maybeReminder);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
maybeReminder.sort((left, right) => right.index - left.index).forEach((reminder) => {
|
|
414
|
+
const player = playerSeatMap.get(reminder.playerSeat);
|
|
415
|
+
if (!player) {
|
|
416
|
+
console.warn("Player not found for REMINDER_REMOVE effect:", reminder.playerSeat);
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (!player.reminders) {
|
|
420
|
+
player.reminders = [];
|
|
421
|
+
}
|
|
422
|
+
player.reminders = player.reminders.filter((_value, index) => index !== reminder.index);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// src/effects/handlers/seat-change.ts
|
|
427
|
+
var applySeatChange = createEffectHandler("SEAT_CHANGE", () => {
|
|
428
|
+
console.warn("SEAT_CHANGE effect handling not implemented yet.");
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// src/effects/handlers/status-change.ts
|
|
432
|
+
var applyStatusChange = createEffectHandler("STATUS_CHANGE", ({ effect, payloads, makePlayersEffect }) => {
|
|
433
|
+
const maybeStatus = getSourceValue(effect.source, payloads);
|
|
434
|
+
if (maybeStatus === "DEAD") {
|
|
435
|
+
makePlayersEffect.forEach((player) => {
|
|
436
|
+
player.isDead = true;
|
|
437
|
+
});
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
if (maybeStatus === "ALIVE") {
|
|
441
|
+
makePlayersEffect.forEach((player) => {
|
|
442
|
+
player.isDead = false;
|
|
443
|
+
});
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
console.warn("Invalid status value for STATUS_CHANGE effect:", maybeStatus);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// src/effects/registry.ts
|
|
450
|
+
var effectHandlers = {
|
|
451
|
+
ABILITY_CHANGE: applyAbilityChange,
|
|
452
|
+
ALIGNMENT_CHANGE: applyAlignmentChange,
|
|
453
|
+
CHARACTER_CHANGE: applyCharacterChange,
|
|
454
|
+
CHARACTER_COUNT_CHANGE: applyCharacterCountChange,
|
|
455
|
+
GAME_CHANGE: applyGameChange,
|
|
456
|
+
PERCEIVED_CHARACTER_CHANGE: applyPerceivedCharacterChange,
|
|
457
|
+
REMINDER_ADD: applyReminderAdd,
|
|
458
|
+
REMINDER_REMOVE: applyReminderRemove,
|
|
459
|
+
SEAT_CHANGE: applySeatChange,
|
|
460
|
+
STATUS_CHANGE: applyStatusChange
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// src/effects/resolve-targets.ts
|
|
464
|
+
var isPayloadRef = (value) => {
|
|
465
|
+
if (!value || typeof value !== "object") {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
const candidate = value;
|
|
469
|
+
if (candidate.from !== "PAYLOAD") {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
return typeof candidate.value === "number" || Array.isArray(candidate.value);
|
|
473
|
+
};
|
|
474
|
+
var resolveDynamicValue = (value, payloads, isExpected) => {
|
|
475
|
+
if (typeof value === "undefined") {
|
|
476
|
+
return void 0;
|
|
477
|
+
}
|
|
478
|
+
if (!isPayloadRef(value)) {
|
|
479
|
+
return isExpected(value) ? value : void 0;
|
|
480
|
+
}
|
|
481
|
+
const resolved = getValueFromPayloads(payloads, value.value);
|
|
482
|
+
if (!isExpected(resolved)) {
|
|
483
|
+
return void 0;
|
|
484
|
+
}
|
|
485
|
+
return resolved;
|
|
486
|
+
};
|
|
487
|
+
var getSortedPlayers = (playerSeatMap) => {
|
|
488
|
+
return [...playerSeatMap.values()].sort((left, right) => left.seat - right.seat);
|
|
489
|
+
};
|
|
490
|
+
var getCircularDistance = (anchorIdx, targetIdx, total) => {
|
|
491
|
+
const clockwise = (targetIdx - anchorIdx + total) % total;
|
|
492
|
+
const anticlockwise = (anchorIdx - targetIdx + total) % total;
|
|
493
|
+
return Math.min(clockwise, anticlockwise);
|
|
494
|
+
};
|
|
495
|
+
var getAnchorSeat = (dynamicTarget, payloads, effector) => {
|
|
496
|
+
const anchor = dynamicTarget.anchor;
|
|
497
|
+
if (!anchor || anchor.from === "EFFECTOR") {
|
|
498
|
+
return effector?.seat;
|
|
499
|
+
}
|
|
500
|
+
if (anchor.from === "STATIC") {
|
|
501
|
+
return anchor.value;
|
|
502
|
+
}
|
|
503
|
+
const payloadSeat = getValueFromPayloads(payloads, anchor.value);
|
|
504
|
+
return typeof payloadSeat === "number" ? payloadSeat : void 0;
|
|
505
|
+
};
|
|
506
|
+
var resolveSelectorScope = (dynamicTarget, payloads) => {
|
|
507
|
+
return resolveDynamicValue(
|
|
508
|
+
dynamicTarget.selector?.scope,
|
|
509
|
+
payloads,
|
|
510
|
+
(value) => value === "BOTH_SIDES" || value === "LEFT_SIDE" || value === "RIGHT_SIDE"
|
|
511
|
+
) ?? "BOTH_SIDES";
|
|
512
|
+
};
|
|
513
|
+
var getCandidatesBySelector = (dynamicTarget, sortedPlayers, anchorSeat, payloads) => {
|
|
514
|
+
const selector = dynamicTarget.selector;
|
|
515
|
+
if (!selector) {
|
|
516
|
+
return sortedPlayers;
|
|
517
|
+
}
|
|
518
|
+
const scope = resolveSelectorScope(dynamicTarget, payloads);
|
|
519
|
+
if (!anchorSeat) {
|
|
520
|
+
return [];
|
|
521
|
+
}
|
|
522
|
+
const anchorIdx = sortedPlayers.findIndex((player) => player.seat === anchorSeat);
|
|
523
|
+
if (anchorIdx < 0) {
|
|
524
|
+
return [];
|
|
525
|
+
}
|
|
526
|
+
const total = sortedPlayers.length;
|
|
527
|
+
if (scope === "LEFT_SIDE") {
|
|
528
|
+
const left2 = [];
|
|
529
|
+
for (let step = 1; step < total; step += 1) {
|
|
530
|
+
left2.push(sortedPlayers[(anchorIdx - step + total) % total]);
|
|
531
|
+
}
|
|
532
|
+
return left2;
|
|
533
|
+
}
|
|
534
|
+
if (scope === "RIGHT_SIDE") {
|
|
535
|
+
const right2 = [];
|
|
536
|
+
for (let step = 1; step < total; step += 1) {
|
|
537
|
+
right2.push(sortedPlayers[(anchorIdx + step) % total]);
|
|
538
|
+
}
|
|
539
|
+
return right2;
|
|
540
|
+
}
|
|
541
|
+
const left = [];
|
|
542
|
+
const right = [];
|
|
543
|
+
for (let step = 1; step < total; step += 1) {
|
|
544
|
+
left.push(sortedPlayers[(anchorIdx - step + total) % total]);
|
|
545
|
+
right.push(sortedPlayers[(anchorIdx + step) % total]);
|
|
546
|
+
}
|
|
547
|
+
return [...left, ...right];
|
|
548
|
+
};
|
|
549
|
+
var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
550
|
+
if (condition.field === "IS_DEAD") {
|
|
551
|
+
const expected = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "boolean");
|
|
552
|
+
if (typeof expected !== "boolean") {
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
return Boolean(player.isDead) === expected;
|
|
556
|
+
}
|
|
557
|
+
if (condition.field === "SEAT") {
|
|
558
|
+
if (condition.operator === "IN") {
|
|
559
|
+
const expectedSeats = resolveDynamicValue(condition.value, payloads, (value) => Array.isArray(value) && value.every((item) => typeof item === "number"));
|
|
560
|
+
return Array.isArray(expectedSeats) && expectedSeats.includes(player.seat);
|
|
561
|
+
}
|
|
562
|
+
const expectedSeat = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "number");
|
|
563
|
+
return typeof expectedSeat === "number" && player.seat === expectedSeat;
|
|
564
|
+
}
|
|
565
|
+
const character = characterMap.get(player.characterId);
|
|
566
|
+
const kind = character?.kind;
|
|
567
|
+
if (!kind) {
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
if (condition.operator === "IN") {
|
|
571
|
+
const expectedKinds = resolveDynamicValue(condition.value, payloads, (value) => Array.isArray(value) && value.every((item) => typeof item === "string"));
|
|
572
|
+
return Array.isArray(expectedKinds) && expectedKinds.includes(kind);
|
|
573
|
+
}
|
|
574
|
+
const expectedKind = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
|
|
575
|
+
return typeof expectedKind === "string" && kind === expectedKind;
|
|
576
|
+
};
|
|
577
|
+
var filterByWhere = (candidates, dynamicTarget, payloads, characterMap) => {
|
|
578
|
+
const where = dynamicTarget.where;
|
|
579
|
+
const conditions = where?.conditions ?? [];
|
|
580
|
+
if (conditions.length === 0) {
|
|
581
|
+
return candidates;
|
|
582
|
+
}
|
|
583
|
+
const isAllMode = where?.mode !== "ANY";
|
|
584
|
+
return candidates.filter((player) => {
|
|
585
|
+
const results = conditions.map((condition) => matchesCondition(player, condition, payloads, characterMap));
|
|
586
|
+
return isAllMode ? results.every(Boolean) : results.some(Boolean);
|
|
587
|
+
});
|
|
588
|
+
};
|
|
589
|
+
var applySort = (candidates, dynamicTarget, payloads, sortedPlayers, anchorSeat) => {
|
|
590
|
+
const scope = resolveSelectorScope(dynamicTarget, payloads);
|
|
591
|
+
if (scope !== "BOTH_SIDES") {
|
|
592
|
+
return [...candidates];
|
|
593
|
+
}
|
|
594
|
+
if (!anchorSeat || sortedPlayers.length <= 1) {
|
|
595
|
+
return [...candidates].sort((left, right) => left.seat - right.seat);
|
|
596
|
+
}
|
|
597
|
+
const seatIndexMap = new Map(sortedPlayers.map((player, idx) => [player.seat, idx]));
|
|
598
|
+
const anchorIdx = seatIndexMap.get(anchorSeat);
|
|
599
|
+
if (typeof anchorIdx !== "number") {
|
|
600
|
+
return [...candidates].sort((left, right) => left.seat - right.seat);
|
|
601
|
+
}
|
|
602
|
+
return [...candidates].sort((left, right) => {
|
|
603
|
+
const leftIdx = seatIndexMap.get(left.seat);
|
|
604
|
+
const rightIdx = seatIndexMap.get(right.seat);
|
|
605
|
+
if (typeof leftIdx !== "number" || typeof rightIdx !== "number") {
|
|
606
|
+
return left.seat - right.seat;
|
|
607
|
+
}
|
|
608
|
+
const leftDistance = getCircularDistance(anchorIdx, leftIdx, sortedPlayers.length);
|
|
609
|
+
const rightDistance = getCircularDistance(anchorIdx, rightIdx, sortedPlayers.length);
|
|
610
|
+
if (leftDistance !== rightDistance) {
|
|
611
|
+
return leftDistance - rightDistance;
|
|
612
|
+
}
|
|
613
|
+
return left.seat - right.seat;
|
|
614
|
+
});
|
|
615
|
+
};
|
|
616
|
+
var pickTargets = (sortedCandidates, dynamicTarget, payloads) => {
|
|
617
|
+
const dedupedCandidates = [...new Map(sortedCandidates.map((player) => [player.seat, player])).values()];
|
|
618
|
+
const configuredLimit = resolveDynamicValue(dynamicTarget.limit, payloads, (value) => typeof value === "number");
|
|
619
|
+
if (typeof configuredLimit === "undefined") {
|
|
620
|
+
return dedupedCandidates;
|
|
621
|
+
}
|
|
622
|
+
if (configuredLimit <= 0) {
|
|
623
|
+
return [];
|
|
624
|
+
}
|
|
625
|
+
return dedupedCandidates.slice(0, configuredLimit);
|
|
626
|
+
};
|
|
627
|
+
var resolveEffectTargets = ({
|
|
628
|
+
effect,
|
|
629
|
+
operation,
|
|
630
|
+
payloads,
|
|
631
|
+
effector,
|
|
632
|
+
playerSeatMap,
|
|
633
|
+
characterMap
|
|
634
|
+
}) => {
|
|
635
|
+
if (!("target" in effect)) {
|
|
636
|
+
return [];
|
|
637
|
+
}
|
|
638
|
+
if (effect.target.from === "EFFECTOR") {
|
|
639
|
+
if (!effector) {
|
|
640
|
+
console.warn("Effector player not found for operation:", operation, "effector index:", operation.effector);
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
return [effector];
|
|
644
|
+
}
|
|
645
|
+
if (effect.target.from === "PAYLOAD") {
|
|
646
|
+
const seatValue = getValueFromPayloads(payloads, effect.target.value);
|
|
647
|
+
if (typeof seatValue !== "number" && !Array.isArray(seatValue)) {
|
|
648
|
+
console.warn("Expected seat number or array of seat numbers from payloads, got:", seatValue);
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
const seats = Array.isArray(seatValue) ? seatValue : [seatValue];
|
|
652
|
+
return seats.map((seat) => playerSeatMap.get(seat)).filter((player) => Boolean(player));
|
|
653
|
+
}
|
|
654
|
+
if (effect.target.from === "DYNAMIC") {
|
|
655
|
+
const dynamicTarget = effect.target;
|
|
656
|
+
const sortedPlayers = getSortedPlayers(playerSeatMap);
|
|
657
|
+
const anchorSeat = getAnchorSeat(dynamicTarget, payloads, effector);
|
|
658
|
+
const candidates = getCandidatesBySelector(dynamicTarget, sortedPlayers, anchorSeat, payloads);
|
|
659
|
+
const filtered = filterByWhere(candidates, dynamicTarget, payloads, characterMap);
|
|
660
|
+
const ordered = applySort(filtered, dynamicTarget, payloads, sortedPlayers, anchorSeat);
|
|
661
|
+
return pickTargets(ordered, dynamicTarget, payloads);
|
|
662
|
+
}
|
|
663
|
+
return [];
|
|
664
|
+
};
|
|
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
|
+
|
|
160
759
|
// src/apply-operation.ts
|
|
760
|
+
var isDeferredOperation = (operation, abilityMap) => {
|
|
761
|
+
const ability = abilityMap.get(operation.abilityId);
|
|
762
|
+
return ability?.executionTiming === "DEFER_TO_END";
|
|
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
|
+
};
|
|
161
771
|
var applyOperationToPlayers = ({
|
|
162
772
|
players,
|
|
163
773
|
operations,
|
|
@@ -173,7 +783,6 @@ var applyOperationToPlayers = ({
|
|
|
173
783
|
const characterMap = new Map(characters.map((character) => [character.id, character]));
|
|
174
784
|
const operationTimelineMap = /* @__PURE__ */ new Map();
|
|
175
785
|
timelines.forEach((timeline, idx) => timeline.operations?.forEach((op) => operationTimelineMap.set(op, idx)));
|
|
176
|
-
const playersWithStatus = copyPlayers(players);
|
|
177
786
|
const nowTimelineIndex = typeof timelineIndexAtNow === "number" ? timelineIndexAtNow : timelines.length - 1;
|
|
178
787
|
const operationsByTimeline = /* @__PURE__ */ new Map();
|
|
179
788
|
operations.forEach((operation) => {
|
|
@@ -183,216 +792,124 @@ var applyOperationToPlayers = ({
|
|
|
183
792
|
}
|
|
184
793
|
operationsByTimeline.get(timelineIndex)?.push(operation);
|
|
185
794
|
});
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const playerSeatMap = buildPlayerSeatMap(playersWithStatus);
|
|
195
|
-
const effector = playerSeatMap.get(operation.effector);
|
|
196
|
-
const payloads = operation.payloads || [];
|
|
197
|
-
const writableParts = ability.inputs.filter((part) => !part.readonly);
|
|
198
|
-
let snapshotSeatMap = null;
|
|
199
|
-
const getSnapshotSeatMap = () => {
|
|
200
|
-
if (!snapshotSeatMap) {
|
|
201
|
-
snapshotSeatMap = buildPlayerSeatMap(copyPlayers(playersWithStatus));
|
|
202
|
-
}
|
|
203
|
-
return snapshotSeatMap;
|
|
204
|
-
};
|
|
205
|
-
ability.effects.forEach((effect) => {
|
|
206
|
-
const makePlayersEffect = [];
|
|
207
|
-
if (effect.type === "GAME_CHANGE" || effect.type === "CHARACTER_COUNT_CHANGE") {
|
|
208
|
-
console.warn(effect.type + " effect handling not implemented yet.");
|
|
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);
|
|
209
803
|
return;
|
|
210
804
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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));
|
|
215
813
|
}
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
814
|
+
return snapshotSeatMap;
|
|
815
|
+
};
|
|
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) {
|
|
221
826
|
return;
|
|
222
827
|
}
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
828
|
+
const makePlayersEffect = evaluateLifetime({
|
|
829
|
+
effect,
|
|
830
|
+
operationTimelineIdx: operationInTimelineIdx,
|
|
831
|
+
nowTimelineIndex,
|
|
832
|
+
timelines,
|
|
833
|
+
effector,
|
|
834
|
+
targets: resolvedTargets,
|
|
835
|
+
snapshotSeatMap: lifetimeSnapshotSeatMap,
|
|
836
|
+
characterMap
|
|
229
837
|
});
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
switch (effect.type) {
|
|
234
|
-
case "ABILITY_CHANGE": {
|
|
235
|
-
console.warn("ABILITY_CHANGE effect handling not implemented yet.");
|
|
236
|
-
break;
|
|
237
|
-
}
|
|
238
|
-
case "ALIGNMENT_CHANGE": {
|
|
239
|
-
const resolved = resolveSourceValue(
|
|
240
|
-
effect.source,
|
|
241
|
-
writableParts,
|
|
242
|
-
payloads,
|
|
243
|
-
effector,
|
|
244
|
-
getSnapshotSeatMap(),
|
|
245
|
-
(player) => player.alignment
|
|
246
|
-
);
|
|
247
|
-
let alignment;
|
|
248
|
-
if (resolved === "GOOD" || resolved === "EVIL") {
|
|
249
|
-
alignment = resolved;
|
|
250
|
-
}
|
|
251
|
-
if (!alignment) {
|
|
252
|
-
console.warn("Alignment not found for ALIGNMENT_CHANGE effect:", effect.source);
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
makePlayersEffect.forEach((player) => {
|
|
256
|
-
player.alignment = alignment;
|
|
257
|
-
});
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
case "CHARACTER_CHANGE":
|
|
261
|
-
case "PERCEIVED_CHARACTER_CHANGE": {
|
|
262
|
-
const resolvedCharacterId = resolveSourceValue(
|
|
263
|
-
effect.source,
|
|
264
|
-
writableParts,
|
|
265
|
-
payloads,
|
|
266
|
-
effector,
|
|
267
|
-
getSnapshotSeatMap(),
|
|
268
|
-
(player) => player.characterId
|
|
269
|
-
);
|
|
270
|
-
let characterId;
|
|
271
|
-
if (typeof resolvedCharacterId === "string") {
|
|
272
|
-
characterId = resolvedCharacterId;
|
|
273
|
-
} else if (Array.isArray(resolvedCharacterId) && typeof resolvedCharacterId[0] === "string") {
|
|
274
|
-
characterId = resolvedCharacterId[0];
|
|
275
|
-
}
|
|
276
|
-
if (!characterId) {
|
|
277
|
-
console.warn("Character ID not found for " + effect.type + " effect:", effect.source);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
const newCharacter = characterMap.get(characterId);
|
|
281
|
-
if (!newCharacter) {
|
|
282
|
-
console.warn("Character not found for " + effect.type + " effect:", characterId);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
if (effect.type === "CHARACTER_CHANGE") {
|
|
286
|
-
const abilityIds = newCharacter.abilities.map((ability2) => ability2.id);
|
|
287
|
-
makePlayersEffect.forEach((player) => {
|
|
288
|
-
player.characterId = newCharacter.id;
|
|
289
|
-
player.abilities = abilityIds;
|
|
290
|
-
});
|
|
291
|
-
} else {
|
|
292
|
-
makePlayersEffect.forEach((player) => {
|
|
293
|
-
player.perceivedCharacter = {
|
|
294
|
-
characterId: newCharacter.id,
|
|
295
|
-
abilities: newCharacter.abilities.map((ability2) => ability2.id),
|
|
296
|
-
asCharacter: effect.followPriority || false
|
|
297
|
-
};
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
break;
|
|
301
|
-
}
|
|
302
|
-
case "REMINDER_ADD": {
|
|
303
|
-
const maybeReminder = getSourceValue(effect.source, payloads);
|
|
304
|
-
if (!isReminder(maybeReminder)) {
|
|
305
|
-
console.warn("Invalid reminder data for REMINDER_ADD effect:", maybeReminder);
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
const timelineDistance = nowTimelineIndex - operationInTimelineIdx;
|
|
309
|
-
if (typeof maybeReminder.duration === "number" && maybeReminder.duration > 0 && timelineDistance >= maybeReminder.duration) {
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
makePlayersEffect.forEach((player) => {
|
|
313
|
-
if (!player.reminders) {
|
|
314
|
-
player.reminders = [];
|
|
315
|
-
}
|
|
316
|
-
player.reminders.push(maybeReminder);
|
|
317
|
-
});
|
|
318
|
-
break;
|
|
319
|
-
}
|
|
320
|
-
case "REMINDER_REMOVE": {
|
|
321
|
-
const maybeReminder = getSourceValue(effect.source, payloads);
|
|
322
|
-
if (!isPlayerReminderArray(maybeReminder)) {
|
|
323
|
-
console.warn("Invalid reminder data for REMINDER_REMOVE effect:", maybeReminder);
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
maybeReminder.sort((left, right) => right.index - left.index).forEach((reminder) => {
|
|
327
|
-
const player = playerSeatMap.get(reminder.playerSeat);
|
|
328
|
-
if (!player) {
|
|
329
|
-
console.warn("Player not found for REMINDER_REMOVE effect:", reminder.playerSeat);
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
if (!player.reminders) {
|
|
333
|
-
player.reminders = [];
|
|
334
|
-
}
|
|
335
|
-
player.reminders = player.reminders.filter((_value, index) => index !== reminder.index);
|
|
336
|
-
});
|
|
337
|
-
break;
|
|
338
|
-
}
|
|
339
|
-
case "SEAT_CHANGE": {
|
|
340
|
-
console.warn("SEAT_CHANGE effect handling not implemented yet.");
|
|
341
|
-
break;
|
|
342
|
-
}
|
|
343
|
-
case "STATUS_CHANGE": {
|
|
344
|
-
const maybeStatus = getSourceValue(effect.source, payloads);
|
|
345
|
-
if (maybeStatus === "DEAD") {
|
|
346
|
-
makePlayersEffect.forEach((player) => {
|
|
347
|
-
player.isDead = true;
|
|
348
|
-
});
|
|
349
|
-
} else if (maybeStatus === "ALIVE") {
|
|
350
|
-
makePlayersEffect.forEach((player) => {
|
|
351
|
-
player.isDead = false;
|
|
352
|
-
});
|
|
353
|
-
} else {
|
|
354
|
-
console.warn("Invalid status value for STATUS_CHANGE effect:", maybeStatus);
|
|
355
|
-
}
|
|
356
|
-
break;
|
|
357
|
-
}
|
|
358
|
-
default: {
|
|
838
|
+
const handler = effectHandlers[effect.type];
|
|
839
|
+
if (!handler) {
|
|
359
840
|
console.warn("Unknown effect type:", effect.type);
|
|
841
|
+
return;
|
|
360
842
|
}
|
|
361
|
-
|
|
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
|
+
});
|
|
362
859
|
});
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const timeline = timelines[i];
|
|
371
|
-
if (!timeline) {
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
374
|
-
const nominations = timeline.nominations;
|
|
375
|
-
if (nominations && nominations.length > 0) {
|
|
376
|
-
const playerSeatMap = buildPlayerSeatMap(playersWithStatus);
|
|
377
|
-
nominations.forEach((nomination) => {
|
|
378
|
-
const voterSeats = nomination.voterSeats;
|
|
379
|
-
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);
|
|
380
867
|
return;
|
|
381
868
|
}
|
|
382
|
-
|
|
383
|
-
const player = playerSeatMap.get(seat);
|
|
384
|
-
if (player && player.isDead && !player.hasUsedDeadVote) {
|
|
385
|
-
player.hasUsedDeadVote = true;
|
|
386
|
-
}
|
|
387
|
-
});
|
|
869
|
+
normalOps.push(operation);
|
|
388
870
|
});
|
|
871
|
+
applyOpsSequence(normalOps);
|
|
872
|
+
applyOpsSequence(deferredOps);
|
|
873
|
+
};
|
|
874
|
+
const settingsOps = operationsByTimeline.get(-1);
|
|
875
|
+
if (settingsOps) {
|
|
876
|
+
applyOps(settingsOps);
|
|
389
877
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
+
}
|
|
393
903
|
}
|
|
904
|
+
return playersWithStatus;
|
|
905
|
+
};
|
|
906
|
+
if (!hasGatedLifetime(allAbilities)) {
|
|
907
|
+
return transformEmptyArray(runReplay(null));
|
|
394
908
|
}
|
|
395
|
-
|
|
909
|
+
const groundTruth = runReplay(null);
|
|
910
|
+
const groundTruthSeatMap = buildPlayerSeatMap(groundTruth);
|
|
911
|
+
const final = runReplay(groundTruthSeatMap);
|
|
912
|
+
return transformEmptyArray(final);
|
|
396
913
|
};
|
|
397
914
|
// Annotate the CommonJS export names for ESM import in node:
|
|
398
915
|
0 && (module.exports = {
|