@bct-app/game-engine 0.1.18 → 0.1.19
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 +678 -33
- package/dist/index.mjs +662 -33
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -505,6 +505,521 @@ var effectHandlers = {
|
|
|
505
505
|
STATUS_CHANGE: applyStatusChange
|
|
506
506
|
};
|
|
507
507
|
|
|
508
|
+
// src/dsl/context.ts
|
|
509
|
+
var decoratePlayer = (player, characterMap, currentTurn) => {
|
|
510
|
+
const isDead = Boolean(player.isDead);
|
|
511
|
+
const retainsBypass = isDead && (player.retainsAbility === "ALIVENESS" || player.retainsAbility === "ALL");
|
|
512
|
+
const wasExecuted = isDead && player.deathCause === "EXECUTION";
|
|
513
|
+
return {
|
|
514
|
+
...player,
|
|
515
|
+
characterKind: characterMap.get(player.characterId)?.kind,
|
|
516
|
+
reminderMarks: (player.reminders ?? []).map((r) => r.mark),
|
|
517
|
+
effectiveAlive: !isDead || retainsBypass,
|
|
518
|
+
diedThisTurn: isDead && typeof player.deathTurn === "number" && player.deathTurn === currentTurn,
|
|
519
|
+
wasExecuted,
|
|
520
|
+
wasExecutedToday: wasExecuted && player.deathTurn === currentTurn
|
|
521
|
+
};
|
|
522
|
+
};
|
|
523
|
+
var isExecutionTimeline = (tl) => {
|
|
524
|
+
if (!tl) return false;
|
|
525
|
+
return (tl.operations ?? []).some((op) => op.kind === "execution");
|
|
526
|
+
};
|
|
527
|
+
var buildDslContextFromDerived = (derived, payload = []) => {
|
|
528
|
+
const players = derived.players.map((p) => decoratePlayer(p, derived.characterMap, derived.currentTurn));
|
|
529
|
+
const effector = players.find((p) => p.seat === derived.effectorSeat);
|
|
530
|
+
const aliveCount = players.filter((p) => !p.isDead).length;
|
|
531
|
+
const deadCount = players.length - aliveCount;
|
|
532
|
+
const executedTodayCount = players.filter(
|
|
533
|
+
(p) => p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === derived.currentTurn
|
|
534
|
+
).length;
|
|
535
|
+
const nominatedTodayCount = derived.todayNominations.length;
|
|
536
|
+
const playerDiedToday = players.some(
|
|
537
|
+
(p) => p.isDead && typeof p.deathTurn === "number" && p.deathTurn === derived.currentTurn
|
|
538
|
+
);
|
|
539
|
+
return {
|
|
540
|
+
effector,
|
|
541
|
+
players,
|
|
542
|
+
operations: {
|
|
543
|
+
all: derived.allOperations,
|
|
544
|
+
today: derived.todayOperations,
|
|
545
|
+
tonight: derived.tonightOperations,
|
|
546
|
+
yesterdayDay: derived.yesterdayDayOperations,
|
|
547
|
+
yesterdayNight: derived.yesterdayNightOperations
|
|
548
|
+
},
|
|
549
|
+
nominations: {
|
|
550
|
+
all: derived.allNominations,
|
|
551
|
+
today: derived.todayNominations,
|
|
552
|
+
yesterday: derived.yesterdayNominations
|
|
553
|
+
},
|
|
554
|
+
currentTimeline: {
|
|
555
|
+
turn: derived.currentTurn,
|
|
556
|
+
phase: derived.currentPhase,
|
|
557
|
+
time: derived.currentPhase === "NIGHT" ? "night" : derived.currentPhase === "DAY" ? "day" : void 0,
|
|
558
|
+
isFirstOfPhase: derived.isFirstOfPhase,
|
|
559
|
+
isExecution: (derived.currentPhase === "DAY" ? derived.todayOperations : derived.tonightOperations).some((op) => op.kind === "execution")
|
|
560
|
+
},
|
|
561
|
+
global: {
|
|
562
|
+
aliveCount,
|
|
563
|
+
deadCount,
|
|
564
|
+
executedTodayCount,
|
|
565
|
+
nominatedTodayCount,
|
|
566
|
+
executionHappenedToday: derived.todayOperations.some((op) => op.kind === "execution"),
|
|
567
|
+
playerDiedToday
|
|
568
|
+
},
|
|
569
|
+
payload
|
|
570
|
+
};
|
|
571
|
+
};
|
|
572
|
+
var buildDslContextFromLifetime = ({
|
|
573
|
+
snapshotSeatMap,
|
|
574
|
+
timelines,
|
|
575
|
+
nowTimelineIndex,
|
|
576
|
+
characterMap,
|
|
577
|
+
effector,
|
|
578
|
+
payloads
|
|
579
|
+
}) => {
|
|
580
|
+
const nowTl = timelines[nowTimelineIndex];
|
|
581
|
+
const currentTurn = nowTl?.turn ?? -1;
|
|
582
|
+
const currentTime = nowTl?.time;
|
|
583
|
+
const currentPhase = currentTime === "night" ? "NIGHT" : currentTime === "day" ? "DAY" : "NIGHT";
|
|
584
|
+
const playersRaw = snapshotSeatMap ? [...snapshotSeatMap.values()] : [];
|
|
585
|
+
const players = playersRaw.map((p) => decoratePlayer(p, characterMap, currentTurn));
|
|
586
|
+
const effectorDecorated = effector ? players.find((p) => p.seat === effector.seat) ?? decoratePlayer(effector, characterMap, currentTurn) : void 0;
|
|
587
|
+
const todayOps = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "day").flatMap((tl) => tl.operations ?? []);
|
|
588
|
+
const tonightOps = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "night").flatMap((tl) => tl.operations ?? []);
|
|
589
|
+
const yesterdayDayOps = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "day").flatMap((tl) => tl.operations ?? []);
|
|
590
|
+
const yesterdayNightOps = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "night").flatMap((tl) => tl.operations ?? []);
|
|
591
|
+
const allOps = timelines.flatMap((tl) => tl.operations ?? []);
|
|
592
|
+
const todayNoms = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee })));
|
|
593
|
+
const yesterdayNoms = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee })));
|
|
594
|
+
const allNoms = timelines.filter((tl) => tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({
|
|
595
|
+
nominator: n.nominator,
|
|
596
|
+
nominee: n.nominee,
|
|
597
|
+
turn: tl.turn
|
|
598
|
+
})));
|
|
599
|
+
const aliveCount = players.filter((p) => !p.isDead).length;
|
|
600
|
+
const deadCount = players.length - aliveCount;
|
|
601
|
+
const executedTodayCount = players.filter(
|
|
602
|
+
(p) => p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === currentTurn
|
|
603
|
+
).length;
|
|
604
|
+
const playerDiedToday = timelines.some((tl) => {
|
|
605
|
+
if (tl.turn !== currentTurn || tl.time !== "day") return false;
|
|
606
|
+
return (tl.operations ?? []).some((op) => {
|
|
607
|
+
const kind = op.kind;
|
|
608
|
+
return kind === "execution" || kind === "death";
|
|
609
|
+
});
|
|
610
|
+
});
|
|
611
|
+
return {
|
|
612
|
+
effector: effectorDecorated,
|
|
613
|
+
players,
|
|
614
|
+
operations: {
|
|
615
|
+
all: allOps,
|
|
616
|
+
today: todayOps,
|
|
617
|
+
tonight: tonightOps,
|
|
618
|
+
yesterdayDay: yesterdayDayOps,
|
|
619
|
+
yesterdayNight: yesterdayNightOps
|
|
620
|
+
},
|
|
621
|
+
nominations: {
|
|
622
|
+
all: allNoms,
|
|
623
|
+
today: todayNoms,
|
|
624
|
+
yesterday: yesterdayNoms
|
|
625
|
+
},
|
|
626
|
+
currentTimeline: {
|
|
627
|
+
turn: currentTurn,
|
|
628
|
+
phase: currentPhase,
|
|
629
|
+
time: currentTime,
|
|
630
|
+
isFirstOfPhase: false,
|
|
631
|
+
isExecution: isExecutionTimeline(nowTl)
|
|
632
|
+
},
|
|
633
|
+
global: {
|
|
634
|
+
aliveCount,
|
|
635
|
+
deadCount,
|
|
636
|
+
executedTodayCount,
|
|
637
|
+
nominatedTodayCount: todayNoms.length,
|
|
638
|
+
executionHappenedToday: timelines.some(
|
|
639
|
+
(tl) => tl.turn === currentTurn && tl.time === "day" && isExecutionTimeline(tl)
|
|
640
|
+
),
|
|
641
|
+
playerDiedToday
|
|
642
|
+
},
|
|
643
|
+
payload: payloads
|
|
644
|
+
};
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
// src/dsl/bindings.ts
|
|
648
|
+
var rootBindings = (ctx) => ({
|
|
649
|
+
effector: ctx.effector,
|
|
650
|
+
players: ctx.players,
|
|
651
|
+
operations: ctx.operations,
|
|
652
|
+
nominations: ctx.nominations,
|
|
653
|
+
currentTimeline: ctx.currentTimeline,
|
|
654
|
+
global: ctx.global,
|
|
655
|
+
payload: ctx.payload
|
|
656
|
+
});
|
|
657
|
+
var extendBindings = (parent, name, value) => ({
|
|
658
|
+
...parent,
|
|
659
|
+
[name]: value
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// src/dsl/operators.ts
|
|
663
|
+
var deepEqual = (a, b) => {
|
|
664
|
+
if (a === b) return true;
|
|
665
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
666
|
+
if (a.length !== b.length) return false;
|
|
667
|
+
for (let i = 0; i < a.length; i++) {
|
|
668
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
669
|
+
}
|
|
670
|
+
return true;
|
|
671
|
+
}
|
|
672
|
+
return false;
|
|
673
|
+
};
|
|
674
|
+
var asNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
675
|
+
var compareOrder = (lhs, rhs, cmp) => {
|
|
676
|
+
const l = asNumber(lhs);
|
|
677
|
+
const r = asNumber(rhs);
|
|
678
|
+
if (l === void 0 || r === void 0) return false;
|
|
679
|
+
return cmp(l, r);
|
|
680
|
+
};
|
|
681
|
+
var applyOperator = (op, lhs, rhs) => {
|
|
682
|
+
switch (op) {
|
|
683
|
+
case "EQ":
|
|
684
|
+
return deepEqual(lhs, rhs);
|
|
685
|
+
case "NE":
|
|
686
|
+
return !deepEqual(lhs, rhs);
|
|
687
|
+
case "LT":
|
|
688
|
+
return compareOrder(lhs, rhs, (l, r) => l < r);
|
|
689
|
+
case "LTE":
|
|
690
|
+
return compareOrder(lhs, rhs, (l, r) => l <= r);
|
|
691
|
+
case "GT":
|
|
692
|
+
return compareOrder(lhs, rhs, (l, r) => l > r);
|
|
693
|
+
case "GTE":
|
|
694
|
+
return compareOrder(lhs, rhs, (l, r) => l >= r);
|
|
695
|
+
case "IN":
|
|
696
|
+
return Array.isArray(rhs) && rhs.some((v) => deepEqual(v, lhs));
|
|
697
|
+
case "NOT_IN":
|
|
698
|
+
return Array.isArray(rhs) && !rhs.some((v) => deepEqual(v, lhs));
|
|
699
|
+
case "CONTAINS":
|
|
700
|
+
if (Array.isArray(lhs)) return lhs.some((v) => deepEqual(v, rhs));
|
|
701
|
+
if (typeof lhs === "string") return typeof rhs === "string" && lhs.includes(rhs);
|
|
702
|
+
return false;
|
|
703
|
+
case "MATCHES":
|
|
704
|
+
if (typeof lhs !== "string" || typeof rhs !== "string") return false;
|
|
705
|
+
try {
|
|
706
|
+
return new RegExp(rhs).test(lhs);
|
|
707
|
+
} catch {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
case "EXISTS":
|
|
711
|
+
return lhs !== void 0 && lhs !== null;
|
|
712
|
+
case "TRUTHY":
|
|
713
|
+
return Boolean(lhs);
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
var applyCountOperator = (op, actual, expected) => {
|
|
717
|
+
switch (op) {
|
|
718
|
+
case "EQ":
|
|
719
|
+
return actual === expected;
|
|
720
|
+
case "NE":
|
|
721
|
+
return actual !== expected;
|
|
722
|
+
case "LT":
|
|
723
|
+
return actual < expected;
|
|
724
|
+
case "LTE":
|
|
725
|
+
return actual <= expected;
|
|
726
|
+
case "GT":
|
|
727
|
+
return actual > expected;
|
|
728
|
+
case "GTE":
|
|
729
|
+
return actual >= expected;
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
// src/dsl/path.ts
|
|
734
|
+
var MAX_STEPS = 32;
|
|
735
|
+
var stepInto = (current, step) => {
|
|
736
|
+
if (current === void 0 || current === null) return void 0;
|
|
737
|
+
if (step.kind === "field") {
|
|
738
|
+
if (typeof current !== "object") return void 0;
|
|
739
|
+
const record = current;
|
|
740
|
+
return record[step.name];
|
|
741
|
+
}
|
|
742
|
+
if (step.kind === "index") {
|
|
743
|
+
if (!Array.isArray(current)) return void 0;
|
|
744
|
+
return current[step.value];
|
|
745
|
+
}
|
|
746
|
+
return void 0;
|
|
747
|
+
};
|
|
748
|
+
var evaluatePath = (path, bindings) => {
|
|
749
|
+
if (path.steps.length > MAX_STEPS) return void 0;
|
|
750
|
+
let current = Object.prototype.hasOwnProperty.call(bindings, path.root) ? bindings[path.root] : void 0;
|
|
751
|
+
for (const step of path.steps) {
|
|
752
|
+
current = stepInto(current, step);
|
|
753
|
+
if (current === void 0) return void 0;
|
|
754
|
+
}
|
|
755
|
+
return current;
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
// src/dsl/eval.ts
|
|
759
|
+
var MAX_EXPR_DEPTH = 16;
|
|
760
|
+
var MAX_QUANTIFIER_SIZE = 256;
|
|
761
|
+
var resolveValue = (value, bindings) => {
|
|
762
|
+
if (!value) return void 0;
|
|
763
|
+
if (value.kind === "LITERAL") return value.value;
|
|
764
|
+
return evaluatePath(value.path, bindings);
|
|
765
|
+
};
|
|
766
|
+
var evalAtom = (atom, bindings, depth) => {
|
|
767
|
+
if (atom.type === "PATH") {
|
|
768
|
+
const lhs = evaluatePath(atom.path, bindings);
|
|
769
|
+
const rhs = resolveValue(atom.value, bindings);
|
|
770
|
+
return applyOperator(atom.op, lhs, rhs);
|
|
771
|
+
}
|
|
772
|
+
const source = evaluatePath(atom.source, bindings);
|
|
773
|
+
if (!Array.isArray(source)) {
|
|
774
|
+
return atom.type === "FORALL";
|
|
775
|
+
}
|
|
776
|
+
if (source.length > MAX_QUANTIFIER_SIZE) return false;
|
|
777
|
+
if (atom.type === "EXISTS") {
|
|
778
|
+
return source.some(
|
|
779
|
+
(item) => evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
if (atom.type === "FORALL") {
|
|
783
|
+
return source.every(
|
|
784
|
+
(item) => evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
let count = 0;
|
|
788
|
+
for (const item of source) {
|
|
789
|
+
if (!atom.where || evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)) {
|
|
790
|
+
count += 1;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return applyCountOperator(atom.op, count, atom.value);
|
|
794
|
+
};
|
|
795
|
+
var evalExpr = (expr, bindings, depth = 0) => {
|
|
796
|
+
if (depth > MAX_EXPR_DEPTH) return false;
|
|
797
|
+
switch (expr.op) {
|
|
798
|
+
case "AND":
|
|
799
|
+
return expr.children.every((child) => evalExpr(child, bindings, depth + 1));
|
|
800
|
+
case "OR":
|
|
801
|
+
return expr.children.some((child) => evalExpr(child, bindings, depth + 1));
|
|
802
|
+
case "NOT":
|
|
803
|
+
return !evalExpr(expr.child, bindings, depth + 1);
|
|
804
|
+
case "ATOM":
|
|
805
|
+
return evalAtom(expr.atom, bindings, depth + 1);
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
var evalDslExpr = (expr, ctx) => evalExpr(expr, rootBindings(ctx), 0);
|
|
809
|
+
|
|
810
|
+
// src/dsl/catalog.ts
|
|
811
|
+
var PLAYER_FIELDS = [
|
|
812
|
+
{ name: "seat", type: "number", label: "\u5EA7\u4F4D\u53F7" },
|
|
813
|
+
{ name: "alignment", type: "string", label: "\u9635\u8425", description: "GOOD / EVIL" },
|
|
814
|
+
{ name: "isDead", type: "boolean", label: "\u662F\u5426\u6B7B\u4EA1" },
|
|
815
|
+
{ name: "effectiveAlive", type: "boolean", label: "\u6709\u6548\u5B58\u6D3B", description: "\u8003\u8651 retainsAbility\uFF08ALIVENESS/ALL\uFF09\u540E\u662F\u5426\u6309\u5B58\u6D3B\u5904\u7406" },
|
|
816
|
+
{ name: "diedThisTurn", type: "boolean", label: "\u672C\u56DE\u5408\u6B7B\u4EA1" },
|
|
817
|
+
{ name: "wasExecuted", type: "boolean", label: "\u66FE\u88AB\u5904\u51B3" },
|
|
818
|
+
{ name: "wasExecutedToday", type: "boolean", label: "\u4ECA\u65E5\u88AB\u5904\u51B3" },
|
|
819
|
+
{ name: "characterId", type: "string", label: "\u89D2\u8272 ID" },
|
|
820
|
+
{ name: "characterKind", type: "string", label: "\u89D2\u8272\u7C7B\u578B", description: "Townsfolk / Outsiders / Minions / Demons" },
|
|
821
|
+
{ name: "deathCause", type: "string", label: "\u6B7B\u4EA1\u539F\u56E0", description: "EXECUTION / DEMON / ABILITY / STORYTELLER / OTHER" },
|
|
822
|
+
{ name: "deathTurn", type: "number", label: "\u6B7B\u4EA1\u56DE\u5408" },
|
|
823
|
+
{ name: "abilities", type: "string[]", label: "\u80FD\u529B ID \u5217\u8868" },
|
|
824
|
+
{ name: "reminderMarks", type: "string[]", label: "\u63D0\u793A\u7269 mark \u5217\u8868" },
|
|
825
|
+
{ name: "hasUsedDeadVote", type: "boolean", label: "\u662F\u5426\u4F7F\u7528\u8FC7\u6B7B\u4EA1\u6295\u7968" },
|
|
826
|
+
{ name: "retainsAbility", type: "string", label: "\u80FD\u529B\u4FDD\u7559\u8303\u56F4" }
|
|
827
|
+
];
|
|
828
|
+
var OPERATION_FIELDS = [
|
|
829
|
+
{ name: "effector", type: "number", label: "\u65BD\u52A8\u8005\u5EA7\u4F4D" },
|
|
830
|
+
{ name: "abilityId", type: "string", label: "\u80FD\u529B ID" },
|
|
831
|
+
{ name: "kind", type: "string", label: "\u64CD\u4F5C\u79CD\u7C7B" },
|
|
832
|
+
{ name: "payloads", type: "unknown[]", label: "Payload \u5217\u8868" }
|
|
833
|
+
];
|
|
834
|
+
var NOMINATION_FIELDS = [
|
|
835
|
+
{ name: "nominator", type: "number", label: "\u63D0\u540D\u8005\u5EA7\u4F4D" },
|
|
836
|
+
{ name: "nominee", type: "number", label: "\u88AB\u63D0\u540D\u8005\u5EA7\u4F4D" },
|
|
837
|
+
{ name: "turn", type: "number", label: "\u56DE\u5408", description: "\u4EC5 all \u96C6\u5408\u53EF\u7528" }
|
|
838
|
+
];
|
|
839
|
+
var CURRENT_TIMELINE_FIELDS = [
|
|
840
|
+
{ name: "turn", type: "number", label: "\u5F53\u524D\u56DE\u5408" },
|
|
841
|
+
{ name: "phase", type: "string", label: "\u5F53\u524D\u9636\u6BB5", description: "NIGHT / DAY / DUSK / DAWN" },
|
|
842
|
+
{ name: "time", type: "string", label: "\u5F53\u524D\u65F6\u6BB5", description: "night / day" },
|
|
843
|
+
{ name: "isFirstOfPhase", type: "boolean", label: "\u662F\u5426\u672C\u9636\u6BB5\u9996\u6B21" },
|
|
844
|
+
{ name: "isExecution", type: "boolean", label: "\u5F53\u524D\u65F6\u95F4\u7EBF\u542B\u5904\u51B3" }
|
|
845
|
+
];
|
|
846
|
+
var GLOBAL_FIELDS = [
|
|
847
|
+
{ name: "aliveCount", type: "number", label: "\u5B58\u6D3B\u73A9\u5BB6\u6570" },
|
|
848
|
+
{ name: "deadCount", type: "number", label: "\u6B7B\u4EA1\u73A9\u5BB6\u6570" },
|
|
849
|
+
{ name: "executedTodayCount", type: "number", label: "\u4ECA\u65E5\u88AB\u5904\u51B3\u4EBA\u6570" },
|
|
850
|
+
{ name: "nominatedTodayCount", type: "number", label: "\u4ECA\u65E5\u63D0\u540D\u603B\u6570" },
|
|
851
|
+
{ name: "executionHappenedToday", type: "boolean", label: "\u4ECA\u65E5\u662F\u5426\u5DF2\u53D1\u751F\u5904\u51B3" },
|
|
852
|
+
{ name: "playerDiedToday", type: "boolean", label: "\u4ECA\u65E5\u662F\u5426\u6709\u73A9\u5BB6\u6B7B\u4EA1" }
|
|
853
|
+
];
|
|
854
|
+
var OPERATIONS_FIELDS = [
|
|
855
|
+
{ name: "all", type: "operation[]", label: "\u6240\u6709\u64CD\u4F5C" },
|
|
856
|
+
{ name: "today", type: "operation[]", label: "\u4ECA\u65E5\u64CD\u4F5C" },
|
|
857
|
+
{ name: "tonight", type: "operation[]", label: "\u4ECA\u591C\u64CD\u4F5C" },
|
|
858
|
+
{ name: "yesterdayDay", type: "operation[]", label: "\u6628\u65E5\u767D\u5929\u64CD\u4F5C" },
|
|
859
|
+
{ name: "yesterdayNight", type: "operation[]", label: "\u6628\u591C\u64CD\u4F5C" }
|
|
860
|
+
];
|
|
861
|
+
var NOMINATIONS_FIELDS = [
|
|
862
|
+
{ name: "all", type: "nomination[]", label: "\u6240\u6709\u63D0\u540D" },
|
|
863
|
+
{ name: "today", type: "nomination[]", label: "\u4ECA\u65E5\u63D0\u540D" },
|
|
864
|
+
{ name: "yesterday", type: "nomination[]", label: "\u6628\u65E5\u63D0\u540D" }
|
|
865
|
+
];
|
|
866
|
+
var DSL_CATALOG = [
|
|
867
|
+
{
|
|
868
|
+
name: "effector",
|
|
869
|
+
type: "player",
|
|
870
|
+
label: "\u80FD\u529B\u53D1\u52A8\u8005",
|
|
871
|
+
description: "\u5F53\u524D\u8BC4\u4F30\u65F6\u7684\u80FD\u529B\u65BD\u653E\u8005\u5FEB\u7167",
|
|
872
|
+
fields: PLAYER_FIELDS
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
name: "players",
|
|
876
|
+
type: "player[]",
|
|
877
|
+
label: "\u5168\u90E8\u73A9\u5BB6",
|
|
878
|
+
description: "\u5F53\u524D\u5FEB\u7167\u4E0B\u6240\u6709\u73A9\u5BB6\uFF08\u5E94\u914D\u5408 EXISTS / FORALL / COUNT \u4F7F\u7528\uFF09"
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
name: "operations",
|
|
882
|
+
type: "object",
|
|
883
|
+
label: "\u5386\u53F2\u64CD\u4F5C\u96C6\u5408",
|
|
884
|
+
fields: OPERATIONS_FIELDS
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
name: "nominations",
|
|
888
|
+
type: "object",
|
|
889
|
+
label: "\u5386\u53F2\u63D0\u540D\u96C6\u5408",
|
|
890
|
+
fields: NOMINATIONS_FIELDS
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
name: "currentTimeline",
|
|
894
|
+
type: "object",
|
|
895
|
+
label: "\u5F53\u524D\u65F6\u95F4\u7EBF",
|
|
896
|
+
fields: CURRENT_TIMELINE_FIELDS
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
name: "global",
|
|
900
|
+
type: "object",
|
|
901
|
+
label: "\u5168\u5C40\u805A\u5408",
|
|
902
|
+
fields: GLOBAL_FIELDS
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
name: "payload",
|
|
906
|
+
type: "unknown[]",
|
|
907
|
+
label: "\u80FD\u529B Payload \u6570\u7EC4",
|
|
908
|
+
description: "\u5F53\u524D\u8C03\u7528 / \u64CD\u4F5C\u7684 payload \u539F\u59CB\u6570\u7EC4"
|
|
909
|
+
}
|
|
910
|
+
];
|
|
911
|
+
var DSL_ITEM_FIELDS = {
|
|
912
|
+
player: PLAYER_FIELDS,
|
|
913
|
+
operation: OPERATION_FIELDS,
|
|
914
|
+
nomination: NOMINATION_FIELDS
|
|
915
|
+
};
|
|
916
|
+
var DSL_OPERATORS = [
|
|
917
|
+
{ value: "EQ", label: "\u7B49\u4E8E", arity: "binary" },
|
|
918
|
+
{ value: "NE", label: "\u4E0D\u7B49\u4E8E", arity: "binary" },
|
|
919
|
+
{ value: "LT", label: "<", arity: "binary" },
|
|
920
|
+
{ value: "LTE", label: "<=", arity: "binary" },
|
|
921
|
+
{ value: "GT", label: ">", arity: "binary" },
|
|
922
|
+
{ value: "GTE", label: ">=", arity: "binary" },
|
|
923
|
+
{ value: "IN", label: "\u5C5E\u4E8E", arity: "binary" },
|
|
924
|
+
{ value: "NOT_IN", label: "\u4E0D\u5C5E\u4E8E", arity: "binary" },
|
|
925
|
+
{ value: "CONTAINS", label: "\u5305\u542B", arity: "binary" },
|
|
926
|
+
{ value: "MATCHES", label: "\u6B63\u5219\u5339\u914D", arity: "binary" },
|
|
927
|
+
{ value: "EXISTS", label: "\u5B58\u5728", arity: "unary" },
|
|
928
|
+
{ value: "TRUTHY", label: "\u4E3A\u771F", arity: "unary" }
|
|
929
|
+
];
|
|
930
|
+
|
|
931
|
+
// src/dsl/trace.ts
|
|
932
|
+
var MAX_QUANTIFIER_SIZE2 = 256;
|
|
933
|
+
var renderPath = (p) => p.steps.reduce((acc, s) => acc + (s.kind === "field" ? `.${s.name}` : `[${s.value}]`), p.root);
|
|
934
|
+
var resolveValue2 = (value, bindings) => {
|
|
935
|
+
if (!value) return void 0;
|
|
936
|
+
if (value.kind === "LITERAL") return value.value;
|
|
937
|
+
return evaluatePath(value.path, bindings);
|
|
938
|
+
};
|
|
939
|
+
var renderValue = (value, bindings) => {
|
|
940
|
+
if (!value) return void 0;
|
|
941
|
+
if (value.kind === "LITERAL") return value.value;
|
|
942
|
+
return { ref: renderPath(value.path), resolved: evaluatePath(value.path, bindings) };
|
|
943
|
+
};
|
|
944
|
+
var traceAtom = (atom, bindings) => {
|
|
945
|
+
if (atom.type === "PATH") {
|
|
946
|
+
const lhs = evaluatePath(atom.path, bindings);
|
|
947
|
+
const rhs = resolveValue2(atom.value, bindings);
|
|
948
|
+
const ok2 = applyOperator(atom.op, lhs, rhs);
|
|
949
|
+
return {
|
|
950
|
+
op: "ATOM",
|
|
951
|
+
atomType: "PATH",
|
|
952
|
+
ok: ok2,
|
|
953
|
+
path: renderPath(atom.path),
|
|
954
|
+
operator: atom.op,
|
|
955
|
+
lhs,
|
|
956
|
+
rhs: renderValue(atom.value, bindings)
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
const source = evaluatePath(atom.source, bindings);
|
|
960
|
+
const sourceArr = Array.isArray(source) ? source : [];
|
|
961
|
+
const sizeCapped = sourceArr.length > MAX_QUANTIFIER_SIZE2;
|
|
962
|
+
let matched = 0;
|
|
963
|
+
if (!sizeCapped) {
|
|
964
|
+
for (const item of sourceArr) {
|
|
965
|
+
const childBindings = extendBindings(bindings, atom.as, item);
|
|
966
|
+
if (!atom.where || traceExprOk(atom.where, childBindings)) matched += 1;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (atom.type === "COUNT") {
|
|
970
|
+
const ok2 = !sizeCapped && applyCountOperator(atom.op, matched, atom.value);
|
|
971
|
+
return {
|
|
972
|
+
op: "ATOM",
|
|
973
|
+
atomType: "COUNT",
|
|
974
|
+
ok: ok2,
|
|
975
|
+
source: renderPath(atom.source),
|
|
976
|
+
size: sourceArr.length,
|
|
977
|
+
matched,
|
|
978
|
+
operator: atom.op,
|
|
979
|
+
expected: atom.value,
|
|
980
|
+
bindings: atom.as
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
const ok = atom.type === "EXISTS" ? matched > 0 : !sizeCapped && (sourceArr.length === 0 || matched === sourceArr.length);
|
|
984
|
+
return {
|
|
985
|
+
op: "ATOM",
|
|
986
|
+
atomType: atom.type,
|
|
987
|
+
ok,
|
|
988
|
+
source: renderPath(atom.source),
|
|
989
|
+
size: sourceArr.length,
|
|
990
|
+
matched,
|
|
991
|
+
bindings: atom.as
|
|
992
|
+
};
|
|
993
|
+
};
|
|
994
|
+
var traceExprOk = (expr, bindings) => {
|
|
995
|
+
switch (expr.op) {
|
|
996
|
+
case "AND":
|
|
997
|
+
return expr.children.every((c) => traceExprOk(c, bindings));
|
|
998
|
+
case "OR":
|
|
999
|
+
return expr.children.some((c) => traceExprOk(c, bindings));
|
|
1000
|
+
case "NOT":
|
|
1001
|
+
return !traceExprOk(expr.child, bindings);
|
|
1002
|
+
case "ATOM":
|
|
1003
|
+
return traceAtom(expr.atom, bindings).ok;
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
var traceExpr = (expr, bindings) => {
|
|
1007
|
+
if (expr.op === "AND") {
|
|
1008
|
+
const children = expr.children.map((c) => traceExpr(c, bindings));
|
|
1009
|
+
return { op: "AND", ok: children.every((c) => c.ok), children };
|
|
1010
|
+
}
|
|
1011
|
+
if (expr.op === "OR") {
|
|
1012
|
+
const children = expr.children.map((c) => traceExpr(c, bindings));
|
|
1013
|
+
return { op: "OR", ok: children.some((c) => c.ok), children };
|
|
1014
|
+
}
|
|
1015
|
+
if (expr.op === "NOT") {
|
|
1016
|
+
const child = traceExpr(expr.child, bindings);
|
|
1017
|
+
return { op: "NOT", ok: !child.ok, child };
|
|
1018
|
+
}
|
|
1019
|
+
return traceAtom(expr.atom, bindings);
|
|
1020
|
+
};
|
|
1021
|
+
var traceDslExpr = (expr, ctx) => traceExpr(expr, rootBindings(ctx));
|
|
1022
|
+
|
|
508
1023
|
// src/effects/resolve-targets.ts
|
|
509
1024
|
var isPayloadRef = (value) => {
|
|
510
1025
|
if (!value || typeof value !== "object") {
|
|
@@ -550,7 +1065,23 @@ var resolveSelectorScope = (dynamicTarget, payloads) => {
|
|
|
550
1065
|
(value) => value === "BOTH_SIDES" || value === "LEFT_SIDE" || value === "RIGHT_SIDE"
|
|
551
1066
|
) ?? "BOTH_SIDES";
|
|
552
1067
|
};
|
|
553
|
-
var
|
|
1068
|
+
var matchesExprCondition = (player, condition, playerSeatMap, characterMap, effector, payloads) => {
|
|
1069
|
+
const dslCtx = buildDslContextFromLifetime({
|
|
1070
|
+
snapshotSeatMap: playerSeatMap,
|
|
1071
|
+
timelines: [],
|
|
1072
|
+
nowTimelineIndex: -1,
|
|
1073
|
+
characterMap,
|
|
1074
|
+
effector,
|
|
1075
|
+
payloads
|
|
1076
|
+
});
|
|
1077
|
+
const decoratedPlayer = dslCtx.players.find((p) => p.seat === player.seat);
|
|
1078
|
+
const bindings = extendBindings(rootBindings(dslCtx), "it", decoratedPlayer ?? player);
|
|
1079
|
+
return evalExpr(condition.expr, bindings);
|
|
1080
|
+
};
|
|
1081
|
+
var matchesCondition = (player, condition, payloads, characterMap, playerSeatMap, effector) => {
|
|
1082
|
+
if (condition.kind === "EXPR") {
|
|
1083
|
+
return matchesExprCondition(player, condition, playerSeatMap, characterMap, effector, payloads);
|
|
1084
|
+
}
|
|
554
1085
|
if (condition.field === "IS_DEAD") {
|
|
555
1086
|
const expected = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "boolean");
|
|
556
1087
|
if (typeof expected !== "boolean") {
|
|
@@ -566,6 +1097,18 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
|
566
1097
|
const expectedSeat = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "number");
|
|
567
1098
|
return typeof expectedSeat === "number" && player.seat === expectedSeat;
|
|
568
1099
|
}
|
|
1100
|
+
if (condition.field === "ALIGNMENT") {
|
|
1101
|
+
const alignment = player.alignment;
|
|
1102
|
+
if (!alignment) {
|
|
1103
|
+
return false;
|
|
1104
|
+
}
|
|
1105
|
+
if (condition.operator === "IN") {
|
|
1106
|
+
const expectedAlignments = resolveDynamicValue(condition.value, payloads, (value) => Array.isArray(value) && value.every((item) => typeof item === "string"));
|
|
1107
|
+
return Array.isArray(expectedAlignments) && expectedAlignments.includes(alignment);
|
|
1108
|
+
}
|
|
1109
|
+
const expectedAlignment = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
|
|
1110
|
+
return typeof expectedAlignment === "string" && alignment === expectedAlignment;
|
|
1111
|
+
}
|
|
569
1112
|
const character = characterMap.get(player.characterId);
|
|
570
1113
|
const kind = character?.kind;
|
|
571
1114
|
if (!kind) {
|
|
@@ -578,15 +1121,17 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
|
578
1121
|
const expectedKind = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
|
|
579
1122
|
return typeof expectedKind === "string" && kind === expectedKind;
|
|
580
1123
|
};
|
|
581
|
-
var passesWhere = (player, dynamicTarget, payloads, characterMap) => {
|
|
1124
|
+
var passesWhere = (player, dynamicTarget, payloads, characterMap, playerSeatMap, effector) => {
|
|
582
1125
|
const where = dynamicTarget.where;
|
|
583
1126
|
const conditions = where?.conditions ?? [];
|
|
584
1127
|
if (conditions.length === 0) return true;
|
|
585
1128
|
const isAllMode = where?.mode !== "ANY";
|
|
586
|
-
const results = conditions.map(
|
|
1129
|
+
const results = conditions.map(
|
|
1130
|
+
(condition) => matchesCondition(player, condition, payloads, characterMap, playerSeatMap, effector)
|
|
1131
|
+
);
|
|
587
1132
|
return isAllMode ? results.every(Boolean) : results.some(Boolean);
|
|
588
1133
|
};
|
|
589
|
-
var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap) => {
|
|
1134
|
+
var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector) => {
|
|
590
1135
|
const scope = resolveSelectorScope(dynamicTarget, payloads);
|
|
591
1136
|
if (!anchorSeat) return { left: [], right: [] };
|
|
592
1137
|
const anchorIdx = sortedPlayers.findIndex((player) => player.seat === anchorSeat);
|
|
@@ -597,13 +1142,13 @@ var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, c
|
|
|
597
1142
|
if (scope === "LEFT_SIDE" || scope === "BOTH_SIDES") {
|
|
598
1143
|
for (let step = 1; step < total; step += 1) {
|
|
599
1144
|
const player = sortedPlayers[(anchorIdx + step) % total];
|
|
600
|
-
if (passesWhere(player, dynamicTarget, payloads, characterMap)) left.push(player);
|
|
1145
|
+
if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) left.push(player);
|
|
601
1146
|
}
|
|
602
1147
|
}
|
|
603
1148
|
if (scope === "RIGHT_SIDE" || scope === "BOTH_SIDES") {
|
|
604
1149
|
for (let step = 1; step < total; step += 1) {
|
|
605
1150
|
const player = sortedPlayers[(anchorIdx - step + total) % total];
|
|
606
|
-
if (passesWhere(player, dynamicTarget, payloads, characterMap)) right.push(player);
|
|
1151
|
+
if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) right.push(player);
|
|
607
1152
|
}
|
|
608
1153
|
}
|
|
609
1154
|
return { left, right };
|
|
@@ -677,10 +1222,12 @@ var resolveEffectTargets = ({
|
|
|
677
1222
|
const sortedPlayers = getSortedPlayers(playerSeatMap);
|
|
678
1223
|
const anchorSeat = getAnchorSeat(dynamicTarget, payloads, effector);
|
|
679
1224
|
if (!dynamicTarget.selector) {
|
|
680
|
-
const matches = sortedPlayers.filter(
|
|
1225
|
+
const matches = sortedPlayers.filter(
|
|
1226
|
+
(player) => passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)
|
|
1227
|
+
);
|
|
681
1228
|
return pickBalanced({ left: matches, right: [] }, dynamicTarget, payloads);
|
|
682
1229
|
}
|
|
683
|
-
const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap);
|
|
1230
|
+
const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector);
|
|
684
1231
|
return pickBalanced(sides, dynamicTarget, payloads);
|
|
685
1232
|
}
|
|
686
1233
|
return [];
|
|
@@ -826,7 +1373,7 @@ var evalPlayerState = (atom, ctx) => {
|
|
|
826
1373
|
const single = players[0];
|
|
827
1374
|
return evalPlayerStateOnPlayer(single, atom, ctx);
|
|
828
1375
|
};
|
|
829
|
-
var
|
|
1376
|
+
var isExecutionTimeline2 = (tl) => {
|
|
830
1377
|
if (!tl) return false;
|
|
831
1378
|
return (tl.operations ?? []).some((op) => op.kind === "execution");
|
|
832
1379
|
};
|
|
@@ -854,7 +1401,7 @@ var evalTime = (atom, ctx) => {
|
|
|
854
1401
|
if (spec.event === "NEXT_NIGHT" && tl.time === "night") return false;
|
|
855
1402
|
if (spec.event === "NEXT_DUSK" && prevTime === "day" && tl.time === "night") return false;
|
|
856
1403
|
if (spec.event === "NEXT_DAWN" && prevTime === "night" && tl.time === "day") return false;
|
|
857
|
-
if (spec.event === "NEXT_EXECUTION" &&
|
|
1404
|
+
if (spec.event === "NEXT_EXECUTION" && isExecutionTimeline2(tl)) return false;
|
|
858
1405
|
prevTime = tl.time;
|
|
859
1406
|
}
|
|
860
1407
|
return true;
|
|
@@ -895,7 +1442,7 @@ var evalGameState = (atom, ctx) => {
|
|
|
895
1442
|
if (spec.kind === "EXECUTION_HAPPENED_TODAY") {
|
|
896
1443
|
const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
|
|
897
1444
|
if (typeof turn !== "number") return false;
|
|
898
|
-
return ctx.timelines.some((tl) => tl.turn === turn && tl.time === "day" &&
|
|
1445
|
+
return ctx.timelines.some((tl) => tl.turn === turn && tl.time === "day" && isExecutionTimeline2(tl));
|
|
899
1446
|
}
|
|
900
1447
|
if (spec.kind === "PLAYER_DIED_TODAY") {
|
|
901
1448
|
const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
|
|
@@ -910,7 +1457,7 @@ var evalGameState = (atom, ctx) => {
|
|
|
910
1457
|
}
|
|
911
1458
|
return true;
|
|
912
1459
|
};
|
|
913
|
-
var
|
|
1460
|
+
var evalAtom2 = (atom, ctx) => {
|
|
914
1461
|
switch (atom.type) {
|
|
915
1462
|
case "TIME":
|
|
916
1463
|
return evalTime(atom, ctx);
|
|
@@ -929,18 +1476,30 @@ var evalAtom = (atom, ctx) => {
|
|
|
929
1476
|
target: ctx.target,
|
|
930
1477
|
snapshotSeatMap: ctx.snapshotSeatMap
|
|
931
1478
|
}) : false;
|
|
1479
|
+
case "DSL": {
|
|
1480
|
+
if (!ctx.snapshotSeatMap) return true;
|
|
1481
|
+
const dslCtx = buildDslContextFromLifetime({
|
|
1482
|
+
snapshotSeatMap: ctx.snapshotSeatMap,
|
|
1483
|
+
timelines: ctx.timelines,
|
|
1484
|
+
nowTimelineIndex: ctx.nowTimelineIndex,
|
|
1485
|
+
characterMap: ctx.characterMap,
|
|
1486
|
+
effector: ctx.effector,
|
|
1487
|
+
payloads: ctx.payloads
|
|
1488
|
+
});
|
|
1489
|
+
return evalDslExpr(atom.expr, dslCtx);
|
|
1490
|
+
}
|
|
932
1491
|
}
|
|
933
1492
|
};
|
|
934
|
-
var
|
|
1493
|
+
var evalExpr2 = (expr, ctx) => {
|
|
935
1494
|
switch (expr.op) {
|
|
936
1495
|
case "AND":
|
|
937
|
-
return expr.children.every((child) =>
|
|
1496
|
+
return expr.children.every((child) => evalExpr2(child, ctx));
|
|
938
1497
|
case "OR":
|
|
939
|
-
return expr.children.some((child) =>
|
|
1498
|
+
return expr.children.some((child) => evalExpr2(child, ctx));
|
|
940
1499
|
case "NOT":
|
|
941
|
-
return !
|
|
1500
|
+
return !evalExpr2(expr.child, ctx);
|
|
942
1501
|
case "ATOM":
|
|
943
|
-
return
|
|
1502
|
+
return evalAtom2(expr.atom, ctx);
|
|
944
1503
|
}
|
|
945
1504
|
};
|
|
946
1505
|
var expressionReferencesTarget = (expr) => {
|
|
@@ -978,9 +1537,9 @@ var evaluateLifetime = ({
|
|
|
978
1537
|
const expr = lifetime.expr;
|
|
979
1538
|
const dependsOnTarget = expressionReferencesTarget(expr);
|
|
980
1539
|
if (!dependsOnTarget) {
|
|
981
|
-
return
|
|
1540
|
+
return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
|
|
982
1541
|
}
|
|
983
|
-
return targets.filter((target) =>
|
|
1542
|
+
return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
|
|
984
1543
|
};
|
|
985
1544
|
var evaluatePrecondition = ({
|
|
986
1545
|
expr,
|
|
@@ -1006,9 +1565,9 @@ var evaluatePrecondition = ({
|
|
|
1006
1565
|
};
|
|
1007
1566
|
const dependsOnTarget = expressionReferencesTarget(expr);
|
|
1008
1567
|
if (!dependsOnTarget) {
|
|
1009
|
-
return
|
|
1568
|
+
return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
|
|
1010
1569
|
}
|
|
1011
|
-
return targets.filter((target) =>
|
|
1570
|
+
return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
|
|
1012
1571
|
};
|
|
1013
1572
|
|
|
1014
1573
|
// src/apply-operation.ts
|
|
@@ -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
|
};
|