@bct-app/game-engine 0.1.17 → 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 +731 -108
- package/dist/index.mjs +715 -108
- 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") {
|
|
@@ -532,11 +1047,6 @@ var resolveDynamicValue = (value, payloads, isExpected) => {
|
|
|
532
1047
|
var getSortedPlayers = (playerSeatMap) => {
|
|
533
1048
|
return [...playerSeatMap.values()].sort((left, right) => left.seat - right.seat);
|
|
534
1049
|
};
|
|
535
|
-
var getCircularDistance = (anchorIdx, targetIdx, total) => {
|
|
536
|
-
const clockwise = (targetIdx - anchorIdx + total) % total;
|
|
537
|
-
const anticlockwise = (anchorIdx - targetIdx + total) % total;
|
|
538
|
-
return Math.min(clockwise, anticlockwise);
|
|
539
|
-
};
|
|
540
1050
|
var getAnchorSeat = (dynamicTarget, payloads, effector) => {
|
|
541
1051
|
const anchor = dynamicTarget.anchor;
|
|
542
1052
|
if (!anchor || anchor.from === "EFFECTOR") {
|
|
@@ -555,43 +1065,23 @@ var resolveSelectorScope = (dynamicTarget, payloads) => {
|
|
|
555
1065
|
(value) => value === "BOTH_SIDES" || value === "LEFT_SIDE" || value === "RIGHT_SIDE"
|
|
556
1066
|
) ?? "BOTH_SIDES";
|
|
557
1067
|
};
|
|
558
|
-
var
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
571
|
-
const total = sortedPlayers.length;
|
|
572
|
-
if (scope === "LEFT_SIDE") {
|
|
573
|
-
const left2 = [];
|
|
574
|
-
for (let step = 1; step < total; step += 1) {
|
|
575
|
-
left2.push(sortedPlayers[(anchorIdx + step) % total]);
|
|
576
|
-
}
|
|
577
|
-
return left2;
|
|
578
|
-
}
|
|
579
|
-
if (scope === "RIGHT_SIDE") {
|
|
580
|
-
const right2 = [];
|
|
581
|
-
for (let step = 1; step < total; step += 1) {
|
|
582
|
-
right2.push(sortedPlayers[(anchorIdx - step + total) % total]);
|
|
583
|
-
}
|
|
584
|
-
return right2;
|
|
585
|
-
}
|
|
586
|
-
const left = [];
|
|
587
|
-
const right = [];
|
|
588
|
-
for (let step = 1; step < total; step += 1) {
|
|
589
|
-
left.push(sortedPlayers[(anchorIdx + step) % total]);
|
|
590
|
-
right.push(sortedPlayers[(anchorIdx - step + total) % total]);
|
|
591
|
-
}
|
|
592
|
-
return [...left, ...right];
|
|
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);
|
|
593
1080
|
};
|
|
594
|
-
var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
1081
|
+
var matchesCondition = (player, condition, payloads, characterMap, playerSeatMap, effector) => {
|
|
1082
|
+
if (condition.kind === "EXPR") {
|
|
1083
|
+
return matchesExprCondition(player, condition, playerSeatMap, characterMap, effector, payloads);
|
|
1084
|
+
}
|
|
595
1085
|
if (condition.field === "IS_DEAD") {
|
|
596
1086
|
const expected = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "boolean");
|
|
597
1087
|
if (typeof expected !== "boolean") {
|
|
@@ -607,6 +1097,18 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
|
607
1097
|
const expectedSeat = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "number");
|
|
608
1098
|
return typeof expectedSeat === "number" && player.seat === expectedSeat;
|
|
609
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
|
+
}
|
|
610
1112
|
const character = characterMap.get(player.characterId);
|
|
611
1113
|
const kind = character?.kind;
|
|
612
1114
|
if (!kind) {
|
|
@@ -619,55 +1121,74 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
|
619
1121
|
const expectedKind = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
|
|
620
1122
|
return typeof expectedKind === "string" && kind === expectedKind;
|
|
621
1123
|
};
|
|
622
|
-
var
|
|
1124
|
+
var passesWhere = (player, dynamicTarget, payloads, characterMap, playerSeatMap, effector) => {
|
|
623
1125
|
const where = dynamicTarget.where;
|
|
624
1126
|
const conditions = where?.conditions ?? [];
|
|
625
|
-
if (conditions.length === 0)
|
|
626
|
-
return candidates;
|
|
627
|
-
}
|
|
1127
|
+
if (conditions.length === 0) return true;
|
|
628
1128
|
const isAllMode = where?.mode !== "ANY";
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1129
|
+
const results = conditions.map(
|
|
1130
|
+
(condition) => matchesCondition(player, condition, payloads, characterMap, playerSeatMap, effector)
|
|
1131
|
+
);
|
|
1132
|
+
return isAllMode ? results.every(Boolean) : results.some(Boolean);
|
|
633
1133
|
};
|
|
634
|
-
var
|
|
1134
|
+
var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector) => {
|
|
635
1135
|
const scope = resolveSelectorScope(dynamicTarget, payloads);
|
|
636
|
-
if (
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
}
|
|
647
|
-
return [...candidates].sort((left, right) => {
|
|
648
|
-
const leftIdx = seatIndexMap.get(left.seat);
|
|
649
|
-
const rightIdx = seatIndexMap.get(right.seat);
|
|
650
|
-
if (typeof leftIdx !== "number" || typeof rightIdx !== "number") {
|
|
651
|
-
return left.seat - right.seat;
|
|
1136
|
+
if (!anchorSeat) return { left: [], right: [] };
|
|
1137
|
+
const anchorIdx = sortedPlayers.findIndex((player) => player.seat === anchorSeat);
|
|
1138
|
+
if (anchorIdx < 0) return { left: [], right: [] };
|
|
1139
|
+
const total = sortedPlayers.length;
|
|
1140
|
+
const left = [];
|
|
1141
|
+
const right = [];
|
|
1142
|
+
if (scope === "LEFT_SIDE" || scope === "BOTH_SIDES") {
|
|
1143
|
+
for (let step = 1; step < total; step += 1) {
|
|
1144
|
+
const player = sortedPlayers[(anchorIdx + step) % total];
|
|
1145
|
+
if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) left.push(player);
|
|
652
1146
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
1147
|
+
}
|
|
1148
|
+
if (scope === "RIGHT_SIDE" || scope === "BOTH_SIDES") {
|
|
1149
|
+
for (let step = 1; step < total; step += 1) {
|
|
1150
|
+
const player = sortedPlayers[(anchorIdx - step + total) % total];
|
|
1151
|
+
if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) right.push(player);
|
|
657
1152
|
}
|
|
658
|
-
|
|
659
|
-
}
|
|
1153
|
+
}
|
|
1154
|
+
return { left, right };
|
|
660
1155
|
};
|
|
661
|
-
var
|
|
662
|
-
const dedupedCandidates = [...new Map(sortedCandidates.map((player) => [player.seat, player])).values()];
|
|
1156
|
+
var pickBalanced = (sides, dynamicTarget, payloads) => {
|
|
663
1157
|
const configuredLimit = resolveDynamicValue(dynamicTarget.limit, payloads, (value) => typeof value === "number");
|
|
1158
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1159
|
+
const out = [];
|
|
1160
|
+
const pushUnique = (player) => {
|
|
1161
|
+
if (!player || seen.has(player.seat)) return false;
|
|
1162
|
+
seen.add(player.seat);
|
|
1163
|
+
out.push(player);
|
|
1164
|
+
return true;
|
|
1165
|
+
};
|
|
664
1166
|
if (typeof configuredLimit === "undefined") {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
1167
|
+
sides.left.forEach(pushUnique);
|
|
1168
|
+
sides.right.forEach(pushUnique);
|
|
1169
|
+
return out;
|
|
1170
|
+
}
|
|
1171
|
+
if (configuredLimit <= 0) return [];
|
|
1172
|
+
let li = 0;
|
|
1173
|
+
let ri = 0;
|
|
1174
|
+
let preferLeft = true;
|
|
1175
|
+
while (out.length < configuredLimit && (li < sides.left.length || ri < sides.right.length)) {
|
|
1176
|
+
if (preferLeft && li < sides.left.length) {
|
|
1177
|
+
if (pushUnique(sides.left[li])) li += 1;
|
|
1178
|
+
else li += 1;
|
|
1179
|
+
} else if (!preferLeft && ri < sides.right.length) {
|
|
1180
|
+
if (pushUnique(sides.right[ri])) ri += 1;
|
|
1181
|
+
else ri += 1;
|
|
1182
|
+
} else if (li < sides.left.length) {
|
|
1183
|
+
if (pushUnique(sides.left[li])) li += 1;
|
|
1184
|
+
else li += 1;
|
|
1185
|
+
} else if (ri < sides.right.length) {
|
|
1186
|
+
if (pushUnique(sides.right[ri])) ri += 1;
|
|
1187
|
+
else ri += 1;
|
|
1188
|
+
}
|
|
1189
|
+
preferLeft = !preferLeft;
|
|
669
1190
|
}
|
|
670
|
-
return
|
|
1191
|
+
return out;
|
|
671
1192
|
};
|
|
672
1193
|
var resolveEffectTargets = ({
|
|
673
1194
|
effect,
|
|
@@ -700,10 +1221,14 @@ var resolveEffectTargets = ({
|
|
|
700
1221
|
const dynamicTarget = effect.target;
|
|
701
1222
|
const sortedPlayers = getSortedPlayers(playerSeatMap);
|
|
702
1223
|
const anchorSeat = getAnchorSeat(dynamicTarget, payloads, effector);
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
1224
|
+
if (!dynamicTarget.selector) {
|
|
1225
|
+
const matches = sortedPlayers.filter(
|
|
1226
|
+
(player) => passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)
|
|
1227
|
+
);
|
|
1228
|
+
return pickBalanced({ left: matches, right: [] }, dynamicTarget, payloads);
|
|
1229
|
+
}
|
|
1230
|
+
const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector);
|
|
1231
|
+
return pickBalanced(sides, dynamicTarget, payloads);
|
|
707
1232
|
}
|
|
708
1233
|
return [];
|
|
709
1234
|
};
|
|
@@ -848,7 +1373,7 @@ var evalPlayerState = (atom, ctx) => {
|
|
|
848
1373
|
const single = players[0];
|
|
849
1374
|
return evalPlayerStateOnPlayer(single, atom, ctx);
|
|
850
1375
|
};
|
|
851
|
-
var
|
|
1376
|
+
var isExecutionTimeline2 = (tl) => {
|
|
852
1377
|
if (!tl) return false;
|
|
853
1378
|
return (tl.operations ?? []).some((op) => op.kind === "execution");
|
|
854
1379
|
};
|
|
@@ -876,7 +1401,7 @@ var evalTime = (atom, ctx) => {
|
|
|
876
1401
|
if (spec.event === "NEXT_NIGHT" && tl.time === "night") return false;
|
|
877
1402
|
if (spec.event === "NEXT_DUSK" && prevTime === "day" && tl.time === "night") return false;
|
|
878
1403
|
if (spec.event === "NEXT_DAWN" && prevTime === "night" && tl.time === "day") return false;
|
|
879
|
-
if (spec.event === "NEXT_EXECUTION" &&
|
|
1404
|
+
if (spec.event === "NEXT_EXECUTION" && isExecutionTimeline2(tl)) return false;
|
|
880
1405
|
prevTime = tl.time;
|
|
881
1406
|
}
|
|
882
1407
|
return true;
|
|
@@ -917,7 +1442,7 @@ var evalGameState = (atom, ctx) => {
|
|
|
917
1442
|
if (spec.kind === "EXECUTION_HAPPENED_TODAY") {
|
|
918
1443
|
const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
|
|
919
1444
|
if (typeof turn !== "number") return false;
|
|
920
|
-
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));
|
|
921
1446
|
}
|
|
922
1447
|
if (spec.kind === "PLAYER_DIED_TODAY") {
|
|
923
1448
|
const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
|
|
@@ -932,7 +1457,7 @@ var evalGameState = (atom, ctx) => {
|
|
|
932
1457
|
}
|
|
933
1458
|
return true;
|
|
934
1459
|
};
|
|
935
|
-
var
|
|
1460
|
+
var evalAtom2 = (atom, ctx) => {
|
|
936
1461
|
switch (atom.type) {
|
|
937
1462
|
case "TIME":
|
|
938
1463
|
return evalTime(atom, ctx);
|
|
@@ -951,18 +1476,30 @@ var evalAtom = (atom, ctx) => {
|
|
|
951
1476
|
target: ctx.target,
|
|
952
1477
|
snapshotSeatMap: ctx.snapshotSeatMap
|
|
953
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
|
+
}
|
|
954
1491
|
}
|
|
955
1492
|
};
|
|
956
|
-
var
|
|
1493
|
+
var evalExpr2 = (expr, ctx) => {
|
|
957
1494
|
switch (expr.op) {
|
|
958
1495
|
case "AND":
|
|
959
|
-
return expr.children.every((child) =>
|
|
1496
|
+
return expr.children.every((child) => evalExpr2(child, ctx));
|
|
960
1497
|
case "OR":
|
|
961
|
-
return expr.children.some((child) =>
|
|
1498
|
+
return expr.children.some((child) => evalExpr2(child, ctx));
|
|
962
1499
|
case "NOT":
|
|
963
|
-
return !
|
|
1500
|
+
return !evalExpr2(expr.child, ctx);
|
|
964
1501
|
case "ATOM":
|
|
965
|
-
return
|
|
1502
|
+
return evalAtom2(expr.atom, ctx);
|
|
966
1503
|
}
|
|
967
1504
|
};
|
|
968
1505
|
var expressionReferencesTarget = (expr) => {
|
|
@@ -1000,9 +1537,9 @@ var evaluateLifetime = ({
|
|
|
1000
1537
|
const expr = lifetime.expr;
|
|
1001
1538
|
const dependsOnTarget = expressionReferencesTarget(expr);
|
|
1002
1539
|
if (!dependsOnTarget) {
|
|
1003
|
-
return
|
|
1540
|
+
return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
|
|
1004
1541
|
}
|
|
1005
|
-
return targets.filter((target) =>
|
|
1542
|
+
return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
|
|
1006
1543
|
};
|
|
1007
1544
|
var evaluatePrecondition = ({
|
|
1008
1545
|
expr,
|
|
@@ -1028,9 +1565,9 @@ var evaluatePrecondition = ({
|
|
|
1028
1565
|
};
|
|
1029
1566
|
const dependsOnTarget = expressionReferencesTarget(expr);
|
|
1030
1567
|
if (!dependsOnTarget) {
|
|
1031
|
-
return
|
|
1568
|
+
return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
|
|
1032
1569
|
}
|
|
1033
|
-
return targets.filter((target) =>
|
|
1570
|
+
return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
|
|
1034
1571
|
};
|
|
1035
1572
|
|
|
1036
1573
|
// src/apply-operation.ts
|
|
@@ -1352,6 +1889,12 @@ var deriveContext = ({
|
|
|
1352
1889
|
const yesterdayDayOperations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "day").flatMap((tl) => tl.operations ?? []);
|
|
1353
1890
|
const yesterdayNightOperations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "night").flatMap((tl) => tl.operations ?? []);
|
|
1354
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
|
+
})));
|
|
1355
1898
|
return {
|
|
1356
1899
|
currentTurn: last.turn,
|
|
1357
1900
|
currentPhase: TIME_TO_PHASE[last.time],
|
|
@@ -1363,6 +1906,8 @@ var deriveContext = ({
|
|
|
1363
1906
|
yesterdayDayOperations,
|
|
1364
1907
|
yesterdayNightOperations,
|
|
1365
1908
|
todayNominations,
|
|
1909
|
+
yesterdayNominations,
|
|
1910
|
+
allNominations,
|
|
1366
1911
|
effectorSeat: candidate.effector,
|
|
1367
1912
|
effector,
|
|
1368
1913
|
abilityId: candidate.abilityId,
|
|
@@ -1371,6 +1916,14 @@ var deriveContext = ({
|
|
|
1371
1916
|
operationTimeMap
|
|
1372
1917
|
};
|
|
1373
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
|
+
);
|
|
1374
1927
|
var operationsForActionScope = (scope, ctx) => {
|
|
1375
1928
|
if (scope === "TONIGHT") return ctx.tonightOperations;
|
|
1376
1929
|
if (scope === "TODAY") return ctx.todayOperations;
|
|
@@ -1426,23 +1979,37 @@ var evalAtomAction = (atom, ctx) => {
|
|
|
1426
1979
|
if (subjectSeats.size === 0) return `no players match subject ${atom.subject.ref}`;
|
|
1427
1980
|
const actorSeats = atom.actor ? seatsMatchingTargetRef(atom.actor, ctx) : null;
|
|
1428
1981
|
if (atom.action === "NOMINATED") {
|
|
1429
|
-
const noms = ctx
|
|
1982
|
+
const noms = nominationsForActionScope(atom.scope, ctx);
|
|
1430
1983
|
const found = noms.some(
|
|
1431
1984
|
(n) => subjectSeats.has(n.nominee) && (actorSeats === null || actorSeats.has(n.nominator))
|
|
1432
1985
|
);
|
|
1433
|
-
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";
|
|
1434
1994
|
}
|
|
1435
1995
|
const scoped = operationsForActionScope(atom.scope, ctx);
|
|
1436
1996
|
if (atom.action === "CHOSEN_AS_TARGET") {
|
|
1437
1997
|
const found = scoped.some((op) => {
|
|
1438
1998
|
if (actorSeats !== null && !actorSeats.has(op.effector)) return false;
|
|
1439
|
-
const payloadSeats = (op
|
|
1440
|
-
(v) => typeof v === "number" ? [v] : Array.isArray(v) ? v.filter((x) => typeof x === "number") : []
|
|
1441
|
-
);
|
|
1999
|
+
const payloadSeats = extractPayloadSeats(op);
|
|
1442
2000
|
return payloadSeats.some((s) => subjectSeats.has(s));
|
|
1443
2001
|
});
|
|
1444
2002
|
return found ? null : "no matching CHOSEN_AS_TARGET in scope";
|
|
1445
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
|
+
}
|
|
1446
2013
|
const turnsInScope = (() => {
|
|
1447
2014
|
if (atom.scope === "TONIGHT" || atom.scope === "TODAY") return [ctx.currentTurn];
|
|
1448
2015
|
if (atom.scope === "YESTERDAY") return [ctx.currentTurn - 1];
|
|
@@ -1472,6 +2039,18 @@ var evalAtomAction = (atom, ctx) => {
|
|
|
1472
2039
|
}
|
|
1473
2040
|
return "no matching EXECUTED in scope";
|
|
1474
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
|
+
}
|
|
1475
2054
|
void scoped;
|
|
1476
2055
|
return `unknown action ${atom.action}`;
|
|
1477
2056
|
};
|
|
@@ -1508,7 +2087,7 @@ var evalAtomGlobalCount = (atom, ctx) => {
|
|
|
1508
2087
|
}
|
|
1509
2088
|
return compareCount2(actual, atom.operator, atom.value) ? null : `global count ${atom.subject}=${actual} fails ${atom.operator} ${atom.value}`;
|
|
1510
2089
|
};
|
|
1511
|
-
var
|
|
2090
|
+
var evalAtom3 = (atom, ctx, customResolver) => {
|
|
1512
2091
|
if (atom.type === "TIME") return evalAtomTime(atom, ctx);
|
|
1513
2092
|
if (atom.type === "STATE") return evalAtomState(atom, ctx);
|
|
1514
2093
|
if (atom.type === "ACTION") return evalAtomAction(atom, ctx);
|
|
@@ -1518,12 +2097,16 @@ var evalAtom2 = (atom, ctx, customResolver) => {
|
|
|
1518
2097
|
if (!customResolver) return `no custom resolver registered for ${atom.resolverId}`;
|
|
1519
2098
|
return customResolver({ resolverId: atom.resolverId, args: atom.args }, ctx) ? null : `custom resolver ${atom.resolverId} returned false`;
|
|
1520
2099
|
}
|
|
2100
|
+
if (atom.type === "DSL") {
|
|
2101
|
+
const dslCtx = buildDslContextFromDerived(ctx);
|
|
2102
|
+
return evalDslExpr(atom.expr, dslCtx) ? null : "DSL expression returned false";
|
|
2103
|
+
}
|
|
1521
2104
|
return "unknown atom";
|
|
1522
2105
|
};
|
|
1523
|
-
var
|
|
2106
|
+
var evalExpr3 = (expr, ctx, customResolver) => {
|
|
1524
2107
|
if (expr.op === "AND") {
|
|
1525
2108
|
for (const child of expr.children) {
|
|
1526
|
-
const reason =
|
|
2109
|
+
const reason = evalExpr3(child, ctx, customResolver);
|
|
1527
2110
|
if (reason) return reason;
|
|
1528
2111
|
}
|
|
1529
2112
|
return null;
|
|
@@ -1531,17 +2114,17 @@ var evalExpr2 = (expr, ctx, customResolver) => {
|
|
|
1531
2114
|
if (expr.op === "OR") {
|
|
1532
2115
|
const reasons = [];
|
|
1533
2116
|
for (const child of expr.children) {
|
|
1534
|
-
const reason =
|
|
2117
|
+
const reason = evalExpr3(child, ctx, customResolver);
|
|
1535
2118
|
if (!reason) return null;
|
|
1536
2119
|
reasons.push(reason);
|
|
1537
2120
|
}
|
|
1538
2121
|
return `OR: all branches failed (${reasons.join("; ")})`;
|
|
1539
2122
|
}
|
|
1540
2123
|
if (expr.op === "NOT") {
|
|
1541
|
-
const reason =
|
|
2124
|
+
const reason = evalExpr3(expr.child, ctx, customResolver);
|
|
1542
2125
|
return reason ? null : "NOT: inner expression was true";
|
|
1543
2126
|
}
|
|
1544
|
-
return
|
|
2127
|
+
return evalAtom3(expr.atom, ctx, customResolver);
|
|
1545
2128
|
};
|
|
1546
2129
|
var canInvokeAbility = ({
|
|
1547
2130
|
ability,
|
|
@@ -1561,23 +2144,47 @@ var canInvokeAbility = ({
|
|
|
1561
2144
|
if (derived.effector?.retainsAbility === "ALL") {
|
|
1562
2145
|
return { allowed: true };
|
|
1563
2146
|
}
|
|
1564
|
-
const reason =
|
|
2147
|
+
const reason = evalExpr3(window, derived, customResolver);
|
|
1565
2148
|
return reason ? { allowed: false, reason } : { allowed: true };
|
|
1566
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);
|
|
1567
2158
|
export {
|
|
2159
|
+
ACTIVE_TRIGGER_ACTIONS,
|
|
2160
|
+
DSL_CATALOG,
|
|
2161
|
+
DSL_ITEM_FIELDS,
|
|
2162
|
+
DSL_OPERATORS,
|
|
1568
2163
|
adjustValueAsNumber,
|
|
1569
2164
|
adjustValueAsNumberArray,
|
|
2165
|
+
applyCountOperator,
|
|
1570
2166
|
applyOperationToPlayers,
|
|
2167
|
+
applyOperator,
|
|
2168
|
+
buildDslContextFromDerived,
|
|
2169
|
+
buildDslContextFromLifetime,
|
|
1571
2170
|
buildPlayerSeatMap,
|
|
1572
2171
|
canInvokeAbility,
|
|
1573
2172
|
copyPlayers,
|
|
1574
2173
|
deriveContext,
|
|
2174
|
+
evalDslExpr,
|
|
2175
|
+
evalExpr as evalDslExprWithBindings,
|
|
2176
|
+
evaluatePath,
|
|
2177
|
+
extendBindings,
|
|
1575
2178
|
getSourceValue,
|
|
1576
2179
|
getValueFromPayloads,
|
|
2180
|
+
isActiveTriggerAction,
|
|
1577
2181
|
isNumberOrNumberArray,
|
|
1578
2182
|
isPlayerReminderArray,
|
|
1579
2183
|
isReminder,
|
|
1580
2184
|
resolveSourceValue,
|
|
1581
2185
|
resolveTargetRef,
|
|
2186
|
+
rootBindings,
|
|
2187
|
+
traceDslExpr,
|
|
2188
|
+
traceExpr,
|
|
1582
2189
|
transformEmptyArray
|
|
1583
2190
|
};
|