@bct-app/game-engine 0.1.18 → 0.1.20
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 +149 -2
- package/dist/index.d.ts +149 -2
- package/dist/index.js +681 -36
- package/dist/index.mjs +665 -36
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -463,19 +463,18 @@ var applyStatusChange = createEffectHandler("STATUS_CHANGE", ({
|
|
|
463
463
|
effect,
|
|
464
464
|
payloads,
|
|
465
465
|
makePlayersEffect,
|
|
466
|
-
|
|
466
|
+
operationTurn,
|
|
467
467
|
abilityMap,
|
|
468
468
|
operation
|
|
469
469
|
}) => {
|
|
470
470
|
const maybeStatus = getSourceValue(effect.source, payloads);
|
|
471
471
|
if (maybeStatus === "DEAD") {
|
|
472
472
|
const ability = abilityMap.get(operation.abilityId);
|
|
473
|
-
const turn = operationInTimelineIdx >= 0 ? operationInTimelineIdx : 0;
|
|
474
473
|
const cause = effect.deathCause ?? (ability?.category === "STORYTELLER" ? "STORYTELLER" : "ABILITY");
|
|
475
474
|
makePlayersEffect.forEach((player) => {
|
|
476
475
|
player.isDead = true;
|
|
477
476
|
player.deathCause = cause;
|
|
478
|
-
player.deathTurn =
|
|
477
|
+
player.deathTurn = operationTurn;
|
|
479
478
|
});
|
|
480
479
|
return;
|
|
481
480
|
}
|
|
@@ -505,6 +504,521 @@ var effectHandlers = {
|
|
|
505
504
|
STATUS_CHANGE: applyStatusChange
|
|
506
505
|
};
|
|
507
506
|
|
|
507
|
+
// src/dsl/context.ts
|
|
508
|
+
var decoratePlayer = (player, characterMap, currentTurn) => {
|
|
509
|
+
const isDead = Boolean(player.isDead);
|
|
510
|
+
const retainsBypass = isDead && (player.retainsAbility === "ALIVENESS" || player.retainsAbility === "ALL");
|
|
511
|
+
const wasExecuted = isDead && player.deathCause === "EXECUTION";
|
|
512
|
+
return {
|
|
513
|
+
...player,
|
|
514
|
+
characterKind: characterMap.get(player.characterId)?.kind,
|
|
515
|
+
reminderMarks: (player.reminders ?? []).map((r) => r.mark),
|
|
516
|
+
effectiveAlive: !isDead || retainsBypass,
|
|
517
|
+
diedThisTurn: isDead && typeof player.deathTurn === "number" && player.deathTurn === currentTurn,
|
|
518
|
+
wasExecuted,
|
|
519
|
+
wasExecutedToday: wasExecuted && player.deathTurn === currentTurn
|
|
520
|
+
};
|
|
521
|
+
};
|
|
522
|
+
var isExecutionTimeline = (tl) => {
|
|
523
|
+
if (!tl) return false;
|
|
524
|
+
return (tl.operations ?? []).some((op) => op.kind === "execution");
|
|
525
|
+
};
|
|
526
|
+
var buildDslContextFromDerived = (derived, payload = []) => {
|
|
527
|
+
const players = derived.players.map((p) => decoratePlayer(p, derived.characterMap, derived.currentTurn));
|
|
528
|
+
const effector = players.find((p) => p.seat === derived.effectorSeat);
|
|
529
|
+
const aliveCount = players.filter((p) => !p.isDead).length;
|
|
530
|
+
const deadCount = players.length - aliveCount;
|
|
531
|
+
const executedTodayCount = players.filter(
|
|
532
|
+
(p) => p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === derived.currentTurn
|
|
533
|
+
).length;
|
|
534
|
+
const nominatedTodayCount = derived.todayNominations.length;
|
|
535
|
+
const playerDiedToday = players.some(
|
|
536
|
+
(p) => p.isDead && typeof p.deathTurn === "number" && p.deathTurn === derived.currentTurn
|
|
537
|
+
);
|
|
538
|
+
return {
|
|
539
|
+
effector,
|
|
540
|
+
players,
|
|
541
|
+
operations: {
|
|
542
|
+
all: derived.allOperations,
|
|
543
|
+
today: derived.todayOperations,
|
|
544
|
+
tonight: derived.tonightOperations,
|
|
545
|
+
yesterdayDay: derived.yesterdayDayOperations,
|
|
546
|
+
yesterdayNight: derived.yesterdayNightOperations
|
|
547
|
+
},
|
|
548
|
+
nominations: {
|
|
549
|
+
all: derived.allNominations,
|
|
550
|
+
today: derived.todayNominations,
|
|
551
|
+
yesterday: derived.yesterdayNominations
|
|
552
|
+
},
|
|
553
|
+
currentTimeline: {
|
|
554
|
+
turn: derived.currentTurn,
|
|
555
|
+
phase: derived.currentPhase,
|
|
556
|
+
time: derived.currentPhase === "NIGHT" ? "night" : derived.currentPhase === "DAY" ? "day" : void 0,
|
|
557
|
+
isFirstOfPhase: derived.isFirstOfPhase,
|
|
558
|
+
isExecution: (derived.currentPhase === "DAY" ? derived.todayOperations : derived.tonightOperations).some((op) => op.kind === "execution")
|
|
559
|
+
},
|
|
560
|
+
global: {
|
|
561
|
+
aliveCount,
|
|
562
|
+
deadCount,
|
|
563
|
+
executedTodayCount,
|
|
564
|
+
nominatedTodayCount,
|
|
565
|
+
executionHappenedToday: derived.todayOperations.some((op) => op.kind === "execution"),
|
|
566
|
+
playerDiedToday
|
|
567
|
+
},
|
|
568
|
+
payload
|
|
569
|
+
};
|
|
570
|
+
};
|
|
571
|
+
var buildDslContextFromLifetime = ({
|
|
572
|
+
snapshotSeatMap,
|
|
573
|
+
timelines,
|
|
574
|
+
nowTimelineIndex,
|
|
575
|
+
characterMap,
|
|
576
|
+
effector,
|
|
577
|
+
payloads
|
|
578
|
+
}) => {
|
|
579
|
+
const nowTl = timelines[nowTimelineIndex];
|
|
580
|
+
const currentTurn = nowTl?.turn ?? -1;
|
|
581
|
+
const currentTime = nowTl?.time;
|
|
582
|
+
const currentPhase = currentTime === "night" ? "NIGHT" : currentTime === "day" ? "DAY" : "NIGHT";
|
|
583
|
+
const playersRaw = snapshotSeatMap ? [...snapshotSeatMap.values()] : [];
|
|
584
|
+
const players = playersRaw.map((p) => decoratePlayer(p, characterMap, currentTurn));
|
|
585
|
+
const effectorDecorated = effector ? players.find((p) => p.seat === effector.seat) ?? decoratePlayer(effector, characterMap, currentTurn) : void 0;
|
|
586
|
+
const todayOps = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "day").flatMap((tl) => tl.operations ?? []);
|
|
587
|
+
const tonightOps = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "night").flatMap((tl) => tl.operations ?? []);
|
|
588
|
+
const yesterdayDayOps = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "day").flatMap((tl) => tl.operations ?? []);
|
|
589
|
+
const yesterdayNightOps = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "night").flatMap((tl) => tl.operations ?? []);
|
|
590
|
+
const allOps = timelines.flatMap((tl) => tl.operations ?? []);
|
|
591
|
+
const todayNoms = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee })));
|
|
592
|
+
const yesterdayNoms = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee })));
|
|
593
|
+
const allNoms = timelines.filter((tl) => tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({
|
|
594
|
+
nominator: n.nominator,
|
|
595
|
+
nominee: n.nominee,
|
|
596
|
+
turn: tl.turn
|
|
597
|
+
})));
|
|
598
|
+
const aliveCount = players.filter((p) => !p.isDead).length;
|
|
599
|
+
const deadCount = players.length - aliveCount;
|
|
600
|
+
const executedTodayCount = players.filter(
|
|
601
|
+
(p) => p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === currentTurn
|
|
602
|
+
).length;
|
|
603
|
+
const playerDiedToday = timelines.some((tl) => {
|
|
604
|
+
if (tl.turn !== currentTurn || tl.time !== "day") return false;
|
|
605
|
+
return (tl.operations ?? []).some((op) => {
|
|
606
|
+
const kind = op.kind;
|
|
607
|
+
return kind === "execution" || kind === "death";
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
return {
|
|
611
|
+
effector: effectorDecorated,
|
|
612
|
+
players,
|
|
613
|
+
operations: {
|
|
614
|
+
all: allOps,
|
|
615
|
+
today: todayOps,
|
|
616
|
+
tonight: tonightOps,
|
|
617
|
+
yesterdayDay: yesterdayDayOps,
|
|
618
|
+
yesterdayNight: yesterdayNightOps
|
|
619
|
+
},
|
|
620
|
+
nominations: {
|
|
621
|
+
all: allNoms,
|
|
622
|
+
today: todayNoms,
|
|
623
|
+
yesterday: yesterdayNoms
|
|
624
|
+
},
|
|
625
|
+
currentTimeline: {
|
|
626
|
+
turn: currentTurn,
|
|
627
|
+
phase: currentPhase,
|
|
628
|
+
time: currentTime,
|
|
629
|
+
isFirstOfPhase: false,
|
|
630
|
+
isExecution: isExecutionTimeline(nowTl)
|
|
631
|
+
},
|
|
632
|
+
global: {
|
|
633
|
+
aliveCount,
|
|
634
|
+
deadCount,
|
|
635
|
+
executedTodayCount,
|
|
636
|
+
nominatedTodayCount: todayNoms.length,
|
|
637
|
+
executionHappenedToday: timelines.some(
|
|
638
|
+
(tl) => tl.turn === currentTurn && tl.time === "day" && isExecutionTimeline(tl)
|
|
639
|
+
),
|
|
640
|
+
playerDiedToday
|
|
641
|
+
},
|
|
642
|
+
payload: payloads
|
|
643
|
+
};
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
// src/dsl/bindings.ts
|
|
647
|
+
var rootBindings = (ctx) => ({
|
|
648
|
+
effector: ctx.effector,
|
|
649
|
+
players: ctx.players,
|
|
650
|
+
operations: ctx.operations,
|
|
651
|
+
nominations: ctx.nominations,
|
|
652
|
+
currentTimeline: ctx.currentTimeline,
|
|
653
|
+
global: ctx.global,
|
|
654
|
+
payload: ctx.payload
|
|
655
|
+
});
|
|
656
|
+
var extendBindings = (parent, name, value) => ({
|
|
657
|
+
...parent,
|
|
658
|
+
[name]: value
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
// src/dsl/operators.ts
|
|
662
|
+
var deepEqual = (a, b) => {
|
|
663
|
+
if (a === b) return true;
|
|
664
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
665
|
+
if (a.length !== b.length) return false;
|
|
666
|
+
for (let i = 0; i < a.length; i++) {
|
|
667
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
668
|
+
}
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
return false;
|
|
672
|
+
};
|
|
673
|
+
var asNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
674
|
+
var compareOrder = (lhs, rhs, cmp) => {
|
|
675
|
+
const l = asNumber(lhs);
|
|
676
|
+
const r = asNumber(rhs);
|
|
677
|
+
if (l === void 0 || r === void 0) return false;
|
|
678
|
+
return cmp(l, r);
|
|
679
|
+
};
|
|
680
|
+
var applyOperator = (op, lhs, rhs) => {
|
|
681
|
+
switch (op) {
|
|
682
|
+
case "EQ":
|
|
683
|
+
return deepEqual(lhs, rhs);
|
|
684
|
+
case "NE":
|
|
685
|
+
return !deepEqual(lhs, rhs);
|
|
686
|
+
case "LT":
|
|
687
|
+
return compareOrder(lhs, rhs, (l, r) => l < r);
|
|
688
|
+
case "LTE":
|
|
689
|
+
return compareOrder(lhs, rhs, (l, r) => l <= r);
|
|
690
|
+
case "GT":
|
|
691
|
+
return compareOrder(lhs, rhs, (l, r) => l > r);
|
|
692
|
+
case "GTE":
|
|
693
|
+
return compareOrder(lhs, rhs, (l, r) => l >= r);
|
|
694
|
+
case "IN":
|
|
695
|
+
return Array.isArray(rhs) && rhs.some((v) => deepEqual(v, lhs));
|
|
696
|
+
case "NOT_IN":
|
|
697
|
+
return Array.isArray(rhs) && !rhs.some((v) => deepEqual(v, lhs));
|
|
698
|
+
case "CONTAINS":
|
|
699
|
+
if (Array.isArray(lhs)) return lhs.some((v) => deepEqual(v, rhs));
|
|
700
|
+
if (typeof lhs === "string") return typeof rhs === "string" && lhs.includes(rhs);
|
|
701
|
+
return false;
|
|
702
|
+
case "MATCHES":
|
|
703
|
+
if (typeof lhs !== "string" || typeof rhs !== "string") return false;
|
|
704
|
+
try {
|
|
705
|
+
return new RegExp(rhs).test(lhs);
|
|
706
|
+
} catch {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
case "EXISTS":
|
|
710
|
+
return lhs !== void 0 && lhs !== null;
|
|
711
|
+
case "TRUTHY":
|
|
712
|
+
return Boolean(lhs);
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
var applyCountOperator = (op, actual, expected) => {
|
|
716
|
+
switch (op) {
|
|
717
|
+
case "EQ":
|
|
718
|
+
return actual === expected;
|
|
719
|
+
case "NE":
|
|
720
|
+
return actual !== expected;
|
|
721
|
+
case "LT":
|
|
722
|
+
return actual < expected;
|
|
723
|
+
case "LTE":
|
|
724
|
+
return actual <= expected;
|
|
725
|
+
case "GT":
|
|
726
|
+
return actual > expected;
|
|
727
|
+
case "GTE":
|
|
728
|
+
return actual >= expected;
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
// src/dsl/path.ts
|
|
733
|
+
var MAX_STEPS = 32;
|
|
734
|
+
var stepInto = (current, step) => {
|
|
735
|
+
if (current === void 0 || current === null) return void 0;
|
|
736
|
+
if (step.kind === "field") {
|
|
737
|
+
if (typeof current !== "object") return void 0;
|
|
738
|
+
const record = current;
|
|
739
|
+
return record[step.name];
|
|
740
|
+
}
|
|
741
|
+
if (step.kind === "index") {
|
|
742
|
+
if (!Array.isArray(current)) return void 0;
|
|
743
|
+
return current[step.value];
|
|
744
|
+
}
|
|
745
|
+
return void 0;
|
|
746
|
+
};
|
|
747
|
+
var evaluatePath = (path, bindings) => {
|
|
748
|
+
if (path.steps.length > MAX_STEPS) return void 0;
|
|
749
|
+
let current = Object.prototype.hasOwnProperty.call(bindings, path.root) ? bindings[path.root] : void 0;
|
|
750
|
+
for (const step of path.steps) {
|
|
751
|
+
current = stepInto(current, step);
|
|
752
|
+
if (current === void 0) return void 0;
|
|
753
|
+
}
|
|
754
|
+
return current;
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
// src/dsl/eval.ts
|
|
758
|
+
var MAX_EXPR_DEPTH = 16;
|
|
759
|
+
var MAX_QUANTIFIER_SIZE = 256;
|
|
760
|
+
var resolveValue = (value, bindings) => {
|
|
761
|
+
if (!value) return void 0;
|
|
762
|
+
if (value.kind === "LITERAL") return value.value;
|
|
763
|
+
return evaluatePath(value.path, bindings);
|
|
764
|
+
};
|
|
765
|
+
var evalAtom = (atom, bindings, depth) => {
|
|
766
|
+
if (atom.type === "PATH") {
|
|
767
|
+
const lhs = evaluatePath(atom.path, bindings);
|
|
768
|
+
const rhs = resolveValue(atom.value, bindings);
|
|
769
|
+
return applyOperator(atom.op, lhs, rhs);
|
|
770
|
+
}
|
|
771
|
+
const source = evaluatePath(atom.source, bindings);
|
|
772
|
+
if (!Array.isArray(source)) {
|
|
773
|
+
return atom.type === "FORALL";
|
|
774
|
+
}
|
|
775
|
+
if (source.length > MAX_QUANTIFIER_SIZE) return false;
|
|
776
|
+
if (atom.type === "EXISTS") {
|
|
777
|
+
return source.some(
|
|
778
|
+
(item) => evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
if (atom.type === "FORALL") {
|
|
782
|
+
return source.every(
|
|
783
|
+
(item) => evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
let count = 0;
|
|
787
|
+
for (const item of source) {
|
|
788
|
+
if (!atom.where || evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)) {
|
|
789
|
+
count += 1;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return applyCountOperator(atom.op, count, atom.value);
|
|
793
|
+
};
|
|
794
|
+
var evalExpr = (expr, bindings, depth = 0) => {
|
|
795
|
+
if (depth > MAX_EXPR_DEPTH) return false;
|
|
796
|
+
switch (expr.op) {
|
|
797
|
+
case "AND":
|
|
798
|
+
return expr.children.every((child) => evalExpr(child, bindings, depth + 1));
|
|
799
|
+
case "OR":
|
|
800
|
+
return expr.children.some((child) => evalExpr(child, bindings, depth + 1));
|
|
801
|
+
case "NOT":
|
|
802
|
+
return !evalExpr(expr.child, bindings, depth + 1);
|
|
803
|
+
case "ATOM":
|
|
804
|
+
return evalAtom(expr.atom, bindings, depth + 1);
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
var evalDslExpr = (expr, ctx) => evalExpr(expr, rootBindings(ctx), 0);
|
|
808
|
+
|
|
809
|
+
// src/dsl/catalog.ts
|
|
810
|
+
var PLAYER_FIELDS = [
|
|
811
|
+
{ name: "seat", type: "number", label: "\u5EA7\u4F4D\u53F7" },
|
|
812
|
+
{ name: "alignment", type: "string", label: "\u9635\u8425", description: "GOOD / EVIL" },
|
|
813
|
+
{ name: "isDead", type: "boolean", label: "\u662F\u5426\u6B7B\u4EA1" },
|
|
814
|
+
{ name: "effectiveAlive", type: "boolean", label: "\u6709\u6548\u5B58\u6D3B", description: "\u8003\u8651 retainsAbility\uFF08ALIVENESS/ALL\uFF09\u540E\u662F\u5426\u6309\u5B58\u6D3B\u5904\u7406" },
|
|
815
|
+
{ name: "diedThisTurn", type: "boolean", label: "\u672C\u56DE\u5408\u6B7B\u4EA1" },
|
|
816
|
+
{ name: "wasExecuted", type: "boolean", label: "\u66FE\u88AB\u5904\u51B3" },
|
|
817
|
+
{ name: "wasExecutedToday", type: "boolean", label: "\u4ECA\u65E5\u88AB\u5904\u51B3" },
|
|
818
|
+
{ name: "characterId", type: "string", label: "\u89D2\u8272 ID" },
|
|
819
|
+
{ name: "characterKind", type: "string", label: "\u89D2\u8272\u7C7B\u578B", description: "Townsfolk / Outsiders / Minions / Demons" },
|
|
820
|
+
{ name: "deathCause", type: "string", label: "\u6B7B\u4EA1\u539F\u56E0", description: "EXECUTION / DEMON / ABILITY / STORYTELLER / OTHER" },
|
|
821
|
+
{ name: "deathTurn", type: "number", label: "\u6B7B\u4EA1\u56DE\u5408" },
|
|
822
|
+
{ name: "abilities", type: "string[]", label: "\u80FD\u529B ID \u5217\u8868" },
|
|
823
|
+
{ name: "reminderMarks", type: "string[]", label: "\u63D0\u793A\u7269 mark \u5217\u8868" },
|
|
824
|
+
{ name: "hasUsedDeadVote", type: "boolean", label: "\u662F\u5426\u4F7F\u7528\u8FC7\u6B7B\u4EA1\u6295\u7968" },
|
|
825
|
+
{ name: "retainsAbility", type: "string", label: "\u80FD\u529B\u4FDD\u7559\u8303\u56F4" }
|
|
826
|
+
];
|
|
827
|
+
var OPERATION_FIELDS = [
|
|
828
|
+
{ name: "effector", type: "number", label: "\u65BD\u52A8\u8005\u5EA7\u4F4D" },
|
|
829
|
+
{ name: "abilityId", type: "string", label: "\u80FD\u529B ID" },
|
|
830
|
+
{ name: "kind", type: "string", label: "\u64CD\u4F5C\u79CD\u7C7B" },
|
|
831
|
+
{ name: "payloads", type: "unknown[]", label: "Payload \u5217\u8868" }
|
|
832
|
+
];
|
|
833
|
+
var NOMINATION_FIELDS = [
|
|
834
|
+
{ name: "nominator", type: "number", label: "\u63D0\u540D\u8005\u5EA7\u4F4D" },
|
|
835
|
+
{ name: "nominee", type: "number", label: "\u88AB\u63D0\u540D\u8005\u5EA7\u4F4D" },
|
|
836
|
+
{ name: "turn", type: "number", label: "\u56DE\u5408", description: "\u4EC5 all \u96C6\u5408\u53EF\u7528" }
|
|
837
|
+
];
|
|
838
|
+
var CURRENT_TIMELINE_FIELDS = [
|
|
839
|
+
{ name: "turn", type: "number", label: "\u5F53\u524D\u56DE\u5408" },
|
|
840
|
+
{ name: "phase", type: "string", label: "\u5F53\u524D\u9636\u6BB5", description: "NIGHT / DAY / DUSK / DAWN" },
|
|
841
|
+
{ name: "time", type: "string", label: "\u5F53\u524D\u65F6\u6BB5", description: "night / day" },
|
|
842
|
+
{ name: "isFirstOfPhase", type: "boolean", label: "\u662F\u5426\u672C\u9636\u6BB5\u9996\u6B21" },
|
|
843
|
+
{ name: "isExecution", type: "boolean", label: "\u5F53\u524D\u65F6\u95F4\u7EBF\u542B\u5904\u51B3" }
|
|
844
|
+
];
|
|
845
|
+
var GLOBAL_FIELDS = [
|
|
846
|
+
{ name: "aliveCount", type: "number", label: "\u5B58\u6D3B\u73A9\u5BB6\u6570" },
|
|
847
|
+
{ name: "deadCount", type: "number", label: "\u6B7B\u4EA1\u73A9\u5BB6\u6570" },
|
|
848
|
+
{ name: "executedTodayCount", type: "number", label: "\u4ECA\u65E5\u88AB\u5904\u51B3\u4EBA\u6570" },
|
|
849
|
+
{ name: "nominatedTodayCount", type: "number", label: "\u4ECA\u65E5\u63D0\u540D\u603B\u6570" },
|
|
850
|
+
{ name: "executionHappenedToday", type: "boolean", label: "\u4ECA\u65E5\u662F\u5426\u5DF2\u53D1\u751F\u5904\u51B3" },
|
|
851
|
+
{ name: "playerDiedToday", type: "boolean", label: "\u4ECA\u65E5\u662F\u5426\u6709\u73A9\u5BB6\u6B7B\u4EA1" }
|
|
852
|
+
];
|
|
853
|
+
var OPERATIONS_FIELDS = [
|
|
854
|
+
{ name: "all", type: "operation[]", label: "\u6240\u6709\u64CD\u4F5C" },
|
|
855
|
+
{ name: "today", type: "operation[]", label: "\u4ECA\u65E5\u64CD\u4F5C" },
|
|
856
|
+
{ name: "tonight", type: "operation[]", label: "\u4ECA\u591C\u64CD\u4F5C" },
|
|
857
|
+
{ name: "yesterdayDay", type: "operation[]", label: "\u6628\u65E5\u767D\u5929\u64CD\u4F5C" },
|
|
858
|
+
{ name: "yesterdayNight", type: "operation[]", label: "\u6628\u591C\u64CD\u4F5C" }
|
|
859
|
+
];
|
|
860
|
+
var NOMINATIONS_FIELDS = [
|
|
861
|
+
{ name: "all", type: "nomination[]", label: "\u6240\u6709\u63D0\u540D" },
|
|
862
|
+
{ name: "today", type: "nomination[]", label: "\u4ECA\u65E5\u63D0\u540D" },
|
|
863
|
+
{ name: "yesterday", type: "nomination[]", label: "\u6628\u65E5\u63D0\u540D" }
|
|
864
|
+
];
|
|
865
|
+
var DSL_CATALOG = [
|
|
866
|
+
{
|
|
867
|
+
name: "effector",
|
|
868
|
+
type: "player",
|
|
869
|
+
label: "\u80FD\u529B\u53D1\u52A8\u8005",
|
|
870
|
+
description: "\u5F53\u524D\u8BC4\u4F30\u65F6\u7684\u80FD\u529B\u65BD\u653E\u8005\u5FEB\u7167",
|
|
871
|
+
fields: PLAYER_FIELDS
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
name: "players",
|
|
875
|
+
type: "player[]",
|
|
876
|
+
label: "\u5168\u90E8\u73A9\u5BB6",
|
|
877
|
+
description: "\u5F53\u524D\u5FEB\u7167\u4E0B\u6240\u6709\u73A9\u5BB6\uFF08\u5E94\u914D\u5408 EXISTS / FORALL / COUNT \u4F7F\u7528\uFF09"
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
name: "operations",
|
|
881
|
+
type: "object",
|
|
882
|
+
label: "\u5386\u53F2\u64CD\u4F5C\u96C6\u5408",
|
|
883
|
+
fields: OPERATIONS_FIELDS
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
name: "nominations",
|
|
887
|
+
type: "object",
|
|
888
|
+
label: "\u5386\u53F2\u63D0\u540D\u96C6\u5408",
|
|
889
|
+
fields: NOMINATIONS_FIELDS
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
name: "currentTimeline",
|
|
893
|
+
type: "object",
|
|
894
|
+
label: "\u5F53\u524D\u65F6\u95F4\u7EBF",
|
|
895
|
+
fields: CURRENT_TIMELINE_FIELDS
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
name: "global",
|
|
899
|
+
type: "object",
|
|
900
|
+
label: "\u5168\u5C40\u805A\u5408",
|
|
901
|
+
fields: GLOBAL_FIELDS
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
name: "payload",
|
|
905
|
+
type: "unknown[]",
|
|
906
|
+
label: "\u80FD\u529B Payload \u6570\u7EC4",
|
|
907
|
+
description: "\u5F53\u524D\u8C03\u7528 / \u64CD\u4F5C\u7684 payload \u539F\u59CB\u6570\u7EC4"
|
|
908
|
+
}
|
|
909
|
+
];
|
|
910
|
+
var DSL_ITEM_FIELDS = {
|
|
911
|
+
player: PLAYER_FIELDS,
|
|
912
|
+
operation: OPERATION_FIELDS,
|
|
913
|
+
nomination: NOMINATION_FIELDS
|
|
914
|
+
};
|
|
915
|
+
var DSL_OPERATORS = [
|
|
916
|
+
{ value: "EQ", label: "\u7B49\u4E8E", arity: "binary" },
|
|
917
|
+
{ value: "NE", label: "\u4E0D\u7B49\u4E8E", arity: "binary" },
|
|
918
|
+
{ value: "LT", label: "<", arity: "binary" },
|
|
919
|
+
{ value: "LTE", label: "<=", arity: "binary" },
|
|
920
|
+
{ value: "GT", label: ">", arity: "binary" },
|
|
921
|
+
{ value: "GTE", label: ">=", arity: "binary" },
|
|
922
|
+
{ value: "IN", label: "\u5C5E\u4E8E", arity: "binary" },
|
|
923
|
+
{ value: "NOT_IN", label: "\u4E0D\u5C5E\u4E8E", arity: "binary" },
|
|
924
|
+
{ value: "CONTAINS", label: "\u5305\u542B", arity: "binary" },
|
|
925
|
+
{ value: "MATCHES", label: "\u6B63\u5219\u5339\u914D", arity: "binary" },
|
|
926
|
+
{ value: "EXISTS", label: "\u5B58\u5728", arity: "unary" },
|
|
927
|
+
{ value: "TRUTHY", label: "\u4E3A\u771F", arity: "unary" }
|
|
928
|
+
];
|
|
929
|
+
|
|
930
|
+
// src/dsl/trace.ts
|
|
931
|
+
var MAX_QUANTIFIER_SIZE2 = 256;
|
|
932
|
+
var renderPath = (p) => p.steps.reduce((acc, s) => acc + (s.kind === "field" ? `.${s.name}` : `[${s.value}]`), p.root);
|
|
933
|
+
var resolveValue2 = (value, bindings) => {
|
|
934
|
+
if (!value) return void 0;
|
|
935
|
+
if (value.kind === "LITERAL") return value.value;
|
|
936
|
+
return evaluatePath(value.path, bindings);
|
|
937
|
+
};
|
|
938
|
+
var renderValue = (value, bindings) => {
|
|
939
|
+
if (!value) return void 0;
|
|
940
|
+
if (value.kind === "LITERAL") return value.value;
|
|
941
|
+
return { ref: renderPath(value.path), resolved: evaluatePath(value.path, bindings) };
|
|
942
|
+
};
|
|
943
|
+
var traceAtom = (atom, bindings) => {
|
|
944
|
+
if (atom.type === "PATH") {
|
|
945
|
+
const lhs = evaluatePath(atom.path, bindings);
|
|
946
|
+
const rhs = resolveValue2(atom.value, bindings);
|
|
947
|
+
const ok2 = applyOperator(atom.op, lhs, rhs);
|
|
948
|
+
return {
|
|
949
|
+
op: "ATOM",
|
|
950
|
+
atomType: "PATH",
|
|
951
|
+
ok: ok2,
|
|
952
|
+
path: renderPath(atom.path),
|
|
953
|
+
operator: atom.op,
|
|
954
|
+
lhs,
|
|
955
|
+
rhs: renderValue(atom.value, bindings)
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
const source = evaluatePath(atom.source, bindings);
|
|
959
|
+
const sourceArr = Array.isArray(source) ? source : [];
|
|
960
|
+
const sizeCapped = sourceArr.length > MAX_QUANTIFIER_SIZE2;
|
|
961
|
+
let matched = 0;
|
|
962
|
+
if (!sizeCapped) {
|
|
963
|
+
for (const item of sourceArr) {
|
|
964
|
+
const childBindings = extendBindings(bindings, atom.as, item);
|
|
965
|
+
if (!atom.where || traceExprOk(atom.where, childBindings)) matched += 1;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
if (atom.type === "COUNT") {
|
|
969
|
+
const ok2 = !sizeCapped && applyCountOperator(atom.op, matched, atom.value);
|
|
970
|
+
return {
|
|
971
|
+
op: "ATOM",
|
|
972
|
+
atomType: "COUNT",
|
|
973
|
+
ok: ok2,
|
|
974
|
+
source: renderPath(atom.source),
|
|
975
|
+
size: sourceArr.length,
|
|
976
|
+
matched,
|
|
977
|
+
operator: atom.op,
|
|
978
|
+
expected: atom.value,
|
|
979
|
+
bindings: atom.as
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
const ok = atom.type === "EXISTS" ? matched > 0 : !sizeCapped && (sourceArr.length === 0 || matched === sourceArr.length);
|
|
983
|
+
return {
|
|
984
|
+
op: "ATOM",
|
|
985
|
+
atomType: atom.type,
|
|
986
|
+
ok,
|
|
987
|
+
source: renderPath(atom.source),
|
|
988
|
+
size: sourceArr.length,
|
|
989
|
+
matched,
|
|
990
|
+
bindings: atom.as
|
|
991
|
+
};
|
|
992
|
+
};
|
|
993
|
+
var traceExprOk = (expr, bindings) => {
|
|
994
|
+
switch (expr.op) {
|
|
995
|
+
case "AND":
|
|
996
|
+
return expr.children.every((c) => traceExprOk(c, bindings));
|
|
997
|
+
case "OR":
|
|
998
|
+
return expr.children.some((c) => traceExprOk(c, bindings));
|
|
999
|
+
case "NOT":
|
|
1000
|
+
return !traceExprOk(expr.child, bindings);
|
|
1001
|
+
case "ATOM":
|
|
1002
|
+
return traceAtom(expr.atom, bindings).ok;
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
var traceExpr = (expr, bindings) => {
|
|
1006
|
+
if (expr.op === "AND") {
|
|
1007
|
+
const children = expr.children.map((c) => traceExpr(c, bindings));
|
|
1008
|
+
return { op: "AND", ok: children.every((c) => c.ok), children };
|
|
1009
|
+
}
|
|
1010
|
+
if (expr.op === "OR") {
|
|
1011
|
+
const children = expr.children.map((c) => traceExpr(c, bindings));
|
|
1012
|
+
return { op: "OR", ok: children.some((c) => c.ok), children };
|
|
1013
|
+
}
|
|
1014
|
+
if (expr.op === "NOT") {
|
|
1015
|
+
const child = traceExpr(expr.child, bindings);
|
|
1016
|
+
return { op: "NOT", ok: !child.ok, child };
|
|
1017
|
+
}
|
|
1018
|
+
return traceAtom(expr.atom, bindings);
|
|
1019
|
+
};
|
|
1020
|
+
var traceDslExpr = (expr, ctx) => traceExpr(expr, rootBindings(ctx));
|
|
1021
|
+
|
|
508
1022
|
// src/effects/resolve-targets.ts
|
|
509
1023
|
var isPayloadRef = (value) => {
|
|
510
1024
|
if (!value || typeof value !== "object") {
|
|
@@ -550,7 +1064,23 @@ var resolveSelectorScope = (dynamicTarget, payloads) => {
|
|
|
550
1064
|
(value) => value === "BOTH_SIDES" || value === "LEFT_SIDE" || value === "RIGHT_SIDE"
|
|
551
1065
|
) ?? "BOTH_SIDES";
|
|
552
1066
|
};
|
|
553
|
-
var
|
|
1067
|
+
var matchesExprCondition = (player, condition, playerSeatMap, characterMap, effector, payloads) => {
|
|
1068
|
+
const dslCtx = buildDslContextFromLifetime({
|
|
1069
|
+
snapshotSeatMap: playerSeatMap,
|
|
1070
|
+
timelines: [],
|
|
1071
|
+
nowTimelineIndex: -1,
|
|
1072
|
+
characterMap,
|
|
1073
|
+
effector,
|
|
1074
|
+
payloads
|
|
1075
|
+
});
|
|
1076
|
+
const decoratedPlayer = dslCtx.players.find((p) => p.seat === player.seat);
|
|
1077
|
+
const bindings = extendBindings(rootBindings(dslCtx), "it", decoratedPlayer ?? player);
|
|
1078
|
+
return evalExpr(condition.expr, bindings);
|
|
1079
|
+
};
|
|
1080
|
+
var matchesCondition = (player, condition, payloads, characterMap, playerSeatMap, effector) => {
|
|
1081
|
+
if (condition.kind === "EXPR") {
|
|
1082
|
+
return matchesExprCondition(player, condition, playerSeatMap, characterMap, effector, payloads);
|
|
1083
|
+
}
|
|
554
1084
|
if (condition.field === "IS_DEAD") {
|
|
555
1085
|
const expected = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "boolean");
|
|
556
1086
|
if (typeof expected !== "boolean") {
|
|
@@ -566,6 +1096,18 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
|
566
1096
|
const expectedSeat = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "number");
|
|
567
1097
|
return typeof expectedSeat === "number" && player.seat === expectedSeat;
|
|
568
1098
|
}
|
|
1099
|
+
if (condition.field === "ALIGNMENT") {
|
|
1100
|
+
const alignment = player.alignment;
|
|
1101
|
+
if (!alignment) {
|
|
1102
|
+
return false;
|
|
1103
|
+
}
|
|
1104
|
+
if (condition.operator === "IN") {
|
|
1105
|
+
const expectedAlignments = resolveDynamicValue(condition.value, payloads, (value) => Array.isArray(value) && value.every((item) => typeof item === "string"));
|
|
1106
|
+
return Array.isArray(expectedAlignments) && expectedAlignments.includes(alignment);
|
|
1107
|
+
}
|
|
1108
|
+
const expectedAlignment = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
|
|
1109
|
+
return typeof expectedAlignment === "string" && alignment === expectedAlignment;
|
|
1110
|
+
}
|
|
569
1111
|
const character = characterMap.get(player.characterId);
|
|
570
1112
|
const kind = character?.kind;
|
|
571
1113
|
if (!kind) {
|
|
@@ -578,15 +1120,17 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
|
578
1120
|
const expectedKind = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
|
|
579
1121
|
return typeof expectedKind === "string" && kind === expectedKind;
|
|
580
1122
|
};
|
|
581
|
-
var passesWhere = (player, dynamicTarget, payloads, characterMap) => {
|
|
1123
|
+
var passesWhere = (player, dynamicTarget, payloads, characterMap, playerSeatMap, effector) => {
|
|
582
1124
|
const where = dynamicTarget.where;
|
|
583
1125
|
const conditions = where?.conditions ?? [];
|
|
584
1126
|
if (conditions.length === 0) return true;
|
|
585
1127
|
const isAllMode = where?.mode !== "ANY";
|
|
586
|
-
const results = conditions.map(
|
|
1128
|
+
const results = conditions.map(
|
|
1129
|
+
(condition) => matchesCondition(player, condition, payloads, characterMap, playerSeatMap, effector)
|
|
1130
|
+
);
|
|
587
1131
|
return isAllMode ? results.every(Boolean) : results.some(Boolean);
|
|
588
1132
|
};
|
|
589
|
-
var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap) => {
|
|
1133
|
+
var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector) => {
|
|
590
1134
|
const scope = resolveSelectorScope(dynamicTarget, payloads);
|
|
591
1135
|
if (!anchorSeat) return { left: [], right: [] };
|
|
592
1136
|
const anchorIdx = sortedPlayers.findIndex((player) => player.seat === anchorSeat);
|
|
@@ -597,13 +1141,13 @@ var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, c
|
|
|
597
1141
|
if (scope === "LEFT_SIDE" || scope === "BOTH_SIDES") {
|
|
598
1142
|
for (let step = 1; step < total; step += 1) {
|
|
599
1143
|
const player = sortedPlayers[(anchorIdx + step) % total];
|
|
600
|
-
if (passesWhere(player, dynamicTarget, payloads, characterMap)) left.push(player);
|
|
1144
|
+
if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) left.push(player);
|
|
601
1145
|
}
|
|
602
1146
|
}
|
|
603
1147
|
if (scope === "RIGHT_SIDE" || scope === "BOTH_SIDES") {
|
|
604
1148
|
for (let step = 1; step < total; step += 1) {
|
|
605
1149
|
const player = sortedPlayers[(anchorIdx - step + total) % total];
|
|
606
|
-
if (passesWhere(player, dynamicTarget, payloads, characterMap)) right.push(player);
|
|
1150
|
+
if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) right.push(player);
|
|
607
1151
|
}
|
|
608
1152
|
}
|
|
609
1153
|
return { left, right };
|
|
@@ -677,10 +1221,12 @@ var resolveEffectTargets = ({
|
|
|
677
1221
|
const sortedPlayers = getSortedPlayers(playerSeatMap);
|
|
678
1222
|
const anchorSeat = getAnchorSeat(dynamicTarget, payloads, effector);
|
|
679
1223
|
if (!dynamicTarget.selector) {
|
|
680
|
-
const matches = sortedPlayers.filter(
|
|
1224
|
+
const matches = sortedPlayers.filter(
|
|
1225
|
+
(player) => passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)
|
|
1226
|
+
);
|
|
681
1227
|
return pickBalanced({ left: matches, right: [] }, dynamicTarget, payloads);
|
|
682
1228
|
}
|
|
683
|
-
const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap);
|
|
1229
|
+
const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector);
|
|
684
1230
|
return pickBalanced(sides, dynamicTarget, payloads);
|
|
685
1231
|
}
|
|
686
1232
|
return [];
|
|
@@ -826,7 +1372,7 @@ var evalPlayerState = (atom, ctx) => {
|
|
|
826
1372
|
const single = players[0];
|
|
827
1373
|
return evalPlayerStateOnPlayer(single, atom, ctx);
|
|
828
1374
|
};
|
|
829
|
-
var
|
|
1375
|
+
var isExecutionTimeline2 = (tl) => {
|
|
830
1376
|
if (!tl) return false;
|
|
831
1377
|
return (tl.operations ?? []).some((op) => op.kind === "execution");
|
|
832
1378
|
};
|
|
@@ -854,7 +1400,7 @@ var evalTime = (atom, ctx) => {
|
|
|
854
1400
|
if (spec.event === "NEXT_NIGHT" && tl.time === "night") return false;
|
|
855
1401
|
if (spec.event === "NEXT_DUSK" && prevTime === "day" && tl.time === "night") return false;
|
|
856
1402
|
if (spec.event === "NEXT_DAWN" && prevTime === "night" && tl.time === "day") return false;
|
|
857
|
-
if (spec.event === "NEXT_EXECUTION" &&
|
|
1403
|
+
if (spec.event === "NEXT_EXECUTION" && isExecutionTimeline2(tl)) return false;
|
|
858
1404
|
prevTime = tl.time;
|
|
859
1405
|
}
|
|
860
1406
|
return true;
|
|
@@ -895,7 +1441,7 @@ var evalGameState = (atom, ctx) => {
|
|
|
895
1441
|
if (spec.kind === "EXECUTION_HAPPENED_TODAY") {
|
|
896
1442
|
const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
|
|
897
1443
|
if (typeof turn !== "number") return false;
|
|
898
|
-
return ctx.timelines.some((tl) => tl.turn === turn && tl.time === "day" &&
|
|
1444
|
+
return ctx.timelines.some((tl) => tl.turn === turn && tl.time === "day" && isExecutionTimeline2(tl));
|
|
899
1445
|
}
|
|
900
1446
|
if (spec.kind === "PLAYER_DIED_TODAY") {
|
|
901
1447
|
const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
|
|
@@ -910,7 +1456,7 @@ var evalGameState = (atom, ctx) => {
|
|
|
910
1456
|
}
|
|
911
1457
|
return true;
|
|
912
1458
|
};
|
|
913
|
-
var
|
|
1459
|
+
var evalAtom2 = (atom, ctx) => {
|
|
914
1460
|
switch (atom.type) {
|
|
915
1461
|
case "TIME":
|
|
916
1462
|
return evalTime(atom, ctx);
|
|
@@ -929,18 +1475,30 @@ var evalAtom = (atom, ctx) => {
|
|
|
929
1475
|
target: ctx.target,
|
|
930
1476
|
snapshotSeatMap: ctx.snapshotSeatMap
|
|
931
1477
|
}) : false;
|
|
1478
|
+
case "DSL": {
|
|
1479
|
+
if (!ctx.snapshotSeatMap) return true;
|
|
1480
|
+
const dslCtx = buildDslContextFromLifetime({
|
|
1481
|
+
snapshotSeatMap: ctx.snapshotSeatMap,
|
|
1482
|
+
timelines: ctx.timelines,
|
|
1483
|
+
nowTimelineIndex: ctx.nowTimelineIndex,
|
|
1484
|
+
characterMap: ctx.characterMap,
|
|
1485
|
+
effector: ctx.effector,
|
|
1486
|
+
payloads: ctx.payloads
|
|
1487
|
+
});
|
|
1488
|
+
return evalDslExpr(atom.expr, dslCtx);
|
|
1489
|
+
}
|
|
932
1490
|
}
|
|
933
1491
|
};
|
|
934
|
-
var
|
|
1492
|
+
var evalExpr2 = (expr, ctx) => {
|
|
935
1493
|
switch (expr.op) {
|
|
936
1494
|
case "AND":
|
|
937
|
-
return expr.children.every((child) =>
|
|
1495
|
+
return expr.children.every((child) => evalExpr2(child, ctx));
|
|
938
1496
|
case "OR":
|
|
939
|
-
return expr.children.some((child) =>
|
|
1497
|
+
return expr.children.some((child) => evalExpr2(child, ctx));
|
|
940
1498
|
case "NOT":
|
|
941
|
-
return !
|
|
1499
|
+
return !evalExpr2(expr.child, ctx);
|
|
942
1500
|
case "ATOM":
|
|
943
|
-
return
|
|
1501
|
+
return evalAtom2(expr.atom, ctx);
|
|
944
1502
|
}
|
|
945
1503
|
};
|
|
946
1504
|
var expressionReferencesTarget = (expr) => {
|
|
@@ -978,9 +1536,9 @@ var evaluateLifetime = ({
|
|
|
978
1536
|
const expr = lifetime.expr;
|
|
979
1537
|
const dependsOnTarget = expressionReferencesTarget(expr);
|
|
980
1538
|
if (!dependsOnTarget) {
|
|
981
|
-
return
|
|
1539
|
+
return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
|
|
982
1540
|
}
|
|
983
|
-
return targets.filter((target) =>
|
|
1541
|
+
return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
|
|
984
1542
|
};
|
|
985
1543
|
var evaluatePrecondition = ({
|
|
986
1544
|
expr,
|
|
@@ -1006,9 +1564,9 @@ var evaluatePrecondition = ({
|
|
|
1006
1564
|
};
|
|
1007
1565
|
const dependsOnTarget = expressionReferencesTarget(expr);
|
|
1008
1566
|
if (!dependsOnTarget) {
|
|
1009
|
-
return
|
|
1567
|
+
return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
|
|
1010
1568
|
}
|
|
1011
|
-
return targets.filter((target) =>
|
|
1569
|
+
return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
|
|
1012
1570
|
};
|
|
1013
1571
|
|
|
1014
1572
|
// src/apply-operation.ts
|
|
@@ -1093,6 +1651,7 @@ var applyOperationToPlayers = ({
|
|
|
1093
1651
|
abilityMap,
|
|
1094
1652
|
nowTimelineIndex,
|
|
1095
1653
|
operationInTimelineIdx: record.operationInTimelineIdx,
|
|
1654
|
+
operationTurn: timelines[record.operationInTimelineIdx]?.turn ?? 0,
|
|
1096
1655
|
makePlayersEffect
|
|
1097
1656
|
};
|
|
1098
1657
|
handler(context);
|
|
@@ -1330,6 +1889,12 @@ var deriveContext = ({
|
|
|
1330
1889
|
const yesterdayDayOperations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "day").flatMap((tl) => tl.operations ?? []);
|
|
1331
1890
|
const yesterdayNightOperations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "night").flatMap((tl) => tl.operations ?? []);
|
|
1332
1891
|
const todayNominations = timelines.filter((tl) => tl.turn === last.turn && tl.time === "day").flatMap((tl) => tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee }));
|
|
1892
|
+
const yesterdayNominations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "day").flatMap((tl) => tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee }));
|
|
1893
|
+
const allNominations = timelines.filter((tl) => tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({
|
|
1894
|
+
nominator: n.nominator,
|
|
1895
|
+
nominee: n.nominee,
|
|
1896
|
+
turn: tl.turn
|
|
1897
|
+
})));
|
|
1333
1898
|
return {
|
|
1334
1899
|
currentTurn: last.turn,
|
|
1335
1900
|
currentPhase: TIME_TO_PHASE[last.time],
|
|
@@ -1341,6 +1906,8 @@ var deriveContext = ({
|
|
|
1341
1906
|
yesterdayDayOperations,
|
|
1342
1907
|
yesterdayNightOperations,
|
|
1343
1908
|
todayNominations,
|
|
1909
|
+
yesterdayNominations,
|
|
1910
|
+
allNominations,
|
|
1344
1911
|
effectorSeat: candidate.effector,
|
|
1345
1912
|
effector,
|
|
1346
1913
|
abilityId: candidate.abilityId,
|
|
@@ -1349,6 +1916,14 @@ var deriveContext = ({
|
|
|
1349
1916
|
operationTimeMap
|
|
1350
1917
|
};
|
|
1351
1918
|
};
|
|
1919
|
+
var nominationsForActionScope = (scope, ctx) => {
|
|
1920
|
+
if (scope === "TONIGHT" || scope === "TODAY") return ctx.todayNominations;
|
|
1921
|
+
if (scope === "YESTERDAY") return ctx.yesterdayNominations;
|
|
1922
|
+
return ctx.allNominations.map((n) => ({ nominator: n.nominator, nominee: n.nominee }));
|
|
1923
|
+
};
|
|
1924
|
+
var extractPayloadSeats = (op) => (op.payloads ?? []).flatMap(
|
|
1925
|
+
(v) => typeof v === "number" ? [v] : Array.isArray(v) ? v.filter((x) => typeof x === "number") : []
|
|
1926
|
+
);
|
|
1352
1927
|
var operationsForActionScope = (scope, ctx) => {
|
|
1353
1928
|
if (scope === "TONIGHT") return ctx.tonightOperations;
|
|
1354
1929
|
if (scope === "TODAY") return ctx.todayOperations;
|
|
@@ -1404,23 +1979,37 @@ var evalAtomAction = (atom, ctx) => {
|
|
|
1404
1979
|
if (subjectSeats.size === 0) return `no players match subject ${atom.subject.ref}`;
|
|
1405
1980
|
const actorSeats = atom.actor ? seatsMatchingTargetRef(atom.actor, ctx) : null;
|
|
1406
1981
|
if (atom.action === "NOMINATED") {
|
|
1407
|
-
const noms = ctx
|
|
1982
|
+
const noms = nominationsForActionScope(atom.scope, ctx);
|
|
1408
1983
|
const found = noms.some(
|
|
1409
1984
|
(n) => subjectSeats.has(n.nominee) && (actorSeats === null || actorSeats.has(n.nominator))
|
|
1410
1985
|
);
|
|
1411
|
-
return found ? null : "no matching NOMINATED event in
|
|
1986
|
+
return found ? null : "no matching NOMINATED event in scope";
|
|
1987
|
+
}
|
|
1988
|
+
if (atom.action === "NOMINATES") {
|
|
1989
|
+
const noms = nominationsForActionScope(atom.scope, ctx);
|
|
1990
|
+
const found = noms.some(
|
|
1991
|
+
(n) => subjectSeats.has(n.nominator) && (actorSeats === null || actorSeats.has(n.nominee))
|
|
1992
|
+
);
|
|
1993
|
+
return found ? null : "no matching NOMINATES event in scope";
|
|
1412
1994
|
}
|
|
1413
1995
|
const scoped = operationsForActionScope(atom.scope, ctx);
|
|
1414
1996
|
if (atom.action === "CHOSEN_AS_TARGET") {
|
|
1415
1997
|
const found = scoped.some((op) => {
|
|
1416
1998
|
if (actorSeats !== null && !actorSeats.has(op.effector)) return false;
|
|
1417
|
-
const payloadSeats = (op
|
|
1418
|
-
(v) => typeof v === "number" ? [v] : Array.isArray(v) ? v.filter((x) => typeof x === "number") : []
|
|
1419
|
-
);
|
|
1999
|
+
const payloadSeats = extractPayloadSeats(op);
|
|
1420
2000
|
return payloadSeats.some((s) => subjectSeats.has(s));
|
|
1421
2001
|
});
|
|
1422
2002
|
return found ? null : "no matching CHOSEN_AS_TARGET in scope";
|
|
1423
2003
|
}
|
|
2004
|
+
if (atom.action === "CHOOSES_TARGET") {
|
|
2005
|
+
const found = scoped.some((op) => {
|
|
2006
|
+
if (!subjectSeats.has(op.effector)) return false;
|
|
2007
|
+
if (actorSeats === null) return true;
|
|
2008
|
+
const payloadSeats = extractPayloadSeats(op);
|
|
2009
|
+
return payloadSeats.some((s) => actorSeats.has(s));
|
|
2010
|
+
});
|
|
2011
|
+
return found ? null : "no matching CHOOSES_TARGET in scope";
|
|
2012
|
+
}
|
|
1424
2013
|
const turnsInScope = (() => {
|
|
1425
2014
|
if (atom.scope === "TONIGHT" || atom.scope === "TODAY") return [ctx.currentTurn];
|
|
1426
2015
|
if (atom.scope === "YESTERDAY") return [ctx.currentTurn - 1];
|
|
@@ -1450,6 +2039,18 @@ var evalAtomAction = (atom, ctx) => {
|
|
|
1450
2039
|
}
|
|
1451
2040
|
return "no matching EXECUTED in scope";
|
|
1452
2041
|
}
|
|
2042
|
+
if (atom.action === "EXECUTES") {
|
|
2043
|
+
const scopedNoms = atom.scope === "EVER" ? ctx.allNominations : atom.scope === "YESTERDAY" ? ctx.yesterdayNominations.map((n) => ({ ...n, turn: ctx.currentTurn - 1 })) : ctx.todayNominations.map((n) => ({ ...n, turn: ctx.currentTurn }));
|
|
2044
|
+
const found = scopedNoms.some((n) => {
|
|
2045
|
+
if (!subjectSeats.has(n.nominator)) return false;
|
|
2046
|
+
if (actorSeats !== null && !actorSeats.has(n.nominee)) return false;
|
|
2047
|
+
const executed = ctx.players.find(
|
|
2048
|
+
(p) => p.seat === n.nominee && p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === n.turn
|
|
2049
|
+
);
|
|
2050
|
+
return Boolean(executed);
|
|
2051
|
+
});
|
|
2052
|
+
return found ? null : "no matching EXECUTES in scope";
|
|
2053
|
+
}
|
|
1453
2054
|
void scoped;
|
|
1454
2055
|
return `unknown action ${atom.action}`;
|
|
1455
2056
|
};
|
|
@@ -1486,7 +2087,7 @@ var evalAtomGlobalCount = (atom, ctx) => {
|
|
|
1486
2087
|
}
|
|
1487
2088
|
return compareCount2(actual, atom.operator, atom.value) ? null : `global count ${atom.subject}=${actual} fails ${atom.operator} ${atom.value}`;
|
|
1488
2089
|
};
|
|
1489
|
-
var
|
|
2090
|
+
var evalAtom3 = (atom, ctx, customResolver) => {
|
|
1490
2091
|
if (atom.type === "TIME") return evalAtomTime(atom, ctx);
|
|
1491
2092
|
if (atom.type === "STATE") return evalAtomState(atom, ctx);
|
|
1492
2093
|
if (atom.type === "ACTION") return evalAtomAction(atom, ctx);
|
|
@@ -1496,12 +2097,16 @@ var evalAtom2 = (atom, ctx, customResolver) => {
|
|
|
1496
2097
|
if (!customResolver) return `no custom resolver registered for ${atom.resolverId}`;
|
|
1497
2098
|
return customResolver({ resolverId: atom.resolverId, args: atom.args }, ctx) ? null : `custom resolver ${atom.resolverId} returned false`;
|
|
1498
2099
|
}
|
|
2100
|
+
if (atom.type === "DSL") {
|
|
2101
|
+
const dslCtx = buildDslContextFromDerived(ctx);
|
|
2102
|
+
return evalDslExpr(atom.expr, dslCtx) ? null : "DSL expression returned false";
|
|
2103
|
+
}
|
|
1499
2104
|
return "unknown atom";
|
|
1500
2105
|
};
|
|
1501
|
-
var
|
|
2106
|
+
var evalExpr3 = (expr, ctx, customResolver) => {
|
|
1502
2107
|
if (expr.op === "AND") {
|
|
1503
2108
|
for (const child of expr.children) {
|
|
1504
|
-
const reason =
|
|
2109
|
+
const reason = evalExpr3(child, ctx, customResolver);
|
|
1505
2110
|
if (reason) return reason;
|
|
1506
2111
|
}
|
|
1507
2112
|
return null;
|
|
@@ -1509,17 +2114,17 @@ var evalExpr2 = (expr, ctx, customResolver) => {
|
|
|
1509
2114
|
if (expr.op === "OR") {
|
|
1510
2115
|
const reasons = [];
|
|
1511
2116
|
for (const child of expr.children) {
|
|
1512
|
-
const reason =
|
|
2117
|
+
const reason = evalExpr3(child, ctx, customResolver);
|
|
1513
2118
|
if (!reason) return null;
|
|
1514
2119
|
reasons.push(reason);
|
|
1515
2120
|
}
|
|
1516
2121
|
return `OR: all branches failed (${reasons.join("; ")})`;
|
|
1517
2122
|
}
|
|
1518
2123
|
if (expr.op === "NOT") {
|
|
1519
|
-
const reason =
|
|
2124
|
+
const reason = evalExpr3(expr.child, ctx, customResolver);
|
|
1520
2125
|
return reason ? null : "NOT: inner expression was true";
|
|
1521
2126
|
}
|
|
1522
|
-
return
|
|
2127
|
+
return evalAtom3(expr.atom, ctx, customResolver);
|
|
1523
2128
|
};
|
|
1524
2129
|
var canInvokeAbility = ({
|
|
1525
2130
|
ability,
|
|
@@ -1539,23 +2144,47 @@ var canInvokeAbility = ({
|
|
|
1539
2144
|
if (derived.effector?.retainsAbility === "ALL") {
|
|
1540
2145
|
return { allowed: true };
|
|
1541
2146
|
}
|
|
1542
|
-
const reason =
|
|
2147
|
+
const reason = evalExpr3(window, derived, customResolver);
|
|
1543
2148
|
return reason ? { allowed: false, reason } : { allowed: true };
|
|
1544
2149
|
};
|
|
2150
|
+
|
|
2151
|
+
// src/trigger-action.ts
|
|
2152
|
+
var ACTIVE_TRIGGER_ACTIONS = /* @__PURE__ */ new Set([
|
|
2153
|
+
"NOMINATES",
|
|
2154
|
+
"CHOOSES_TARGET",
|
|
2155
|
+
"EXECUTES"
|
|
2156
|
+
]);
|
|
2157
|
+
var isActiveTriggerAction = (action) => ACTIVE_TRIGGER_ACTIONS.has(action);
|
|
1545
2158
|
export {
|
|
2159
|
+
ACTIVE_TRIGGER_ACTIONS,
|
|
2160
|
+
DSL_CATALOG,
|
|
2161
|
+
DSL_ITEM_FIELDS,
|
|
2162
|
+
DSL_OPERATORS,
|
|
1546
2163
|
adjustValueAsNumber,
|
|
1547
2164
|
adjustValueAsNumberArray,
|
|
2165
|
+
applyCountOperator,
|
|
1548
2166
|
applyOperationToPlayers,
|
|
2167
|
+
applyOperator,
|
|
2168
|
+
buildDslContextFromDerived,
|
|
2169
|
+
buildDslContextFromLifetime,
|
|
1549
2170
|
buildPlayerSeatMap,
|
|
1550
2171
|
canInvokeAbility,
|
|
1551
2172
|
copyPlayers,
|
|
1552
2173
|
deriveContext,
|
|
2174
|
+
evalDslExpr,
|
|
2175
|
+
evalExpr as evalDslExprWithBindings,
|
|
2176
|
+
evaluatePath,
|
|
2177
|
+
extendBindings,
|
|
1553
2178
|
getSourceValue,
|
|
1554
2179
|
getValueFromPayloads,
|
|
2180
|
+
isActiveTriggerAction,
|
|
1555
2181
|
isNumberOrNumberArray,
|
|
1556
2182
|
isPlayerReminderArray,
|
|
1557
2183
|
isReminder,
|
|
1558
2184
|
resolveSourceValue,
|
|
1559
2185
|
resolveTargetRef,
|
|
2186
|
+
rootBindings,
|
|
2187
|
+
traceDslExpr,
|
|
2188
|
+
traceExpr,
|
|
1560
2189
|
transformEmptyArray
|
|
1561
2190
|
};
|