@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.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") {
|
|
@@ -572,11 +1103,6 @@ var resolveDynamicValue = (value, payloads, isExpected) => {
|
|
|
572
1103
|
var getSortedPlayers = (playerSeatMap) => {
|
|
573
1104
|
return [...playerSeatMap.values()].sort((left, right) => left.seat - right.seat);
|
|
574
1105
|
};
|
|
575
|
-
var getCircularDistance = (anchorIdx, targetIdx, total) => {
|
|
576
|
-
const clockwise = (targetIdx - anchorIdx + total) % total;
|
|
577
|
-
const anticlockwise = (anchorIdx - targetIdx + total) % total;
|
|
578
|
-
return Math.min(clockwise, anticlockwise);
|
|
579
|
-
};
|
|
580
1106
|
var getAnchorSeat = (dynamicTarget, payloads, effector) => {
|
|
581
1107
|
const anchor = dynamicTarget.anchor;
|
|
582
1108
|
if (!anchor || anchor.from === "EFFECTOR") {
|
|
@@ -595,43 +1121,23 @@ var resolveSelectorScope = (dynamicTarget, payloads) => {
|
|
|
595
1121
|
(value) => value === "BOTH_SIDES" || value === "LEFT_SIDE" || value === "RIGHT_SIDE"
|
|
596
1122
|
) ?? "BOTH_SIDES";
|
|
597
1123
|
};
|
|
598
|
-
var
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
}
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
}
|
|
611
|
-
const total = sortedPlayers.length;
|
|
612
|
-
if (scope === "LEFT_SIDE") {
|
|
613
|
-
const left2 = [];
|
|
614
|
-
for (let step = 1; step < total; step += 1) {
|
|
615
|
-
left2.push(sortedPlayers[(anchorIdx + step) % total]);
|
|
616
|
-
}
|
|
617
|
-
return left2;
|
|
618
|
-
}
|
|
619
|
-
if (scope === "RIGHT_SIDE") {
|
|
620
|
-
const right2 = [];
|
|
621
|
-
for (let step = 1; step < total; step += 1) {
|
|
622
|
-
right2.push(sortedPlayers[(anchorIdx - step + total) % total]);
|
|
623
|
-
}
|
|
624
|
-
return right2;
|
|
625
|
-
}
|
|
626
|
-
const left = [];
|
|
627
|
-
const right = [];
|
|
628
|
-
for (let step = 1; step < total; step += 1) {
|
|
629
|
-
left.push(sortedPlayers[(anchorIdx + step) % total]);
|
|
630
|
-
right.push(sortedPlayers[(anchorIdx - step + total) % total]);
|
|
631
|
-
}
|
|
632
|
-
return [...left, ...right];
|
|
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);
|
|
633
1136
|
};
|
|
634
|
-
var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
1137
|
+
var matchesCondition = (player, condition, payloads, characterMap, playerSeatMap, effector) => {
|
|
1138
|
+
if (condition.kind === "EXPR") {
|
|
1139
|
+
return matchesExprCondition(player, condition, playerSeatMap, characterMap, effector, payloads);
|
|
1140
|
+
}
|
|
635
1141
|
if (condition.field === "IS_DEAD") {
|
|
636
1142
|
const expected = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "boolean");
|
|
637
1143
|
if (typeof expected !== "boolean") {
|
|
@@ -647,6 +1153,18 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
|
647
1153
|
const expectedSeat = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "number");
|
|
648
1154
|
return typeof expectedSeat === "number" && player.seat === expectedSeat;
|
|
649
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
|
+
}
|
|
650
1168
|
const character = characterMap.get(player.characterId);
|
|
651
1169
|
const kind = character?.kind;
|
|
652
1170
|
if (!kind) {
|
|
@@ -659,55 +1177,74 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
|
659
1177
|
const expectedKind = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
|
|
660
1178
|
return typeof expectedKind === "string" && kind === expectedKind;
|
|
661
1179
|
};
|
|
662
|
-
var
|
|
1180
|
+
var passesWhere = (player, dynamicTarget, payloads, characterMap, playerSeatMap, effector) => {
|
|
663
1181
|
const where = dynamicTarget.where;
|
|
664
1182
|
const conditions = where?.conditions ?? [];
|
|
665
|
-
if (conditions.length === 0)
|
|
666
|
-
return candidates;
|
|
667
|
-
}
|
|
1183
|
+
if (conditions.length === 0) return true;
|
|
668
1184
|
const isAllMode = where?.mode !== "ANY";
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1185
|
+
const results = conditions.map(
|
|
1186
|
+
(condition) => matchesCondition(player, condition, payloads, characterMap, playerSeatMap, effector)
|
|
1187
|
+
);
|
|
1188
|
+
return isAllMode ? results.every(Boolean) : results.some(Boolean);
|
|
673
1189
|
};
|
|
674
|
-
var
|
|
1190
|
+
var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector) => {
|
|
675
1191
|
const scope = resolveSelectorScope(dynamicTarget, payloads);
|
|
676
|
-
if (
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
}
|
|
687
|
-
return [...candidates].sort((left, right) => {
|
|
688
|
-
const leftIdx = seatIndexMap.get(left.seat);
|
|
689
|
-
const rightIdx = seatIndexMap.get(right.seat);
|
|
690
|
-
if (typeof leftIdx !== "number" || typeof rightIdx !== "number") {
|
|
691
|
-
return left.seat - right.seat;
|
|
1192
|
+
if (!anchorSeat) return { left: [], right: [] };
|
|
1193
|
+
const anchorIdx = sortedPlayers.findIndex((player) => player.seat === anchorSeat);
|
|
1194
|
+
if (anchorIdx < 0) return { left: [], right: [] };
|
|
1195
|
+
const total = sortedPlayers.length;
|
|
1196
|
+
const left = [];
|
|
1197
|
+
const right = [];
|
|
1198
|
+
if (scope === "LEFT_SIDE" || scope === "BOTH_SIDES") {
|
|
1199
|
+
for (let step = 1; step < total; step += 1) {
|
|
1200
|
+
const player = sortedPlayers[(anchorIdx + step) % total];
|
|
1201
|
+
if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) left.push(player);
|
|
692
1202
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
1203
|
+
}
|
|
1204
|
+
if (scope === "RIGHT_SIDE" || scope === "BOTH_SIDES") {
|
|
1205
|
+
for (let step = 1; step < total; step += 1) {
|
|
1206
|
+
const player = sortedPlayers[(anchorIdx - step + total) % total];
|
|
1207
|
+
if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) right.push(player);
|
|
697
1208
|
}
|
|
698
|
-
|
|
699
|
-
}
|
|
1209
|
+
}
|
|
1210
|
+
return { left, right };
|
|
700
1211
|
};
|
|
701
|
-
var
|
|
702
|
-
const dedupedCandidates = [...new Map(sortedCandidates.map((player) => [player.seat, player])).values()];
|
|
1212
|
+
var pickBalanced = (sides, dynamicTarget, payloads) => {
|
|
703
1213
|
const configuredLimit = resolveDynamicValue(dynamicTarget.limit, payloads, (value) => typeof value === "number");
|
|
1214
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1215
|
+
const out = [];
|
|
1216
|
+
const pushUnique = (player) => {
|
|
1217
|
+
if (!player || seen.has(player.seat)) return false;
|
|
1218
|
+
seen.add(player.seat);
|
|
1219
|
+
out.push(player);
|
|
1220
|
+
return true;
|
|
1221
|
+
};
|
|
704
1222
|
if (typeof configuredLimit === "undefined") {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
1223
|
+
sides.left.forEach(pushUnique);
|
|
1224
|
+
sides.right.forEach(pushUnique);
|
|
1225
|
+
return out;
|
|
1226
|
+
}
|
|
1227
|
+
if (configuredLimit <= 0) return [];
|
|
1228
|
+
let li = 0;
|
|
1229
|
+
let ri = 0;
|
|
1230
|
+
let preferLeft = true;
|
|
1231
|
+
while (out.length < configuredLimit && (li < sides.left.length || ri < sides.right.length)) {
|
|
1232
|
+
if (preferLeft && li < sides.left.length) {
|
|
1233
|
+
if (pushUnique(sides.left[li])) li += 1;
|
|
1234
|
+
else li += 1;
|
|
1235
|
+
} else if (!preferLeft && ri < sides.right.length) {
|
|
1236
|
+
if (pushUnique(sides.right[ri])) ri += 1;
|
|
1237
|
+
else ri += 1;
|
|
1238
|
+
} else if (li < sides.left.length) {
|
|
1239
|
+
if (pushUnique(sides.left[li])) li += 1;
|
|
1240
|
+
else li += 1;
|
|
1241
|
+
} else if (ri < sides.right.length) {
|
|
1242
|
+
if (pushUnique(sides.right[ri])) ri += 1;
|
|
1243
|
+
else ri += 1;
|
|
1244
|
+
}
|
|
1245
|
+
preferLeft = !preferLeft;
|
|
709
1246
|
}
|
|
710
|
-
return
|
|
1247
|
+
return out;
|
|
711
1248
|
};
|
|
712
1249
|
var resolveEffectTargets = ({
|
|
713
1250
|
effect,
|
|
@@ -740,10 +1277,14 @@ var resolveEffectTargets = ({
|
|
|
740
1277
|
const dynamicTarget = effect.target;
|
|
741
1278
|
const sortedPlayers = getSortedPlayers(playerSeatMap);
|
|
742
1279
|
const anchorSeat = getAnchorSeat(dynamicTarget, payloads, effector);
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
1280
|
+
if (!dynamicTarget.selector) {
|
|
1281
|
+
const matches = sortedPlayers.filter(
|
|
1282
|
+
(player) => passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)
|
|
1283
|
+
);
|
|
1284
|
+
return pickBalanced({ left: matches, right: [] }, dynamicTarget, payloads);
|
|
1285
|
+
}
|
|
1286
|
+
const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector);
|
|
1287
|
+
return pickBalanced(sides, dynamicTarget, payloads);
|
|
747
1288
|
}
|
|
748
1289
|
return [];
|
|
749
1290
|
};
|
|
@@ -888,7 +1429,7 @@ var evalPlayerState = (atom, ctx) => {
|
|
|
888
1429
|
const single = players[0];
|
|
889
1430
|
return evalPlayerStateOnPlayer(single, atom, ctx);
|
|
890
1431
|
};
|
|
891
|
-
var
|
|
1432
|
+
var isExecutionTimeline2 = (tl) => {
|
|
892
1433
|
if (!tl) return false;
|
|
893
1434
|
return (tl.operations ?? []).some((op) => op.kind === "execution");
|
|
894
1435
|
};
|
|
@@ -916,7 +1457,7 @@ var evalTime = (atom, ctx) => {
|
|
|
916
1457
|
if (spec.event === "NEXT_NIGHT" && tl.time === "night") return false;
|
|
917
1458
|
if (spec.event === "NEXT_DUSK" && prevTime === "day" && tl.time === "night") return false;
|
|
918
1459
|
if (spec.event === "NEXT_DAWN" && prevTime === "night" && tl.time === "day") return false;
|
|
919
|
-
if (spec.event === "NEXT_EXECUTION" &&
|
|
1460
|
+
if (spec.event === "NEXT_EXECUTION" && isExecutionTimeline2(tl)) return false;
|
|
920
1461
|
prevTime = tl.time;
|
|
921
1462
|
}
|
|
922
1463
|
return true;
|
|
@@ -957,7 +1498,7 @@ var evalGameState = (atom, ctx) => {
|
|
|
957
1498
|
if (spec.kind === "EXECUTION_HAPPENED_TODAY") {
|
|
958
1499
|
const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
|
|
959
1500
|
if (typeof turn !== "number") return false;
|
|
960
|
-
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));
|
|
961
1502
|
}
|
|
962
1503
|
if (spec.kind === "PLAYER_DIED_TODAY") {
|
|
963
1504
|
const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
|
|
@@ -972,7 +1513,7 @@ var evalGameState = (atom, ctx) => {
|
|
|
972
1513
|
}
|
|
973
1514
|
return true;
|
|
974
1515
|
};
|
|
975
|
-
var
|
|
1516
|
+
var evalAtom2 = (atom, ctx) => {
|
|
976
1517
|
switch (atom.type) {
|
|
977
1518
|
case "TIME":
|
|
978
1519
|
return evalTime(atom, ctx);
|
|
@@ -991,18 +1532,30 @@ var evalAtom = (atom, ctx) => {
|
|
|
991
1532
|
target: ctx.target,
|
|
992
1533
|
snapshotSeatMap: ctx.snapshotSeatMap
|
|
993
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
|
+
}
|
|
994
1547
|
}
|
|
995
1548
|
};
|
|
996
|
-
var
|
|
1549
|
+
var evalExpr2 = (expr, ctx) => {
|
|
997
1550
|
switch (expr.op) {
|
|
998
1551
|
case "AND":
|
|
999
|
-
return expr.children.every((child) =>
|
|
1552
|
+
return expr.children.every((child) => evalExpr2(child, ctx));
|
|
1000
1553
|
case "OR":
|
|
1001
|
-
return expr.children.some((child) =>
|
|
1554
|
+
return expr.children.some((child) => evalExpr2(child, ctx));
|
|
1002
1555
|
case "NOT":
|
|
1003
|
-
return !
|
|
1556
|
+
return !evalExpr2(expr.child, ctx);
|
|
1004
1557
|
case "ATOM":
|
|
1005
|
-
return
|
|
1558
|
+
return evalAtom2(expr.atom, ctx);
|
|
1006
1559
|
}
|
|
1007
1560
|
};
|
|
1008
1561
|
var expressionReferencesTarget = (expr) => {
|
|
@@ -1040,9 +1593,9 @@ var evaluateLifetime = ({
|
|
|
1040
1593
|
const expr = lifetime.expr;
|
|
1041
1594
|
const dependsOnTarget = expressionReferencesTarget(expr);
|
|
1042
1595
|
if (!dependsOnTarget) {
|
|
1043
|
-
return
|
|
1596
|
+
return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
|
|
1044
1597
|
}
|
|
1045
|
-
return targets.filter((target) =>
|
|
1598
|
+
return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
|
|
1046
1599
|
};
|
|
1047
1600
|
var evaluatePrecondition = ({
|
|
1048
1601
|
expr,
|
|
@@ -1068,9 +1621,9 @@ var evaluatePrecondition = ({
|
|
|
1068
1621
|
};
|
|
1069
1622
|
const dependsOnTarget = expressionReferencesTarget(expr);
|
|
1070
1623
|
if (!dependsOnTarget) {
|
|
1071
|
-
return
|
|
1624
|
+
return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
|
|
1072
1625
|
}
|
|
1073
|
-
return targets.filter((target) =>
|
|
1626
|
+
return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
|
|
1074
1627
|
};
|
|
1075
1628
|
|
|
1076
1629
|
// src/apply-operation.ts
|
|
@@ -1392,6 +1945,12 @@ var deriveContext = ({
|
|
|
1392
1945
|
const yesterdayDayOperations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "day").flatMap((tl) => tl.operations ?? []);
|
|
1393
1946
|
const yesterdayNightOperations = timelines.filter((tl) => tl.turn === last.turn - 1 && tl.time === "night").flatMap((tl) => tl.operations ?? []);
|
|
1394
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
|
+
})));
|
|
1395
1954
|
return {
|
|
1396
1955
|
currentTurn: last.turn,
|
|
1397
1956
|
currentPhase: TIME_TO_PHASE[last.time],
|
|
@@ -1403,6 +1962,8 @@ var deriveContext = ({
|
|
|
1403
1962
|
yesterdayDayOperations,
|
|
1404
1963
|
yesterdayNightOperations,
|
|
1405
1964
|
todayNominations,
|
|
1965
|
+
yesterdayNominations,
|
|
1966
|
+
allNominations,
|
|
1406
1967
|
effectorSeat: candidate.effector,
|
|
1407
1968
|
effector,
|
|
1408
1969
|
abilityId: candidate.abilityId,
|
|
@@ -1411,6 +1972,14 @@ var deriveContext = ({
|
|
|
1411
1972
|
operationTimeMap
|
|
1412
1973
|
};
|
|
1413
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
|
+
);
|
|
1414
1983
|
var operationsForActionScope = (scope, ctx) => {
|
|
1415
1984
|
if (scope === "TONIGHT") return ctx.tonightOperations;
|
|
1416
1985
|
if (scope === "TODAY") return ctx.todayOperations;
|
|
@@ -1466,23 +2035,37 @@ var evalAtomAction = (atom, ctx) => {
|
|
|
1466
2035
|
if (subjectSeats.size === 0) return `no players match subject ${atom.subject.ref}`;
|
|
1467
2036
|
const actorSeats = atom.actor ? seatsMatchingTargetRef(atom.actor, ctx) : null;
|
|
1468
2037
|
if (atom.action === "NOMINATED") {
|
|
1469
|
-
const noms = ctx
|
|
2038
|
+
const noms = nominationsForActionScope(atom.scope, ctx);
|
|
1470
2039
|
const found = noms.some(
|
|
1471
2040
|
(n) => subjectSeats.has(n.nominee) && (actorSeats === null || actorSeats.has(n.nominator))
|
|
1472
2041
|
);
|
|
1473
|
-
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";
|
|
1474
2050
|
}
|
|
1475
2051
|
const scoped = operationsForActionScope(atom.scope, ctx);
|
|
1476
2052
|
if (atom.action === "CHOSEN_AS_TARGET") {
|
|
1477
2053
|
const found = scoped.some((op) => {
|
|
1478
2054
|
if (actorSeats !== null && !actorSeats.has(op.effector)) return false;
|
|
1479
|
-
const payloadSeats = (op
|
|
1480
|
-
(v) => typeof v === "number" ? [v] : Array.isArray(v) ? v.filter((x) => typeof x === "number") : []
|
|
1481
|
-
);
|
|
2055
|
+
const payloadSeats = extractPayloadSeats(op);
|
|
1482
2056
|
return payloadSeats.some((s) => subjectSeats.has(s));
|
|
1483
2057
|
});
|
|
1484
2058
|
return found ? null : "no matching CHOSEN_AS_TARGET in scope";
|
|
1485
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
|
+
}
|
|
1486
2069
|
const turnsInScope = (() => {
|
|
1487
2070
|
if (atom.scope === "TONIGHT" || atom.scope === "TODAY") return [ctx.currentTurn];
|
|
1488
2071
|
if (atom.scope === "YESTERDAY") return [ctx.currentTurn - 1];
|
|
@@ -1512,6 +2095,18 @@ var evalAtomAction = (atom, ctx) => {
|
|
|
1512
2095
|
}
|
|
1513
2096
|
return "no matching EXECUTED in scope";
|
|
1514
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
|
+
}
|
|
1515
2110
|
void scoped;
|
|
1516
2111
|
return `unknown action ${atom.action}`;
|
|
1517
2112
|
};
|
|
@@ -1548,7 +2143,7 @@ var evalAtomGlobalCount = (atom, ctx) => {
|
|
|
1548
2143
|
}
|
|
1549
2144
|
return compareCount2(actual, atom.operator, atom.value) ? null : `global count ${atom.subject}=${actual} fails ${atom.operator} ${atom.value}`;
|
|
1550
2145
|
};
|
|
1551
|
-
var
|
|
2146
|
+
var evalAtom3 = (atom, ctx, customResolver) => {
|
|
1552
2147
|
if (atom.type === "TIME") return evalAtomTime(atom, ctx);
|
|
1553
2148
|
if (atom.type === "STATE") return evalAtomState(atom, ctx);
|
|
1554
2149
|
if (atom.type === "ACTION") return evalAtomAction(atom, ctx);
|
|
@@ -1558,12 +2153,16 @@ var evalAtom2 = (atom, ctx, customResolver) => {
|
|
|
1558
2153
|
if (!customResolver) return `no custom resolver registered for ${atom.resolverId}`;
|
|
1559
2154
|
return customResolver({ resolverId: atom.resolverId, args: atom.args }, ctx) ? null : `custom resolver ${atom.resolverId} returned false`;
|
|
1560
2155
|
}
|
|
2156
|
+
if (atom.type === "DSL") {
|
|
2157
|
+
const dslCtx = buildDslContextFromDerived(ctx);
|
|
2158
|
+
return evalDslExpr(atom.expr, dslCtx) ? null : "DSL expression returned false";
|
|
2159
|
+
}
|
|
1561
2160
|
return "unknown atom";
|
|
1562
2161
|
};
|
|
1563
|
-
var
|
|
2162
|
+
var evalExpr3 = (expr, ctx, customResolver) => {
|
|
1564
2163
|
if (expr.op === "AND") {
|
|
1565
2164
|
for (const child of expr.children) {
|
|
1566
|
-
const reason =
|
|
2165
|
+
const reason = evalExpr3(child, ctx, customResolver);
|
|
1567
2166
|
if (reason) return reason;
|
|
1568
2167
|
}
|
|
1569
2168
|
return null;
|
|
@@ -1571,17 +2170,17 @@ var evalExpr2 = (expr, ctx, customResolver) => {
|
|
|
1571
2170
|
if (expr.op === "OR") {
|
|
1572
2171
|
const reasons = [];
|
|
1573
2172
|
for (const child of expr.children) {
|
|
1574
|
-
const reason =
|
|
2173
|
+
const reason = evalExpr3(child, ctx, customResolver);
|
|
1575
2174
|
if (!reason) return null;
|
|
1576
2175
|
reasons.push(reason);
|
|
1577
2176
|
}
|
|
1578
2177
|
return `OR: all branches failed (${reasons.join("; ")})`;
|
|
1579
2178
|
}
|
|
1580
2179
|
if (expr.op === "NOT") {
|
|
1581
|
-
const reason =
|
|
2180
|
+
const reason = evalExpr3(expr.child, ctx, customResolver);
|
|
1582
2181
|
return reason ? null : "NOT: inner expression was true";
|
|
1583
2182
|
}
|
|
1584
|
-
return
|
|
2183
|
+
return evalAtom3(expr.atom, ctx, customResolver);
|
|
1585
2184
|
};
|
|
1586
2185
|
var canInvokeAbility = ({
|
|
1587
2186
|
ability,
|
|
@@ -1601,24 +2200,48 @@ var canInvokeAbility = ({
|
|
|
1601
2200
|
if (derived.effector?.retainsAbility === "ALL") {
|
|
1602
2201
|
return { allowed: true };
|
|
1603
2202
|
}
|
|
1604
|
-
const reason =
|
|
2203
|
+
const reason = evalExpr3(window, derived, customResolver);
|
|
1605
2204
|
return reason ? { allowed: false, reason } : { allowed: true };
|
|
1606
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);
|
|
1607
2214
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1608
2215
|
0 && (module.exports = {
|
|
2216
|
+
ACTIVE_TRIGGER_ACTIONS,
|
|
2217
|
+
DSL_CATALOG,
|
|
2218
|
+
DSL_ITEM_FIELDS,
|
|
2219
|
+
DSL_OPERATORS,
|
|
1609
2220
|
adjustValueAsNumber,
|
|
1610
2221
|
adjustValueAsNumberArray,
|
|
2222
|
+
applyCountOperator,
|
|
1611
2223
|
applyOperationToPlayers,
|
|
2224
|
+
applyOperator,
|
|
2225
|
+
buildDslContextFromDerived,
|
|
2226
|
+
buildDslContextFromLifetime,
|
|
1612
2227
|
buildPlayerSeatMap,
|
|
1613
2228
|
canInvokeAbility,
|
|
1614
2229
|
copyPlayers,
|
|
1615
2230
|
deriveContext,
|
|
2231
|
+
evalDslExpr,
|
|
2232
|
+
evalDslExprWithBindings,
|
|
2233
|
+
evaluatePath,
|
|
2234
|
+
extendBindings,
|
|
1616
2235
|
getSourceValue,
|
|
1617
2236
|
getValueFromPayloads,
|
|
2237
|
+
isActiveTriggerAction,
|
|
1618
2238
|
isNumberOrNumberArray,
|
|
1619
2239
|
isPlayerReminderArray,
|
|
1620
2240
|
isReminder,
|
|
1621
2241
|
resolveSourceValue,
|
|
1622
2242
|
resolveTargetRef,
|
|
2243
|
+
rootBindings,
|
|
2244
|
+
traceDslExpr,
|
|
2245
|
+
traceExpr,
|
|
1623
2246
|
transformEmptyArray
|
|
1624
2247
|
});
|