@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.js
CHANGED
|
@@ -20,20 +20,36 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
ACTIVE_TRIGGER_ACTIONS: () => ACTIVE_TRIGGER_ACTIONS,
|
|
24
|
+
DSL_CATALOG: () => DSL_CATALOG,
|
|
25
|
+
DSL_ITEM_FIELDS: () => DSL_ITEM_FIELDS,
|
|
26
|
+
DSL_OPERATORS: () => DSL_OPERATORS,
|
|
23
27
|
adjustValueAsNumber: () => adjustValueAsNumber,
|
|
24
28
|
adjustValueAsNumberArray: () => adjustValueAsNumberArray,
|
|
29
|
+
applyCountOperator: () => applyCountOperator,
|
|
25
30
|
applyOperationToPlayers: () => applyOperationToPlayers,
|
|
31
|
+
applyOperator: () => applyOperator,
|
|
32
|
+
buildDslContextFromDerived: () => buildDslContextFromDerived,
|
|
33
|
+
buildDslContextFromLifetime: () => buildDslContextFromLifetime,
|
|
26
34
|
buildPlayerSeatMap: () => buildPlayerSeatMap,
|
|
27
35
|
canInvokeAbility: () => canInvokeAbility,
|
|
28
36
|
copyPlayers: () => copyPlayers,
|
|
29
37
|
deriveContext: () => deriveContext,
|
|
38
|
+
evalDslExpr: () => evalDslExpr,
|
|
39
|
+
evalDslExprWithBindings: () => evalExpr,
|
|
40
|
+
evaluatePath: () => evaluatePath,
|
|
41
|
+
extendBindings: () => extendBindings,
|
|
30
42
|
getSourceValue: () => getSourceValue,
|
|
31
43
|
getValueFromPayloads: () => getValueFromPayloads,
|
|
44
|
+
isActiveTriggerAction: () => isActiveTriggerAction,
|
|
32
45
|
isNumberOrNumberArray: () => isNumberOrNumberArray,
|
|
33
46
|
isPlayerReminderArray: () => isPlayerReminderArray,
|
|
34
47
|
isReminder: () => isReminder,
|
|
35
48
|
resolveSourceValue: () => resolveSourceValue,
|
|
36
49
|
resolveTargetRef: () => resolveTargetRef,
|
|
50
|
+
rootBindings: () => rootBindings,
|
|
51
|
+
traceDslExpr: () => traceDslExpr,
|
|
52
|
+
traceExpr: () => traceExpr,
|
|
37
53
|
transformEmptyArray: () => transformEmptyArray
|
|
38
54
|
});
|
|
39
55
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -545,6 +561,521 @@ var effectHandlers = {
|
|
|
545
561
|
STATUS_CHANGE: applyStatusChange
|
|
546
562
|
};
|
|
547
563
|
|
|
564
|
+
// src/dsl/context.ts
|
|
565
|
+
var decoratePlayer = (player, characterMap, currentTurn) => {
|
|
566
|
+
const isDead = Boolean(player.isDead);
|
|
567
|
+
const retainsBypass = isDead && (player.retainsAbility === "ALIVENESS" || player.retainsAbility === "ALL");
|
|
568
|
+
const wasExecuted = isDead && player.deathCause === "EXECUTION";
|
|
569
|
+
return {
|
|
570
|
+
...player,
|
|
571
|
+
characterKind: characterMap.get(player.characterId)?.kind,
|
|
572
|
+
reminderMarks: (player.reminders ?? []).map((r) => r.mark),
|
|
573
|
+
effectiveAlive: !isDead || retainsBypass,
|
|
574
|
+
diedThisTurn: isDead && typeof player.deathTurn === "number" && player.deathTurn === currentTurn,
|
|
575
|
+
wasExecuted,
|
|
576
|
+
wasExecutedToday: wasExecuted && player.deathTurn === currentTurn
|
|
577
|
+
};
|
|
578
|
+
};
|
|
579
|
+
var isExecutionTimeline = (tl) => {
|
|
580
|
+
if (!tl) return false;
|
|
581
|
+
return (tl.operations ?? []).some((op) => op.kind === "execution");
|
|
582
|
+
};
|
|
583
|
+
var buildDslContextFromDerived = (derived, payload = []) => {
|
|
584
|
+
const players = derived.players.map((p) => decoratePlayer(p, derived.characterMap, derived.currentTurn));
|
|
585
|
+
const effector = players.find((p) => p.seat === derived.effectorSeat);
|
|
586
|
+
const aliveCount = players.filter((p) => !p.isDead).length;
|
|
587
|
+
const deadCount = players.length - aliveCount;
|
|
588
|
+
const executedTodayCount = players.filter(
|
|
589
|
+
(p) => p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === derived.currentTurn
|
|
590
|
+
).length;
|
|
591
|
+
const nominatedTodayCount = derived.todayNominations.length;
|
|
592
|
+
const playerDiedToday = players.some(
|
|
593
|
+
(p) => p.isDead && typeof p.deathTurn === "number" && p.deathTurn === derived.currentTurn
|
|
594
|
+
);
|
|
595
|
+
return {
|
|
596
|
+
effector,
|
|
597
|
+
players,
|
|
598
|
+
operations: {
|
|
599
|
+
all: derived.allOperations,
|
|
600
|
+
today: derived.todayOperations,
|
|
601
|
+
tonight: derived.tonightOperations,
|
|
602
|
+
yesterdayDay: derived.yesterdayDayOperations,
|
|
603
|
+
yesterdayNight: derived.yesterdayNightOperations
|
|
604
|
+
},
|
|
605
|
+
nominations: {
|
|
606
|
+
all: derived.allNominations,
|
|
607
|
+
today: derived.todayNominations,
|
|
608
|
+
yesterday: derived.yesterdayNominations
|
|
609
|
+
},
|
|
610
|
+
currentTimeline: {
|
|
611
|
+
turn: derived.currentTurn,
|
|
612
|
+
phase: derived.currentPhase,
|
|
613
|
+
time: derived.currentPhase === "NIGHT" ? "night" : derived.currentPhase === "DAY" ? "day" : void 0,
|
|
614
|
+
isFirstOfPhase: derived.isFirstOfPhase,
|
|
615
|
+
isExecution: (derived.currentPhase === "DAY" ? derived.todayOperations : derived.tonightOperations).some((op) => op.kind === "execution")
|
|
616
|
+
},
|
|
617
|
+
global: {
|
|
618
|
+
aliveCount,
|
|
619
|
+
deadCount,
|
|
620
|
+
executedTodayCount,
|
|
621
|
+
nominatedTodayCount,
|
|
622
|
+
executionHappenedToday: derived.todayOperations.some((op) => op.kind === "execution"),
|
|
623
|
+
playerDiedToday
|
|
624
|
+
},
|
|
625
|
+
payload
|
|
626
|
+
};
|
|
627
|
+
};
|
|
628
|
+
var buildDslContextFromLifetime = ({
|
|
629
|
+
snapshotSeatMap,
|
|
630
|
+
timelines,
|
|
631
|
+
nowTimelineIndex,
|
|
632
|
+
characterMap,
|
|
633
|
+
effector,
|
|
634
|
+
payloads
|
|
635
|
+
}) => {
|
|
636
|
+
const nowTl = timelines[nowTimelineIndex];
|
|
637
|
+
const currentTurn = nowTl?.turn ?? -1;
|
|
638
|
+
const currentTime = nowTl?.time;
|
|
639
|
+
const currentPhase = currentTime === "night" ? "NIGHT" : currentTime === "day" ? "DAY" : "NIGHT";
|
|
640
|
+
const playersRaw = snapshotSeatMap ? [...snapshotSeatMap.values()] : [];
|
|
641
|
+
const players = playersRaw.map((p) => decoratePlayer(p, characterMap, currentTurn));
|
|
642
|
+
const effectorDecorated = effector ? players.find((p) => p.seat === effector.seat) ?? decoratePlayer(effector, characterMap, currentTurn) : void 0;
|
|
643
|
+
const todayOps = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "day").flatMap((tl) => tl.operations ?? []);
|
|
644
|
+
const tonightOps = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "night").flatMap((tl) => tl.operations ?? []);
|
|
645
|
+
const yesterdayDayOps = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "day").flatMap((tl) => tl.operations ?? []);
|
|
646
|
+
const yesterdayNightOps = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "night").flatMap((tl) => tl.operations ?? []);
|
|
647
|
+
const allOps = timelines.flatMap((tl) => tl.operations ?? []);
|
|
648
|
+
const todayNoms = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee })));
|
|
649
|
+
const yesterdayNoms = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee })));
|
|
650
|
+
const allNoms = timelines.filter((tl) => tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({
|
|
651
|
+
nominator: n.nominator,
|
|
652
|
+
nominee: n.nominee,
|
|
653
|
+
turn: tl.turn
|
|
654
|
+
})));
|
|
655
|
+
const aliveCount = players.filter((p) => !p.isDead).length;
|
|
656
|
+
const deadCount = players.length - aliveCount;
|
|
657
|
+
const executedTodayCount = players.filter(
|
|
658
|
+
(p) => p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === currentTurn
|
|
659
|
+
).length;
|
|
660
|
+
const playerDiedToday = timelines.some((tl) => {
|
|
661
|
+
if (tl.turn !== currentTurn || tl.time !== "day") return false;
|
|
662
|
+
return (tl.operations ?? []).some((op) => {
|
|
663
|
+
const kind = op.kind;
|
|
664
|
+
return kind === "execution" || kind === "death";
|
|
665
|
+
});
|
|
666
|
+
});
|
|
667
|
+
return {
|
|
668
|
+
effector: effectorDecorated,
|
|
669
|
+
players,
|
|
670
|
+
operations: {
|
|
671
|
+
all: allOps,
|
|
672
|
+
today: todayOps,
|
|
673
|
+
tonight: tonightOps,
|
|
674
|
+
yesterdayDay: yesterdayDayOps,
|
|
675
|
+
yesterdayNight: yesterdayNightOps
|
|
676
|
+
},
|
|
677
|
+
nominations: {
|
|
678
|
+
all: allNoms,
|
|
679
|
+
today: todayNoms,
|
|
680
|
+
yesterday: yesterdayNoms
|
|
681
|
+
},
|
|
682
|
+
currentTimeline: {
|
|
683
|
+
turn: currentTurn,
|
|
684
|
+
phase: currentPhase,
|
|
685
|
+
time: currentTime,
|
|
686
|
+
isFirstOfPhase: false,
|
|
687
|
+
isExecution: isExecutionTimeline(nowTl)
|
|
688
|
+
},
|
|
689
|
+
global: {
|
|
690
|
+
aliveCount,
|
|
691
|
+
deadCount,
|
|
692
|
+
executedTodayCount,
|
|
693
|
+
nominatedTodayCount: todayNoms.length,
|
|
694
|
+
executionHappenedToday: timelines.some(
|
|
695
|
+
(tl) => tl.turn === currentTurn && tl.time === "day" && isExecutionTimeline(tl)
|
|
696
|
+
),
|
|
697
|
+
playerDiedToday
|
|
698
|
+
},
|
|
699
|
+
payload: payloads
|
|
700
|
+
};
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
// src/dsl/bindings.ts
|
|
704
|
+
var rootBindings = (ctx) => ({
|
|
705
|
+
effector: ctx.effector,
|
|
706
|
+
players: ctx.players,
|
|
707
|
+
operations: ctx.operations,
|
|
708
|
+
nominations: ctx.nominations,
|
|
709
|
+
currentTimeline: ctx.currentTimeline,
|
|
710
|
+
global: ctx.global,
|
|
711
|
+
payload: ctx.payload
|
|
712
|
+
});
|
|
713
|
+
var extendBindings = (parent, name, value) => ({
|
|
714
|
+
...parent,
|
|
715
|
+
[name]: value
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
// src/dsl/operators.ts
|
|
719
|
+
var deepEqual = (a, b) => {
|
|
720
|
+
if (a === b) return true;
|
|
721
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
722
|
+
if (a.length !== b.length) return false;
|
|
723
|
+
for (let i = 0; i < a.length; i++) {
|
|
724
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
725
|
+
}
|
|
726
|
+
return true;
|
|
727
|
+
}
|
|
728
|
+
return false;
|
|
729
|
+
};
|
|
730
|
+
var asNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
731
|
+
var compareOrder = (lhs, rhs, cmp) => {
|
|
732
|
+
const l = asNumber(lhs);
|
|
733
|
+
const r = asNumber(rhs);
|
|
734
|
+
if (l === void 0 || r === void 0) return false;
|
|
735
|
+
return cmp(l, r);
|
|
736
|
+
};
|
|
737
|
+
var applyOperator = (op, lhs, rhs) => {
|
|
738
|
+
switch (op) {
|
|
739
|
+
case "EQ":
|
|
740
|
+
return deepEqual(lhs, rhs);
|
|
741
|
+
case "NE":
|
|
742
|
+
return !deepEqual(lhs, rhs);
|
|
743
|
+
case "LT":
|
|
744
|
+
return compareOrder(lhs, rhs, (l, r) => l < r);
|
|
745
|
+
case "LTE":
|
|
746
|
+
return compareOrder(lhs, rhs, (l, r) => l <= r);
|
|
747
|
+
case "GT":
|
|
748
|
+
return compareOrder(lhs, rhs, (l, r) => l > r);
|
|
749
|
+
case "GTE":
|
|
750
|
+
return compareOrder(lhs, rhs, (l, r) => l >= r);
|
|
751
|
+
case "IN":
|
|
752
|
+
return Array.isArray(rhs) && rhs.some((v) => deepEqual(v, lhs));
|
|
753
|
+
case "NOT_IN":
|
|
754
|
+
return Array.isArray(rhs) && !rhs.some((v) => deepEqual(v, lhs));
|
|
755
|
+
case "CONTAINS":
|
|
756
|
+
if (Array.isArray(lhs)) return lhs.some((v) => deepEqual(v, rhs));
|
|
757
|
+
if (typeof lhs === "string") return typeof rhs === "string" && lhs.includes(rhs);
|
|
758
|
+
return false;
|
|
759
|
+
case "MATCHES":
|
|
760
|
+
if (typeof lhs !== "string" || typeof rhs !== "string") return false;
|
|
761
|
+
try {
|
|
762
|
+
return new RegExp(rhs).test(lhs);
|
|
763
|
+
} catch {
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
case "EXISTS":
|
|
767
|
+
return lhs !== void 0 && lhs !== null;
|
|
768
|
+
case "TRUTHY":
|
|
769
|
+
return Boolean(lhs);
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
var applyCountOperator = (op, actual, expected) => {
|
|
773
|
+
switch (op) {
|
|
774
|
+
case "EQ":
|
|
775
|
+
return actual === expected;
|
|
776
|
+
case "NE":
|
|
777
|
+
return actual !== expected;
|
|
778
|
+
case "LT":
|
|
779
|
+
return actual < expected;
|
|
780
|
+
case "LTE":
|
|
781
|
+
return actual <= expected;
|
|
782
|
+
case "GT":
|
|
783
|
+
return actual > expected;
|
|
784
|
+
case "GTE":
|
|
785
|
+
return actual >= expected;
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
// src/dsl/path.ts
|
|
790
|
+
var MAX_STEPS = 32;
|
|
791
|
+
var stepInto = (current, step) => {
|
|
792
|
+
if (current === void 0 || current === null) return void 0;
|
|
793
|
+
if (step.kind === "field") {
|
|
794
|
+
if (typeof current !== "object") return void 0;
|
|
795
|
+
const record = current;
|
|
796
|
+
return record[step.name];
|
|
797
|
+
}
|
|
798
|
+
if (step.kind === "index") {
|
|
799
|
+
if (!Array.isArray(current)) return void 0;
|
|
800
|
+
return current[step.value];
|
|
801
|
+
}
|
|
802
|
+
return void 0;
|
|
803
|
+
};
|
|
804
|
+
var evaluatePath = (path, bindings) => {
|
|
805
|
+
if (path.steps.length > MAX_STEPS) return void 0;
|
|
806
|
+
let current = Object.prototype.hasOwnProperty.call(bindings, path.root) ? bindings[path.root] : void 0;
|
|
807
|
+
for (const step of path.steps) {
|
|
808
|
+
current = stepInto(current, step);
|
|
809
|
+
if (current === void 0) return void 0;
|
|
810
|
+
}
|
|
811
|
+
return current;
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
// src/dsl/eval.ts
|
|
815
|
+
var MAX_EXPR_DEPTH = 16;
|
|
816
|
+
var MAX_QUANTIFIER_SIZE = 256;
|
|
817
|
+
var resolveValue = (value, bindings) => {
|
|
818
|
+
if (!value) return void 0;
|
|
819
|
+
if (value.kind === "LITERAL") return value.value;
|
|
820
|
+
return evaluatePath(value.path, bindings);
|
|
821
|
+
};
|
|
822
|
+
var evalAtom = (atom, bindings, depth) => {
|
|
823
|
+
if (atom.type === "PATH") {
|
|
824
|
+
const lhs = evaluatePath(atom.path, bindings);
|
|
825
|
+
const rhs = resolveValue(atom.value, bindings);
|
|
826
|
+
return applyOperator(atom.op, lhs, rhs);
|
|
827
|
+
}
|
|
828
|
+
const source = evaluatePath(atom.source, bindings);
|
|
829
|
+
if (!Array.isArray(source)) {
|
|
830
|
+
return atom.type === "FORALL";
|
|
831
|
+
}
|
|
832
|
+
if (source.length > MAX_QUANTIFIER_SIZE) return false;
|
|
833
|
+
if (atom.type === "EXISTS") {
|
|
834
|
+
return source.some(
|
|
835
|
+
(item) => evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
if (atom.type === "FORALL") {
|
|
839
|
+
return source.every(
|
|
840
|
+
(item) => evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
let count = 0;
|
|
844
|
+
for (const item of source) {
|
|
845
|
+
if (!atom.where || evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)) {
|
|
846
|
+
count += 1;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return applyCountOperator(atom.op, count, atom.value);
|
|
850
|
+
};
|
|
851
|
+
var evalExpr = (expr, bindings, depth = 0) => {
|
|
852
|
+
if (depth > MAX_EXPR_DEPTH) return false;
|
|
853
|
+
switch (expr.op) {
|
|
854
|
+
case "AND":
|
|
855
|
+
return expr.children.every((child) => evalExpr(child, bindings, depth + 1));
|
|
856
|
+
case "OR":
|
|
857
|
+
return expr.children.some((child) => evalExpr(child, bindings, depth + 1));
|
|
858
|
+
case "NOT":
|
|
859
|
+
return !evalExpr(expr.child, bindings, depth + 1);
|
|
860
|
+
case "ATOM":
|
|
861
|
+
return evalAtom(expr.atom, bindings, depth + 1);
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
var evalDslExpr = (expr, ctx) => evalExpr(expr, rootBindings(ctx), 0);
|
|
865
|
+
|
|
866
|
+
// src/dsl/catalog.ts
|
|
867
|
+
var PLAYER_FIELDS = [
|
|
868
|
+
{ name: "seat", type: "number", label: "\u5EA7\u4F4D\u53F7" },
|
|
869
|
+
{ name: "alignment", type: "string", label: "\u9635\u8425", description: "GOOD / EVIL" },
|
|
870
|
+
{ name: "isDead", type: "boolean", label: "\u662F\u5426\u6B7B\u4EA1" },
|
|
871
|
+
{ name: "effectiveAlive", type: "boolean", label: "\u6709\u6548\u5B58\u6D3B", description: "\u8003\u8651 retainsAbility\uFF08ALIVENESS/ALL\uFF09\u540E\u662F\u5426\u6309\u5B58\u6D3B\u5904\u7406" },
|
|
872
|
+
{ name: "diedThisTurn", type: "boolean", label: "\u672C\u56DE\u5408\u6B7B\u4EA1" },
|
|
873
|
+
{ name: "wasExecuted", type: "boolean", label: "\u66FE\u88AB\u5904\u51B3" },
|
|
874
|
+
{ name: "wasExecutedToday", type: "boolean", label: "\u4ECA\u65E5\u88AB\u5904\u51B3" },
|
|
875
|
+
{ name: "characterId", type: "string", label: "\u89D2\u8272 ID" },
|
|
876
|
+
{ name: "characterKind", type: "string", label: "\u89D2\u8272\u7C7B\u578B", description: "Townsfolk / Outsiders / Minions / Demons" },
|
|
877
|
+
{ name: "deathCause", type: "string", label: "\u6B7B\u4EA1\u539F\u56E0", description: "EXECUTION / DEMON / ABILITY / STORYTELLER / OTHER" },
|
|
878
|
+
{ name: "deathTurn", type: "number", label: "\u6B7B\u4EA1\u56DE\u5408" },
|
|
879
|
+
{ name: "abilities", type: "string[]", label: "\u80FD\u529B ID \u5217\u8868" },
|
|
880
|
+
{ name: "reminderMarks", type: "string[]", label: "\u63D0\u793A\u7269 mark \u5217\u8868" },
|
|
881
|
+
{ name: "hasUsedDeadVote", type: "boolean", label: "\u662F\u5426\u4F7F\u7528\u8FC7\u6B7B\u4EA1\u6295\u7968" },
|
|
882
|
+
{ name: "retainsAbility", type: "string", label: "\u80FD\u529B\u4FDD\u7559\u8303\u56F4" }
|
|
883
|
+
];
|
|
884
|
+
var OPERATION_FIELDS = [
|
|
885
|
+
{ name: "effector", type: "number", label: "\u65BD\u52A8\u8005\u5EA7\u4F4D" },
|
|
886
|
+
{ name: "abilityId", type: "string", label: "\u80FD\u529B ID" },
|
|
887
|
+
{ name: "kind", type: "string", label: "\u64CD\u4F5C\u79CD\u7C7B" },
|
|
888
|
+
{ name: "payloads", type: "unknown[]", label: "Payload \u5217\u8868" }
|
|
889
|
+
];
|
|
890
|
+
var NOMINATION_FIELDS = [
|
|
891
|
+
{ name: "nominator", type: "number", label: "\u63D0\u540D\u8005\u5EA7\u4F4D" },
|
|
892
|
+
{ name: "nominee", type: "number", label: "\u88AB\u63D0\u540D\u8005\u5EA7\u4F4D" },
|
|
893
|
+
{ name: "turn", type: "number", label: "\u56DE\u5408", description: "\u4EC5 all \u96C6\u5408\u53EF\u7528" }
|
|
894
|
+
];
|
|
895
|
+
var CURRENT_TIMELINE_FIELDS = [
|
|
896
|
+
{ name: "turn", type: "number", label: "\u5F53\u524D\u56DE\u5408" },
|
|
897
|
+
{ name: "phase", type: "string", label: "\u5F53\u524D\u9636\u6BB5", description: "NIGHT / DAY / DUSK / DAWN" },
|
|
898
|
+
{ name: "time", type: "string", label: "\u5F53\u524D\u65F6\u6BB5", description: "night / day" },
|
|
899
|
+
{ name: "isFirstOfPhase", type: "boolean", label: "\u662F\u5426\u672C\u9636\u6BB5\u9996\u6B21" },
|
|
900
|
+
{ name: "isExecution", type: "boolean", label: "\u5F53\u524D\u65F6\u95F4\u7EBF\u542B\u5904\u51B3" }
|
|
901
|
+
];
|
|
902
|
+
var GLOBAL_FIELDS = [
|
|
903
|
+
{ name: "aliveCount", type: "number", label: "\u5B58\u6D3B\u73A9\u5BB6\u6570" },
|
|
904
|
+
{ name: "deadCount", type: "number", label: "\u6B7B\u4EA1\u73A9\u5BB6\u6570" },
|
|
905
|
+
{ name: "executedTodayCount", type: "number", label: "\u4ECA\u65E5\u88AB\u5904\u51B3\u4EBA\u6570" },
|
|
906
|
+
{ name: "nominatedTodayCount", type: "number", label: "\u4ECA\u65E5\u63D0\u540D\u603B\u6570" },
|
|
907
|
+
{ name: "executionHappenedToday", type: "boolean", label: "\u4ECA\u65E5\u662F\u5426\u5DF2\u53D1\u751F\u5904\u51B3" },
|
|
908
|
+
{ name: "playerDiedToday", type: "boolean", label: "\u4ECA\u65E5\u662F\u5426\u6709\u73A9\u5BB6\u6B7B\u4EA1" }
|
|
909
|
+
];
|
|
910
|
+
var OPERATIONS_FIELDS = [
|
|
911
|
+
{ name: "all", type: "operation[]", label: "\u6240\u6709\u64CD\u4F5C" },
|
|
912
|
+
{ name: "today", type: "operation[]", label: "\u4ECA\u65E5\u64CD\u4F5C" },
|
|
913
|
+
{ name: "tonight", type: "operation[]", label: "\u4ECA\u591C\u64CD\u4F5C" },
|
|
914
|
+
{ name: "yesterdayDay", type: "operation[]", label: "\u6628\u65E5\u767D\u5929\u64CD\u4F5C" },
|
|
915
|
+
{ name: "yesterdayNight", type: "operation[]", label: "\u6628\u591C\u64CD\u4F5C" }
|
|
916
|
+
];
|
|
917
|
+
var NOMINATIONS_FIELDS = [
|
|
918
|
+
{ name: "all", type: "nomination[]", label: "\u6240\u6709\u63D0\u540D" },
|
|
919
|
+
{ name: "today", type: "nomination[]", label: "\u4ECA\u65E5\u63D0\u540D" },
|
|
920
|
+
{ name: "yesterday", type: "nomination[]", label: "\u6628\u65E5\u63D0\u540D" }
|
|
921
|
+
];
|
|
922
|
+
var DSL_CATALOG = [
|
|
923
|
+
{
|
|
924
|
+
name: "effector",
|
|
925
|
+
type: "player",
|
|
926
|
+
label: "\u80FD\u529B\u53D1\u52A8\u8005",
|
|
927
|
+
description: "\u5F53\u524D\u8BC4\u4F30\u65F6\u7684\u80FD\u529B\u65BD\u653E\u8005\u5FEB\u7167",
|
|
928
|
+
fields: PLAYER_FIELDS
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
name: "players",
|
|
932
|
+
type: "player[]",
|
|
933
|
+
label: "\u5168\u90E8\u73A9\u5BB6",
|
|
934
|
+
description: "\u5F53\u524D\u5FEB\u7167\u4E0B\u6240\u6709\u73A9\u5BB6\uFF08\u5E94\u914D\u5408 EXISTS / FORALL / COUNT \u4F7F\u7528\uFF09"
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
name: "operations",
|
|
938
|
+
type: "object",
|
|
939
|
+
label: "\u5386\u53F2\u64CD\u4F5C\u96C6\u5408",
|
|
940
|
+
fields: OPERATIONS_FIELDS
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
name: "nominations",
|
|
944
|
+
type: "object",
|
|
945
|
+
label: "\u5386\u53F2\u63D0\u540D\u96C6\u5408",
|
|
946
|
+
fields: NOMINATIONS_FIELDS
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
name: "currentTimeline",
|
|
950
|
+
type: "object",
|
|
951
|
+
label: "\u5F53\u524D\u65F6\u95F4\u7EBF",
|
|
952
|
+
fields: CURRENT_TIMELINE_FIELDS
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
name: "global",
|
|
956
|
+
type: "object",
|
|
957
|
+
label: "\u5168\u5C40\u805A\u5408",
|
|
958
|
+
fields: GLOBAL_FIELDS
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
name: "payload",
|
|
962
|
+
type: "unknown[]",
|
|
963
|
+
label: "\u80FD\u529B Payload \u6570\u7EC4",
|
|
964
|
+
description: "\u5F53\u524D\u8C03\u7528 / \u64CD\u4F5C\u7684 payload \u539F\u59CB\u6570\u7EC4"
|
|
965
|
+
}
|
|
966
|
+
];
|
|
967
|
+
var DSL_ITEM_FIELDS = {
|
|
968
|
+
player: PLAYER_FIELDS,
|
|
969
|
+
operation: OPERATION_FIELDS,
|
|
970
|
+
nomination: NOMINATION_FIELDS
|
|
971
|
+
};
|
|
972
|
+
var DSL_OPERATORS = [
|
|
973
|
+
{ value: "EQ", label: "\u7B49\u4E8E", arity: "binary" },
|
|
974
|
+
{ value: "NE", label: "\u4E0D\u7B49\u4E8E", arity: "binary" },
|
|
975
|
+
{ value: "LT", label: "<", arity: "binary" },
|
|
976
|
+
{ value: "LTE", label: "<=", arity: "binary" },
|
|
977
|
+
{ value: "GT", label: ">", arity: "binary" },
|
|
978
|
+
{ value: "GTE", label: ">=", arity: "binary" },
|
|
979
|
+
{ value: "IN", label: "\u5C5E\u4E8E", arity: "binary" },
|
|
980
|
+
{ value: "NOT_IN", label: "\u4E0D\u5C5E\u4E8E", arity: "binary" },
|
|
981
|
+
{ value: "CONTAINS", label: "\u5305\u542B", arity: "binary" },
|
|
982
|
+
{ value: "MATCHES", label: "\u6B63\u5219\u5339\u914D", arity: "binary" },
|
|
983
|
+
{ value: "EXISTS", label: "\u5B58\u5728", arity: "unary" },
|
|
984
|
+
{ value: "TRUTHY", label: "\u4E3A\u771F", arity: "unary" }
|
|
985
|
+
];
|
|
986
|
+
|
|
987
|
+
// src/dsl/trace.ts
|
|
988
|
+
var MAX_QUANTIFIER_SIZE2 = 256;
|
|
989
|
+
var renderPath = (p) => p.steps.reduce((acc, s) => acc + (s.kind === "field" ? `.${s.name}` : `[${s.value}]`), p.root);
|
|
990
|
+
var resolveValue2 = (value, bindings) => {
|
|
991
|
+
if (!value) return void 0;
|
|
992
|
+
if (value.kind === "LITERAL") return value.value;
|
|
993
|
+
return evaluatePath(value.path, bindings);
|
|
994
|
+
};
|
|
995
|
+
var renderValue = (value, bindings) => {
|
|
996
|
+
if (!value) return void 0;
|
|
997
|
+
if (value.kind === "LITERAL") return value.value;
|
|
998
|
+
return { ref: renderPath(value.path), resolved: evaluatePath(value.path, bindings) };
|
|
999
|
+
};
|
|
1000
|
+
var traceAtom = (atom, bindings) => {
|
|
1001
|
+
if (atom.type === "PATH") {
|
|
1002
|
+
const lhs = evaluatePath(atom.path, bindings);
|
|
1003
|
+
const rhs = resolveValue2(atom.value, bindings);
|
|
1004
|
+
const ok2 = applyOperator(atom.op, lhs, rhs);
|
|
1005
|
+
return {
|
|
1006
|
+
op: "ATOM",
|
|
1007
|
+
atomType: "PATH",
|
|
1008
|
+
ok: ok2,
|
|
1009
|
+
path: renderPath(atom.path),
|
|
1010
|
+
operator: atom.op,
|
|
1011
|
+
lhs,
|
|
1012
|
+
rhs: renderValue(atom.value, bindings)
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
const source = evaluatePath(atom.source, bindings);
|
|
1016
|
+
const sourceArr = Array.isArray(source) ? source : [];
|
|
1017
|
+
const sizeCapped = sourceArr.length > MAX_QUANTIFIER_SIZE2;
|
|
1018
|
+
let matched = 0;
|
|
1019
|
+
if (!sizeCapped) {
|
|
1020
|
+
for (const item of sourceArr) {
|
|
1021
|
+
const childBindings = extendBindings(bindings, atom.as, item);
|
|
1022
|
+
if (!atom.where || traceExprOk(atom.where, childBindings)) matched += 1;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
if (atom.type === "COUNT") {
|
|
1026
|
+
const ok2 = !sizeCapped && applyCountOperator(atom.op, matched, atom.value);
|
|
1027
|
+
return {
|
|
1028
|
+
op: "ATOM",
|
|
1029
|
+
atomType: "COUNT",
|
|
1030
|
+
ok: ok2,
|
|
1031
|
+
source: renderPath(atom.source),
|
|
1032
|
+
size: sourceArr.length,
|
|
1033
|
+
matched,
|
|
1034
|
+
operator: atom.op,
|
|
1035
|
+
expected: atom.value,
|
|
1036
|
+
bindings: atom.as
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
const ok = atom.type === "EXISTS" ? matched > 0 : !sizeCapped && (sourceArr.length === 0 || matched === sourceArr.length);
|
|
1040
|
+
return {
|
|
1041
|
+
op: "ATOM",
|
|
1042
|
+
atomType: atom.type,
|
|
1043
|
+
ok,
|
|
1044
|
+
source: renderPath(atom.source),
|
|
1045
|
+
size: sourceArr.length,
|
|
1046
|
+
matched,
|
|
1047
|
+
bindings: atom.as
|
|
1048
|
+
};
|
|
1049
|
+
};
|
|
1050
|
+
var traceExprOk = (expr, bindings) => {
|
|
1051
|
+
switch (expr.op) {
|
|
1052
|
+
case "AND":
|
|
1053
|
+
return expr.children.every((c) => traceExprOk(c, bindings));
|
|
1054
|
+
case "OR":
|
|
1055
|
+
return expr.children.some((c) => traceExprOk(c, bindings));
|
|
1056
|
+
case "NOT":
|
|
1057
|
+
return !traceExprOk(expr.child, bindings);
|
|
1058
|
+
case "ATOM":
|
|
1059
|
+
return traceAtom(expr.atom, bindings).ok;
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
var traceExpr = (expr, bindings) => {
|
|
1063
|
+
if (expr.op === "AND") {
|
|
1064
|
+
const children = expr.children.map((c) => traceExpr(c, bindings));
|
|
1065
|
+
return { op: "AND", ok: children.every((c) => c.ok), children };
|
|
1066
|
+
}
|
|
1067
|
+
if (expr.op === "OR") {
|
|
1068
|
+
const children = expr.children.map((c) => traceExpr(c, bindings));
|
|
1069
|
+
return { op: "OR", ok: children.some((c) => c.ok), children };
|
|
1070
|
+
}
|
|
1071
|
+
if (expr.op === "NOT") {
|
|
1072
|
+
const child = traceExpr(expr.child, bindings);
|
|
1073
|
+
return { op: "NOT", ok: !child.ok, child };
|
|
1074
|
+
}
|
|
1075
|
+
return traceAtom(expr.atom, bindings);
|
|
1076
|
+
};
|
|
1077
|
+
var traceDslExpr = (expr, ctx) => traceExpr(expr, rootBindings(ctx));
|
|
1078
|
+
|
|
548
1079
|
// src/effects/resolve-targets.ts
|
|
549
1080
|
var isPayloadRef = (value) => {
|
|
550
1081
|
if (!value || typeof value !== "object") {
|
|
@@ -590,7 +1121,23 @@ var resolveSelectorScope = (dynamicTarget, payloads) => {
|
|
|
590
1121
|
(value) => value === "BOTH_SIDES" || value === "LEFT_SIDE" || value === "RIGHT_SIDE"
|
|
591
1122
|
) ?? "BOTH_SIDES";
|
|
592
1123
|
};
|
|
593
|
-
var
|
|
1124
|
+
var matchesExprCondition = (player, condition, playerSeatMap, characterMap, effector, payloads) => {
|
|
1125
|
+
const dslCtx = buildDslContextFromLifetime({
|
|
1126
|
+
snapshotSeatMap: playerSeatMap,
|
|
1127
|
+
timelines: [],
|
|
1128
|
+
nowTimelineIndex: -1,
|
|
1129
|
+
characterMap,
|
|
1130
|
+
effector,
|
|
1131
|
+
payloads
|
|
1132
|
+
});
|
|
1133
|
+
const decoratedPlayer = dslCtx.players.find((p) => p.seat === player.seat);
|
|
1134
|
+
const bindings = extendBindings(rootBindings(dslCtx), "it", decoratedPlayer ?? player);
|
|
1135
|
+
return evalExpr(condition.expr, bindings);
|
|
1136
|
+
};
|
|
1137
|
+
var matchesCondition = (player, condition, payloads, characterMap, playerSeatMap, effector) => {
|
|
1138
|
+
if (condition.kind === "EXPR") {
|
|
1139
|
+
return matchesExprCondition(player, condition, playerSeatMap, characterMap, effector, payloads);
|
|
1140
|
+
}
|
|
594
1141
|
if (condition.field === "IS_DEAD") {
|
|
595
1142
|
const expected = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "boolean");
|
|
596
1143
|
if (typeof expected !== "boolean") {
|
|
@@ -606,6 +1153,18 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
|
606
1153
|
const expectedSeat = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "number");
|
|
607
1154
|
return typeof expectedSeat === "number" && player.seat === expectedSeat;
|
|
608
1155
|
}
|
|
1156
|
+
if (condition.field === "ALIGNMENT") {
|
|
1157
|
+
const alignment = player.alignment;
|
|
1158
|
+
if (!alignment) {
|
|
1159
|
+
return false;
|
|
1160
|
+
}
|
|
1161
|
+
if (condition.operator === "IN") {
|
|
1162
|
+
const expectedAlignments = resolveDynamicValue(condition.value, payloads, (value) => Array.isArray(value) && value.every((item) => typeof item === "string"));
|
|
1163
|
+
return Array.isArray(expectedAlignments) && expectedAlignments.includes(alignment);
|
|
1164
|
+
}
|
|
1165
|
+
const expectedAlignment = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
|
|
1166
|
+
return typeof expectedAlignment === "string" && alignment === expectedAlignment;
|
|
1167
|
+
}
|
|
609
1168
|
const character = characterMap.get(player.characterId);
|
|
610
1169
|
const kind = character?.kind;
|
|
611
1170
|
if (!kind) {
|
|
@@ -618,15 +1177,17 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
|
618
1177
|
const expectedKind = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
|
|
619
1178
|
return typeof expectedKind === "string" && kind === expectedKind;
|
|
620
1179
|
};
|
|
621
|
-
var passesWhere = (player, dynamicTarget, payloads, characterMap) => {
|
|
1180
|
+
var passesWhere = (player, dynamicTarget, payloads, characterMap, playerSeatMap, effector) => {
|
|
622
1181
|
const where = dynamicTarget.where;
|
|
623
1182
|
const conditions = where?.conditions ?? [];
|
|
624
1183
|
if (conditions.length === 0) return true;
|
|
625
1184
|
const isAllMode = where?.mode !== "ANY";
|
|
626
|
-
const results = conditions.map(
|
|
1185
|
+
const results = conditions.map(
|
|
1186
|
+
(condition) => matchesCondition(player, condition, payloads, characterMap, playerSeatMap, effector)
|
|
1187
|
+
);
|
|
627
1188
|
return isAllMode ? results.every(Boolean) : results.some(Boolean);
|
|
628
1189
|
};
|
|
629
|
-
var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap) => {
|
|
1190
|
+
var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector) => {
|
|
630
1191
|
const scope = resolveSelectorScope(dynamicTarget, payloads);
|
|
631
1192
|
if (!anchorSeat) return { left: [], right: [] };
|
|
632
1193
|
const anchorIdx = sortedPlayers.findIndex((player) => player.seat === anchorSeat);
|
|
@@ -637,13 +1198,13 @@ var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, c
|
|
|
637
1198
|
if (scope === "LEFT_SIDE" || scope === "BOTH_SIDES") {
|
|
638
1199
|
for (let step = 1; step < total; step += 1) {
|
|
639
1200
|
const player = sortedPlayers[(anchorIdx + step) % total];
|
|
640
|
-
if (passesWhere(player, dynamicTarget, payloads, characterMap)) left.push(player);
|
|
1201
|
+
if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) left.push(player);
|
|
641
1202
|
}
|
|
642
1203
|
}
|
|
643
1204
|
if (scope === "RIGHT_SIDE" || scope === "BOTH_SIDES") {
|
|
644
1205
|
for (let step = 1; step < total; step += 1) {
|
|
645
1206
|
const player = sortedPlayers[(anchorIdx - step + total) % total];
|
|
646
|
-
if (passesWhere(player, dynamicTarget, payloads, characterMap)) right.push(player);
|
|
1207
|
+
if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) right.push(player);
|
|
647
1208
|
}
|
|
648
1209
|
}
|
|
649
1210
|
return { left, right };
|
|
@@ -717,10 +1278,12 @@ var resolveEffectTargets = ({
|
|
|
717
1278
|
const sortedPlayers = getSortedPlayers(playerSeatMap);
|
|
718
1279
|
const anchorSeat = getAnchorSeat(dynamicTarget, payloads, effector);
|
|
719
1280
|
if (!dynamicTarget.selector) {
|
|
720
|
-
const matches = sortedPlayers.filter(
|
|
1281
|
+
const matches = sortedPlayers.filter(
|
|
1282
|
+
(player) => passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)
|
|
1283
|
+
);
|
|
721
1284
|
return pickBalanced({ left: matches, right: [] }, dynamicTarget, payloads);
|
|
722
1285
|
}
|
|
723
|
-
const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap);
|
|
1286
|
+
const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector);
|
|
724
1287
|
return pickBalanced(sides, dynamicTarget, payloads);
|
|
725
1288
|
}
|
|
726
1289
|
return [];
|
|
@@ -866,7 +1429,7 @@ var evalPlayerState = (atom, ctx) => {
|
|
|
866
1429
|
const single = players[0];
|
|
867
1430
|
return evalPlayerStateOnPlayer(single, atom, ctx);
|
|
868
1431
|
};
|
|
869
|
-
var
|
|
1432
|
+
var isExecutionTimeline2 = (tl) => {
|
|
870
1433
|
if (!tl) return false;
|
|
871
1434
|
return (tl.operations ?? []).some((op) => op.kind === "execution");
|
|
872
1435
|
};
|
|
@@ -894,7 +1457,7 @@ var evalTime = (atom, ctx) => {
|
|
|
894
1457
|
if (spec.event === "NEXT_NIGHT" && tl.time === "night") return false;
|
|
895
1458
|
if (spec.event === "NEXT_DUSK" && prevTime === "day" && tl.time === "night") return false;
|
|
896
1459
|
if (spec.event === "NEXT_DAWN" && prevTime === "night" && tl.time === "day") return false;
|
|
897
|
-
if (spec.event === "NEXT_EXECUTION" &&
|
|
1460
|
+
if (spec.event === "NEXT_EXECUTION" && isExecutionTimeline2(tl)) return false;
|
|
898
1461
|
prevTime = tl.time;
|
|
899
1462
|
}
|
|
900
1463
|
return true;
|
|
@@ -935,7 +1498,7 @@ var evalGameState = (atom, ctx) => {
|
|
|
935
1498
|
if (spec.kind === "EXECUTION_HAPPENED_TODAY") {
|
|
936
1499
|
const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
|
|
937
1500
|
if (typeof turn !== "number") return false;
|
|
938
|
-
return ctx.timelines.some((tl) => tl.turn === turn && tl.time === "day" &&
|
|
1501
|
+
return ctx.timelines.some((tl) => tl.turn === turn && tl.time === "day" && isExecutionTimeline2(tl));
|
|
939
1502
|
}
|
|
940
1503
|
if (spec.kind === "PLAYER_DIED_TODAY") {
|
|
941
1504
|
const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
|
|
@@ -950,7 +1513,7 @@ var evalGameState = (atom, ctx) => {
|
|
|
950
1513
|
}
|
|
951
1514
|
return true;
|
|
952
1515
|
};
|
|
953
|
-
var
|
|
1516
|
+
var evalAtom2 = (atom, ctx) => {
|
|
954
1517
|
switch (atom.type) {
|
|
955
1518
|
case "TIME":
|
|
956
1519
|
return evalTime(atom, ctx);
|
|
@@ -969,18 +1532,30 @@ var evalAtom = (atom, ctx) => {
|
|
|
969
1532
|
target: ctx.target,
|
|
970
1533
|
snapshotSeatMap: ctx.snapshotSeatMap
|
|
971
1534
|
}) : false;
|
|
1535
|
+
case "DSL": {
|
|
1536
|
+
if (!ctx.snapshotSeatMap) return true;
|
|
1537
|
+
const dslCtx = buildDslContextFromLifetime({
|
|
1538
|
+
snapshotSeatMap: ctx.snapshotSeatMap,
|
|
1539
|
+
timelines: ctx.timelines,
|
|
1540
|
+
nowTimelineIndex: ctx.nowTimelineIndex,
|
|
1541
|
+
characterMap: ctx.characterMap,
|
|
1542
|
+
effector: ctx.effector,
|
|
1543
|
+
payloads: ctx.payloads
|
|
1544
|
+
});
|
|
1545
|
+
return evalDslExpr(atom.expr, dslCtx);
|
|
1546
|
+
}
|
|
972
1547
|
}
|
|
973
1548
|
};
|
|
974
|
-
var
|
|
1549
|
+
var evalExpr2 = (expr, ctx) => {
|
|
975
1550
|
switch (expr.op) {
|
|
976
1551
|
case "AND":
|
|
977
|
-
return expr.children.every((child) =>
|
|
1552
|
+
return expr.children.every((child) => evalExpr2(child, ctx));
|
|
978
1553
|
case "OR":
|
|
979
|
-
return expr.children.some((child) =>
|
|
1554
|
+
return expr.children.some((child) => evalExpr2(child, ctx));
|
|
980
1555
|
case "NOT":
|
|
981
|
-
return !
|
|
1556
|
+
return !evalExpr2(expr.child, ctx);
|
|
982
1557
|
case "ATOM":
|
|
983
|
-
return
|
|
1558
|
+
return evalAtom2(expr.atom, ctx);
|
|
984
1559
|
}
|
|
985
1560
|
};
|
|
986
1561
|
var expressionReferencesTarget = (expr) => {
|
|
@@ -1018,9 +1593,9 @@ var evaluateLifetime = ({
|
|
|
1018
1593
|
const expr = lifetime.expr;
|
|
1019
1594
|
const dependsOnTarget = expressionReferencesTarget(expr);
|
|
1020
1595
|
if (!dependsOnTarget) {
|
|
1021
|
-
return
|
|
1596
|
+
return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
|
|
1022
1597
|
}
|
|
1023
|
-
return targets.filter((target) =>
|
|
1598
|
+
return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
|
|
1024
1599
|
};
|
|
1025
1600
|
var evaluatePrecondition = ({
|
|
1026
1601
|
expr,
|
|
@@ -1046,9 +1621,9 @@ var evaluatePrecondition = ({
|
|
|
1046
1621
|
};
|
|
1047
1622
|
const dependsOnTarget = expressionReferencesTarget(expr);
|
|
1048
1623
|
if (!dependsOnTarget) {
|
|
1049
|
-
return
|
|
1624
|
+
return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
|
|
1050
1625
|
}
|
|
1051
|
-
return targets.filter((target) =>
|
|
1626
|
+
return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
|
|
1052
1627
|
};
|
|
1053
1628
|
|
|
1054
1629
|
// src/apply-operation.ts
|
|
@@ -1370,6 +1945,12 @@ var deriveContext = ({
|
|
|
1370
1945
|
const yesterdayDayOperations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "day").flatMap((tl) => tl.operations ?? []);
|
|
1371
1946
|
const yesterdayNightOperations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "night").flatMap((tl) => tl.operations ?? []);
|
|
1372
1947
|
const todayNominations = timelines.filter((tl) => tl.turn === last.turn && tl.time === "day").flatMap((tl) => tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee }));
|
|
1948
|
+
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 }));
|
|
1949
|
+
const allNominations = timelines.filter((tl) => tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({
|
|
1950
|
+
nominator: n.nominator,
|
|
1951
|
+
nominee: n.nominee,
|
|
1952
|
+
turn: tl.turn
|
|
1953
|
+
})));
|
|
1373
1954
|
return {
|
|
1374
1955
|
currentTurn: last.turn,
|
|
1375
1956
|
currentPhase: TIME_TO_PHASE[last.time],
|
|
@@ -1381,6 +1962,8 @@ var deriveContext = ({
|
|
|
1381
1962
|
yesterdayDayOperations,
|
|
1382
1963
|
yesterdayNightOperations,
|
|
1383
1964
|
todayNominations,
|
|
1965
|
+
yesterdayNominations,
|
|
1966
|
+
allNominations,
|
|
1384
1967
|
effectorSeat: candidate.effector,
|
|
1385
1968
|
effector,
|
|
1386
1969
|
abilityId: candidate.abilityId,
|
|
@@ -1389,6 +1972,14 @@ var deriveContext = ({
|
|
|
1389
1972
|
operationTimeMap
|
|
1390
1973
|
};
|
|
1391
1974
|
};
|
|
1975
|
+
var nominationsForActionScope = (scope, ctx) => {
|
|
1976
|
+
if (scope === "TONIGHT" || scope === "TODAY") return ctx.todayNominations;
|
|
1977
|
+
if (scope === "YESTERDAY") return ctx.yesterdayNominations;
|
|
1978
|
+
return ctx.allNominations.map((n) => ({ nominator: n.nominator, nominee: n.nominee }));
|
|
1979
|
+
};
|
|
1980
|
+
var extractPayloadSeats = (op) => (op.payloads ?? []).flatMap(
|
|
1981
|
+
(v) => typeof v === "number" ? [v] : Array.isArray(v) ? v.filter((x) => typeof x === "number") : []
|
|
1982
|
+
);
|
|
1392
1983
|
var operationsForActionScope = (scope, ctx) => {
|
|
1393
1984
|
if (scope === "TONIGHT") return ctx.tonightOperations;
|
|
1394
1985
|
if (scope === "TODAY") return ctx.todayOperations;
|
|
@@ -1444,23 +2035,37 @@ var evalAtomAction = (atom, ctx) => {
|
|
|
1444
2035
|
if (subjectSeats.size === 0) return `no players match subject ${atom.subject.ref}`;
|
|
1445
2036
|
const actorSeats = atom.actor ? seatsMatchingTargetRef(atom.actor, ctx) : null;
|
|
1446
2037
|
if (atom.action === "NOMINATED") {
|
|
1447
|
-
const noms = ctx
|
|
2038
|
+
const noms = nominationsForActionScope(atom.scope, ctx);
|
|
1448
2039
|
const found = noms.some(
|
|
1449
2040
|
(n) => subjectSeats.has(n.nominee) && (actorSeats === null || actorSeats.has(n.nominator))
|
|
1450
2041
|
);
|
|
1451
|
-
return found ? null : "no matching NOMINATED event in
|
|
2042
|
+
return found ? null : "no matching NOMINATED event in scope";
|
|
2043
|
+
}
|
|
2044
|
+
if (atom.action === "NOMINATES") {
|
|
2045
|
+
const noms = nominationsForActionScope(atom.scope, ctx);
|
|
2046
|
+
const found = noms.some(
|
|
2047
|
+
(n) => subjectSeats.has(n.nominator) && (actorSeats === null || actorSeats.has(n.nominee))
|
|
2048
|
+
);
|
|
2049
|
+
return found ? null : "no matching NOMINATES event in scope";
|
|
1452
2050
|
}
|
|
1453
2051
|
const scoped = operationsForActionScope(atom.scope, ctx);
|
|
1454
2052
|
if (atom.action === "CHOSEN_AS_TARGET") {
|
|
1455
2053
|
const found = scoped.some((op) => {
|
|
1456
2054
|
if (actorSeats !== null && !actorSeats.has(op.effector)) return false;
|
|
1457
|
-
const payloadSeats = (op
|
|
1458
|
-
(v) => typeof v === "number" ? [v] : Array.isArray(v) ? v.filter((x) => typeof x === "number") : []
|
|
1459
|
-
);
|
|
2055
|
+
const payloadSeats = extractPayloadSeats(op);
|
|
1460
2056
|
return payloadSeats.some((s) => subjectSeats.has(s));
|
|
1461
2057
|
});
|
|
1462
2058
|
return found ? null : "no matching CHOSEN_AS_TARGET in scope";
|
|
1463
2059
|
}
|
|
2060
|
+
if (atom.action === "CHOOSES_TARGET") {
|
|
2061
|
+
const found = scoped.some((op) => {
|
|
2062
|
+
if (!subjectSeats.has(op.effector)) return false;
|
|
2063
|
+
if (actorSeats === null) return true;
|
|
2064
|
+
const payloadSeats = extractPayloadSeats(op);
|
|
2065
|
+
return payloadSeats.some((s) => actorSeats.has(s));
|
|
2066
|
+
});
|
|
2067
|
+
return found ? null : "no matching CHOOSES_TARGET in scope";
|
|
2068
|
+
}
|
|
1464
2069
|
const turnsInScope = (() => {
|
|
1465
2070
|
if (atom.scope === "TONIGHT" || atom.scope === "TODAY") return [ctx.currentTurn];
|
|
1466
2071
|
if (atom.scope === "YESTERDAY") return [ctx.currentTurn - 1];
|
|
@@ -1490,6 +2095,18 @@ var evalAtomAction = (atom, ctx) => {
|
|
|
1490
2095
|
}
|
|
1491
2096
|
return "no matching EXECUTED in scope";
|
|
1492
2097
|
}
|
|
2098
|
+
if (atom.action === "EXECUTES") {
|
|
2099
|
+
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 }));
|
|
2100
|
+
const found = scopedNoms.some((n) => {
|
|
2101
|
+
if (!subjectSeats.has(n.nominator)) return false;
|
|
2102
|
+
if (actorSeats !== null && !actorSeats.has(n.nominee)) return false;
|
|
2103
|
+
const executed = ctx.players.find(
|
|
2104
|
+
(p) => p.seat === n.nominee && p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === n.turn
|
|
2105
|
+
);
|
|
2106
|
+
return Boolean(executed);
|
|
2107
|
+
});
|
|
2108
|
+
return found ? null : "no matching EXECUTES in scope";
|
|
2109
|
+
}
|
|
1493
2110
|
void scoped;
|
|
1494
2111
|
return `unknown action ${atom.action}`;
|
|
1495
2112
|
};
|
|
@@ -1526,7 +2143,7 @@ var evalAtomGlobalCount = (atom, ctx) => {
|
|
|
1526
2143
|
}
|
|
1527
2144
|
return compareCount2(actual, atom.operator, atom.value) ? null : `global count ${atom.subject}=${actual} fails ${atom.operator} ${atom.value}`;
|
|
1528
2145
|
};
|
|
1529
|
-
var
|
|
2146
|
+
var evalAtom3 = (atom, ctx, customResolver) => {
|
|
1530
2147
|
if (atom.type === "TIME") return evalAtomTime(atom, ctx);
|
|
1531
2148
|
if (atom.type === "STATE") return evalAtomState(atom, ctx);
|
|
1532
2149
|
if (atom.type === "ACTION") return evalAtomAction(atom, ctx);
|
|
@@ -1536,12 +2153,16 @@ var evalAtom2 = (atom, ctx, customResolver) => {
|
|
|
1536
2153
|
if (!customResolver) return `no custom resolver registered for ${atom.resolverId}`;
|
|
1537
2154
|
return customResolver({ resolverId: atom.resolverId, args: atom.args }, ctx) ? null : `custom resolver ${atom.resolverId} returned false`;
|
|
1538
2155
|
}
|
|
2156
|
+
if (atom.type === "DSL") {
|
|
2157
|
+
const dslCtx = buildDslContextFromDerived(ctx);
|
|
2158
|
+
return evalDslExpr(atom.expr, dslCtx) ? null : "DSL expression returned false";
|
|
2159
|
+
}
|
|
1539
2160
|
return "unknown atom";
|
|
1540
2161
|
};
|
|
1541
|
-
var
|
|
2162
|
+
var evalExpr3 = (expr, ctx, customResolver) => {
|
|
1542
2163
|
if (expr.op === "AND") {
|
|
1543
2164
|
for (const child of expr.children) {
|
|
1544
|
-
const reason =
|
|
2165
|
+
const reason = evalExpr3(child, ctx, customResolver);
|
|
1545
2166
|
if (reason) return reason;
|
|
1546
2167
|
}
|
|
1547
2168
|
return null;
|
|
@@ -1549,17 +2170,17 @@ var evalExpr2 = (expr, ctx, customResolver) => {
|
|
|
1549
2170
|
if (expr.op === "OR") {
|
|
1550
2171
|
const reasons = [];
|
|
1551
2172
|
for (const child of expr.children) {
|
|
1552
|
-
const reason =
|
|
2173
|
+
const reason = evalExpr3(child, ctx, customResolver);
|
|
1553
2174
|
if (!reason) return null;
|
|
1554
2175
|
reasons.push(reason);
|
|
1555
2176
|
}
|
|
1556
2177
|
return `OR: all branches failed (${reasons.join("; ")})`;
|
|
1557
2178
|
}
|
|
1558
2179
|
if (expr.op === "NOT") {
|
|
1559
|
-
const reason =
|
|
2180
|
+
const reason = evalExpr3(expr.child, ctx, customResolver);
|
|
1560
2181
|
return reason ? null : "NOT: inner expression was true";
|
|
1561
2182
|
}
|
|
1562
|
-
return
|
|
2183
|
+
return evalAtom3(expr.atom, ctx, customResolver);
|
|
1563
2184
|
};
|
|
1564
2185
|
var canInvokeAbility = ({
|
|
1565
2186
|
ability,
|
|
@@ -1579,24 +2200,48 @@ var canInvokeAbility = ({
|
|
|
1579
2200
|
if (derived.effector?.retainsAbility === "ALL") {
|
|
1580
2201
|
return { allowed: true };
|
|
1581
2202
|
}
|
|
1582
|
-
const reason =
|
|
2203
|
+
const reason = evalExpr3(window, derived, customResolver);
|
|
1583
2204
|
return reason ? { allowed: false, reason } : { allowed: true };
|
|
1584
2205
|
};
|
|
2206
|
+
|
|
2207
|
+
// src/trigger-action.ts
|
|
2208
|
+
var ACTIVE_TRIGGER_ACTIONS = /* @__PURE__ */ new Set([
|
|
2209
|
+
"NOMINATES",
|
|
2210
|
+
"CHOOSES_TARGET",
|
|
2211
|
+
"EXECUTES"
|
|
2212
|
+
]);
|
|
2213
|
+
var isActiveTriggerAction = (action) => ACTIVE_TRIGGER_ACTIONS.has(action);
|
|
1585
2214
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1586
2215
|
0 && (module.exports = {
|
|
2216
|
+
ACTIVE_TRIGGER_ACTIONS,
|
|
2217
|
+
DSL_CATALOG,
|
|
2218
|
+
DSL_ITEM_FIELDS,
|
|
2219
|
+
DSL_OPERATORS,
|
|
1587
2220
|
adjustValueAsNumber,
|
|
1588
2221
|
adjustValueAsNumberArray,
|
|
2222
|
+
applyCountOperator,
|
|
1589
2223
|
applyOperationToPlayers,
|
|
2224
|
+
applyOperator,
|
|
2225
|
+
buildDslContextFromDerived,
|
|
2226
|
+
buildDslContextFromLifetime,
|
|
1590
2227
|
buildPlayerSeatMap,
|
|
1591
2228
|
canInvokeAbility,
|
|
1592
2229
|
copyPlayers,
|
|
1593
2230
|
deriveContext,
|
|
2231
|
+
evalDslExpr,
|
|
2232
|
+
evalDslExprWithBindings,
|
|
2233
|
+
evaluatePath,
|
|
2234
|
+
extendBindings,
|
|
1594
2235
|
getSourceValue,
|
|
1595
2236
|
getValueFromPayloads,
|
|
2237
|
+
isActiveTriggerAction,
|
|
1596
2238
|
isNumberOrNumberArray,
|
|
1597
2239
|
isPlayerReminderArray,
|
|
1598
2240
|
isReminder,
|
|
1599
2241
|
resolveSourceValue,
|
|
1600
2242
|
resolveTargetRef,
|
|
2243
|
+
rootBindings,
|
|
2244
|
+
traceDslExpr,
|
|
2245
|
+
traceExpr,
|
|
1601
2246
|
transformEmptyArray
|
|
1602
2247
|
});
|