@bct-app/game-engine 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +149 -2
- package/dist/index.d.ts +149 -2
- package/dist/index.js +681 -36
- package/dist/index.mjs +665 -36
- package/package.json +2 -2
package/dist/index.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);
|
|
@@ -503,19 +519,18 @@ var applyStatusChange = createEffectHandler("STATUS_CHANGE", ({
|
|
|
503
519
|
effect,
|
|
504
520
|
payloads,
|
|
505
521
|
makePlayersEffect,
|
|
506
|
-
|
|
522
|
+
operationTurn,
|
|
507
523
|
abilityMap,
|
|
508
524
|
operation
|
|
509
525
|
}) => {
|
|
510
526
|
const maybeStatus = getSourceValue(effect.source, payloads);
|
|
511
527
|
if (maybeStatus === "DEAD") {
|
|
512
528
|
const ability = abilityMap.get(operation.abilityId);
|
|
513
|
-
const turn = operationInTimelineIdx >= 0 ? operationInTimelineIdx : 0;
|
|
514
529
|
const cause = effect.deathCause ?? (ability?.category === "STORYTELLER" ? "STORYTELLER" : "ABILITY");
|
|
515
530
|
makePlayersEffect.forEach((player) => {
|
|
516
531
|
player.isDead = true;
|
|
517
532
|
player.deathCause = cause;
|
|
518
|
-
player.deathTurn =
|
|
533
|
+
player.deathTurn = operationTurn;
|
|
519
534
|
});
|
|
520
535
|
return;
|
|
521
536
|
}
|
|
@@ -545,6 +560,521 @@ var effectHandlers = {
|
|
|
545
560
|
STATUS_CHANGE: applyStatusChange
|
|
546
561
|
};
|
|
547
562
|
|
|
563
|
+
// src/dsl/context.ts
|
|
564
|
+
var decoratePlayer = (player, characterMap, currentTurn) => {
|
|
565
|
+
const isDead = Boolean(player.isDead);
|
|
566
|
+
const retainsBypass = isDead && (player.retainsAbility === "ALIVENESS" || player.retainsAbility === "ALL");
|
|
567
|
+
const wasExecuted = isDead && player.deathCause === "EXECUTION";
|
|
568
|
+
return {
|
|
569
|
+
...player,
|
|
570
|
+
characterKind: characterMap.get(player.characterId)?.kind,
|
|
571
|
+
reminderMarks: (player.reminders ?? []).map((r) => r.mark),
|
|
572
|
+
effectiveAlive: !isDead || retainsBypass,
|
|
573
|
+
diedThisTurn: isDead && typeof player.deathTurn === "number" && player.deathTurn === currentTurn,
|
|
574
|
+
wasExecuted,
|
|
575
|
+
wasExecutedToday: wasExecuted && player.deathTurn === currentTurn
|
|
576
|
+
};
|
|
577
|
+
};
|
|
578
|
+
var isExecutionTimeline = (tl) => {
|
|
579
|
+
if (!tl) return false;
|
|
580
|
+
return (tl.operations ?? []).some((op) => op.kind === "execution");
|
|
581
|
+
};
|
|
582
|
+
var buildDslContextFromDerived = (derived, payload = []) => {
|
|
583
|
+
const players = derived.players.map((p) => decoratePlayer(p, derived.characterMap, derived.currentTurn));
|
|
584
|
+
const effector = players.find((p) => p.seat === derived.effectorSeat);
|
|
585
|
+
const aliveCount = players.filter((p) => !p.isDead).length;
|
|
586
|
+
const deadCount = players.length - aliveCount;
|
|
587
|
+
const executedTodayCount = players.filter(
|
|
588
|
+
(p) => p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === derived.currentTurn
|
|
589
|
+
).length;
|
|
590
|
+
const nominatedTodayCount = derived.todayNominations.length;
|
|
591
|
+
const playerDiedToday = players.some(
|
|
592
|
+
(p) => p.isDead && typeof p.deathTurn === "number" && p.deathTurn === derived.currentTurn
|
|
593
|
+
);
|
|
594
|
+
return {
|
|
595
|
+
effector,
|
|
596
|
+
players,
|
|
597
|
+
operations: {
|
|
598
|
+
all: derived.allOperations,
|
|
599
|
+
today: derived.todayOperations,
|
|
600
|
+
tonight: derived.tonightOperations,
|
|
601
|
+
yesterdayDay: derived.yesterdayDayOperations,
|
|
602
|
+
yesterdayNight: derived.yesterdayNightOperations
|
|
603
|
+
},
|
|
604
|
+
nominations: {
|
|
605
|
+
all: derived.allNominations,
|
|
606
|
+
today: derived.todayNominations,
|
|
607
|
+
yesterday: derived.yesterdayNominations
|
|
608
|
+
},
|
|
609
|
+
currentTimeline: {
|
|
610
|
+
turn: derived.currentTurn,
|
|
611
|
+
phase: derived.currentPhase,
|
|
612
|
+
time: derived.currentPhase === "NIGHT" ? "night" : derived.currentPhase === "DAY" ? "day" : void 0,
|
|
613
|
+
isFirstOfPhase: derived.isFirstOfPhase,
|
|
614
|
+
isExecution: (derived.currentPhase === "DAY" ? derived.todayOperations : derived.tonightOperations).some((op) => op.kind === "execution")
|
|
615
|
+
},
|
|
616
|
+
global: {
|
|
617
|
+
aliveCount,
|
|
618
|
+
deadCount,
|
|
619
|
+
executedTodayCount,
|
|
620
|
+
nominatedTodayCount,
|
|
621
|
+
executionHappenedToday: derived.todayOperations.some((op) => op.kind === "execution"),
|
|
622
|
+
playerDiedToday
|
|
623
|
+
},
|
|
624
|
+
payload
|
|
625
|
+
};
|
|
626
|
+
};
|
|
627
|
+
var buildDslContextFromLifetime = ({
|
|
628
|
+
snapshotSeatMap,
|
|
629
|
+
timelines,
|
|
630
|
+
nowTimelineIndex,
|
|
631
|
+
characterMap,
|
|
632
|
+
effector,
|
|
633
|
+
payloads
|
|
634
|
+
}) => {
|
|
635
|
+
const nowTl = timelines[nowTimelineIndex];
|
|
636
|
+
const currentTurn = nowTl?.turn ?? -1;
|
|
637
|
+
const currentTime = nowTl?.time;
|
|
638
|
+
const currentPhase = currentTime === "night" ? "NIGHT" : currentTime === "day" ? "DAY" : "NIGHT";
|
|
639
|
+
const playersRaw = snapshotSeatMap ? [...snapshotSeatMap.values()] : [];
|
|
640
|
+
const players = playersRaw.map((p) => decoratePlayer(p, characterMap, currentTurn));
|
|
641
|
+
const effectorDecorated = effector ? players.find((p) => p.seat === effector.seat) ?? decoratePlayer(effector, characterMap, currentTurn) : void 0;
|
|
642
|
+
const todayOps = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "day").flatMap((tl) => tl.operations ?? []);
|
|
643
|
+
const tonightOps = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "night").flatMap((tl) => tl.operations ?? []);
|
|
644
|
+
const yesterdayDayOps = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "day").flatMap((tl) => tl.operations ?? []);
|
|
645
|
+
const yesterdayNightOps = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "night").flatMap((tl) => tl.operations ?? []);
|
|
646
|
+
const allOps = timelines.flatMap((tl) => tl.operations ?? []);
|
|
647
|
+
const todayNoms = timelines.filter((tl) => tl.turn === currentTurn && tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee })));
|
|
648
|
+
const yesterdayNoms = timelines.filter((tl) => tl.turn === currentTurn - 1 && tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({ nominator: n.nominator, nominee: n.nominee })));
|
|
649
|
+
const allNoms = timelines.filter((tl) => tl.time === "day").flatMap((tl) => (tl.nominations ?? []).map((n) => ({
|
|
650
|
+
nominator: n.nominator,
|
|
651
|
+
nominee: n.nominee,
|
|
652
|
+
turn: tl.turn
|
|
653
|
+
})));
|
|
654
|
+
const aliveCount = players.filter((p) => !p.isDead).length;
|
|
655
|
+
const deadCount = players.length - aliveCount;
|
|
656
|
+
const executedTodayCount = players.filter(
|
|
657
|
+
(p) => p.isDead && p.deathCause === "EXECUTION" && p.deathTurn === currentTurn
|
|
658
|
+
).length;
|
|
659
|
+
const playerDiedToday = timelines.some((tl) => {
|
|
660
|
+
if (tl.turn !== currentTurn || tl.time !== "day") return false;
|
|
661
|
+
return (tl.operations ?? []).some((op) => {
|
|
662
|
+
const kind = op.kind;
|
|
663
|
+
return kind === "execution" || kind === "death";
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
return {
|
|
667
|
+
effector: effectorDecorated,
|
|
668
|
+
players,
|
|
669
|
+
operations: {
|
|
670
|
+
all: allOps,
|
|
671
|
+
today: todayOps,
|
|
672
|
+
tonight: tonightOps,
|
|
673
|
+
yesterdayDay: yesterdayDayOps,
|
|
674
|
+
yesterdayNight: yesterdayNightOps
|
|
675
|
+
},
|
|
676
|
+
nominations: {
|
|
677
|
+
all: allNoms,
|
|
678
|
+
today: todayNoms,
|
|
679
|
+
yesterday: yesterdayNoms
|
|
680
|
+
},
|
|
681
|
+
currentTimeline: {
|
|
682
|
+
turn: currentTurn,
|
|
683
|
+
phase: currentPhase,
|
|
684
|
+
time: currentTime,
|
|
685
|
+
isFirstOfPhase: false,
|
|
686
|
+
isExecution: isExecutionTimeline(nowTl)
|
|
687
|
+
},
|
|
688
|
+
global: {
|
|
689
|
+
aliveCount,
|
|
690
|
+
deadCount,
|
|
691
|
+
executedTodayCount,
|
|
692
|
+
nominatedTodayCount: todayNoms.length,
|
|
693
|
+
executionHappenedToday: timelines.some(
|
|
694
|
+
(tl) => tl.turn === currentTurn && tl.time === "day" && isExecutionTimeline(tl)
|
|
695
|
+
),
|
|
696
|
+
playerDiedToday
|
|
697
|
+
},
|
|
698
|
+
payload: payloads
|
|
699
|
+
};
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
// src/dsl/bindings.ts
|
|
703
|
+
var rootBindings = (ctx) => ({
|
|
704
|
+
effector: ctx.effector,
|
|
705
|
+
players: ctx.players,
|
|
706
|
+
operations: ctx.operations,
|
|
707
|
+
nominations: ctx.nominations,
|
|
708
|
+
currentTimeline: ctx.currentTimeline,
|
|
709
|
+
global: ctx.global,
|
|
710
|
+
payload: ctx.payload
|
|
711
|
+
});
|
|
712
|
+
var extendBindings = (parent, name, value) => ({
|
|
713
|
+
...parent,
|
|
714
|
+
[name]: value
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
// src/dsl/operators.ts
|
|
718
|
+
var deepEqual = (a, b) => {
|
|
719
|
+
if (a === b) return true;
|
|
720
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
721
|
+
if (a.length !== b.length) return false;
|
|
722
|
+
for (let i = 0; i < a.length; i++) {
|
|
723
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
724
|
+
}
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
return false;
|
|
728
|
+
};
|
|
729
|
+
var asNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
730
|
+
var compareOrder = (lhs, rhs, cmp) => {
|
|
731
|
+
const l = asNumber(lhs);
|
|
732
|
+
const r = asNumber(rhs);
|
|
733
|
+
if (l === void 0 || r === void 0) return false;
|
|
734
|
+
return cmp(l, r);
|
|
735
|
+
};
|
|
736
|
+
var applyOperator = (op, lhs, rhs) => {
|
|
737
|
+
switch (op) {
|
|
738
|
+
case "EQ":
|
|
739
|
+
return deepEqual(lhs, rhs);
|
|
740
|
+
case "NE":
|
|
741
|
+
return !deepEqual(lhs, rhs);
|
|
742
|
+
case "LT":
|
|
743
|
+
return compareOrder(lhs, rhs, (l, r) => l < r);
|
|
744
|
+
case "LTE":
|
|
745
|
+
return compareOrder(lhs, rhs, (l, r) => l <= r);
|
|
746
|
+
case "GT":
|
|
747
|
+
return compareOrder(lhs, rhs, (l, r) => l > r);
|
|
748
|
+
case "GTE":
|
|
749
|
+
return compareOrder(lhs, rhs, (l, r) => l >= r);
|
|
750
|
+
case "IN":
|
|
751
|
+
return Array.isArray(rhs) && rhs.some((v) => deepEqual(v, lhs));
|
|
752
|
+
case "NOT_IN":
|
|
753
|
+
return Array.isArray(rhs) && !rhs.some((v) => deepEqual(v, lhs));
|
|
754
|
+
case "CONTAINS":
|
|
755
|
+
if (Array.isArray(lhs)) return lhs.some((v) => deepEqual(v, rhs));
|
|
756
|
+
if (typeof lhs === "string") return typeof rhs === "string" && lhs.includes(rhs);
|
|
757
|
+
return false;
|
|
758
|
+
case "MATCHES":
|
|
759
|
+
if (typeof lhs !== "string" || typeof rhs !== "string") return false;
|
|
760
|
+
try {
|
|
761
|
+
return new RegExp(rhs).test(lhs);
|
|
762
|
+
} catch {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
case "EXISTS":
|
|
766
|
+
return lhs !== void 0 && lhs !== null;
|
|
767
|
+
case "TRUTHY":
|
|
768
|
+
return Boolean(lhs);
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
var applyCountOperator = (op, actual, expected) => {
|
|
772
|
+
switch (op) {
|
|
773
|
+
case "EQ":
|
|
774
|
+
return actual === expected;
|
|
775
|
+
case "NE":
|
|
776
|
+
return actual !== expected;
|
|
777
|
+
case "LT":
|
|
778
|
+
return actual < expected;
|
|
779
|
+
case "LTE":
|
|
780
|
+
return actual <= expected;
|
|
781
|
+
case "GT":
|
|
782
|
+
return actual > expected;
|
|
783
|
+
case "GTE":
|
|
784
|
+
return actual >= expected;
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
// src/dsl/path.ts
|
|
789
|
+
var MAX_STEPS = 32;
|
|
790
|
+
var stepInto = (current, step) => {
|
|
791
|
+
if (current === void 0 || current === null) return void 0;
|
|
792
|
+
if (step.kind === "field") {
|
|
793
|
+
if (typeof current !== "object") return void 0;
|
|
794
|
+
const record = current;
|
|
795
|
+
return record[step.name];
|
|
796
|
+
}
|
|
797
|
+
if (step.kind === "index") {
|
|
798
|
+
if (!Array.isArray(current)) return void 0;
|
|
799
|
+
return current[step.value];
|
|
800
|
+
}
|
|
801
|
+
return void 0;
|
|
802
|
+
};
|
|
803
|
+
var evaluatePath = (path, bindings) => {
|
|
804
|
+
if (path.steps.length > MAX_STEPS) return void 0;
|
|
805
|
+
let current = Object.prototype.hasOwnProperty.call(bindings, path.root) ? bindings[path.root] : void 0;
|
|
806
|
+
for (const step of path.steps) {
|
|
807
|
+
current = stepInto(current, step);
|
|
808
|
+
if (current === void 0) return void 0;
|
|
809
|
+
}
|
|
810
|
+
return current;
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
// src/dsl/eval.ts
|
|
814
|
+
var MAX_EXPR_DEPTH = 16;
|
|
815
|
+
var MAX_QUANTIFIER_SIZE = 256;
|
|
816
|
+
var resolveValue = (value, bindings) => {
|
|
817
|
+
if (!value) return void 0;
|
|
818
|
+
if (value.kind === "LITERAL") return value.value;
|
|
819
|
+
return evaluatePath(value.path, bindings);
|
|
820
|
+
};
|
|
821
|
+
var evalAtom = (atom, bindings, depth) => {
|
|
822
|
+
if (atom.type === "PATH") {
|
|
823
|
+
const lhs = evaluatePath(atom.path, bindings);
|
|
824
|
+
const rhs = resolveValue(atom.value, bindings);
|
|
825
|
+
return applyOperator(atom.op, lhs, rhs);
|
|
826
|
+
}
|
|
827
|
+
const source = evaluatePath(atom.source, bindings);
|
|
828
|
+
if (!Array.isArray(source)) {
|
|
829
|
+
return atom.type === "FORALL";
|
|
830
|
+
}
|
|
831
|
+
if (source.length > MAX_QUANTIFIER_SIZE) return false;
|
|
832
|
+
if (atom.type === "EXISTS") {
|
|
833
|
+
return source.some(
|
|
834
|
+
(item) => evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
if (atom.type === "FORALL") {
|
|
838
|
+
return source.every(
|
|
839
|
+
(item) => evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
let count = 0;
|
|
843
|
+
for (const item of source) {
|
|
844
|
+
if (!atom.where || evalExpr(atom.where, extendBindings(bindings, atom.as, item), depth + 1)) {
|
|
845
|
+
count += 1;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
return applyCountOperator(atom.op, count, atom.value);
|
|
849
|
+
};
|
|
850
|
+
var evalExpr = (expr, bindings, depth = 0) => {
|
|
851
|
+
if (depth > MAX_EXPR_DEPTH) return false;
|
|
852
|
+
switch (expr.op) {
|
|
853
|
+
case "AND":
|
|
854
|
+
return expr.children.every((child) => evalExpr(child, bindings, depth + 1));
|
|
855
|
+
case "OR":
|
|
856
|
+
return expr.children.some((child) => evalExpr(child, bindings, depth + 1));
|
|
857
|
+
case "NOT":
|
|
858
|
+
return !evalExpr(expr.child, bindings, depth + 1);
|
|
859
|
+
case "ATOM":
|
|
860
|
+
return evalAtom(expr.atom, bindings, depth + 1);
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
var evalDslExpr = (expr, ctx) => evalExpr(expr, rootBindings(ctx), 0);
|
|
864
|
+
|
|
865
|
+
// src/dsl/catalog.ts
|
|
866
|
+
var PLAYER_FIELDS = [
|
|
867
|
+
{ name: "seat", type: "number", label: "\u5EA7\u4F4D\u53F7" },
|
|
868
|
+
{ name: "alignment", type: "string", label: "\u9635\u8425", description: "GOOD / EVIL" },
|
|
869
|
+
{ name: "isDead", type: "boolean", label: "\u662F\u5426\u6B7B\u4EA1" },
|
|
870
|
+
{ name: "effectiveAlive", type: "boolean", label: "\u6709\u6548\u5B58\u6D3B", description: "\u8003\u8651 retainsAbility\uFF08ALIVENESS/ALL\uFF09\u540E\u662F\u5426\u6309\u5B58\u6D3B\u5904\u7406" },
|
|
871
|
+
{ name: "diedThisTurn", type: "boolean", label: "\u672C\u56DE\u5408\u6B7B\u4EA1" },
|
|
872
|
+
{ name: "wasExecuted", type: "boolean", label: "\u66FE\u88AB\u5904\u51B3" },
|
|
873
|
+
{ name: "wasExecutedToday", type: "boolean", label: "\u4ECA\u65E5\u88AB\u5904\u51B3" },
|
|
874
|
+
{ name: "characterId", type: "string", label: "\u89D2\u8272 ID" },
|
|
875
|
+
{ name: "characterKind", type: "string", label: "\u89D2\u8272\u7C7B\u578B", description: "Townsfolk / Outsiders / Minions / Demons" },
|
|
876
|
+
{ name: "deathCause", type: "string", label: "\u6B7B\u4EA1\u539F\u56E0", description: "EXECUTION / DEMON / ABILITY / STORYTELLER / OTHER" },
|
|
877
|
+
{ name: "deathTurn", type: "number", label: "\u6B7B\u4EA1\u56DE\u5408" },
|
|
878
|
+
{ name: "abilities", type: "string[]", label: "\u80FD\u529B ID \u5217\u8868" },
|
|
879
|
+
{ name: "reminderMarks", type: "string[]", label: "\u63D0\u793A\u7269 mark \u5217\u8868" },
|
|
880
|
+
{ name: "hasUsedDeadVote", type: "boolean", label: "\u662F\u5426\u4F7F\u7528\u8FC7\u6B7B\u4EA1\u6295\u7968" },
|
|
881
|
+
{ name: "retainsAbility", type: "string", label: "\u80FD\u529B\u4FDD\u7559\u8303\u56F4" }
|
|
882
|
+
];
|
|
883
|
+
var OPERATION_FIELDS = [
|
|
884
|
+
{ name: "effector", type: "number", label: "\u65BD\u52A8\u8005\u5EA7\u4F4D" },
|
|
885
|
+
{ name: "abilityId", type: "string", label: "\u80FD\u529B ID" },
|
|
886
|
+
{ name: "kind", type: "string", label: "\u64CD\u4F5C\u79CD\u7C7B" },
|
|
887
|
+
{ name: "payloads", type: "unknown[]", label: "Payload \u5217\u8868" }
|
|
888
|
+
];
|
|
889
|
+
var NOMINATION_FIELDS = [
|
|
890
|
+
{ name: "nominator", type: "number", label: "\u63D0\u540D\u8005\u5EA7\u4F4D" },
|
|
891
|
+
{ name: "nominee", type: "number", label: "\u88AB\u63D0\u540D\u8005\u5EA7\u4F4D" },
|
|
892
|
+
{ name: "turn", type: "number", label: "\u56DE\u5408", description: "\u4EC5 all \u96C6\u5408\u53EF\u7528" }
|
|
893
|
+
];
|
|
894
|
+
var CURRENT_TIMELINE_FIELDS = [
|
|
895
|
+
{ name: "turn", type: "number", label: "\u5F53\u524D\u56DE\u5408" },
|
|
896
|
+
{ name: "phase", type: "string", label: "\u5F53\u524D\u9636\u6BB5", description: "NIGHT / DAY / DUSK / DAWN" },
|
|
897
|
+
{ name: "time", type: "string", label: "\u5F53\u524D\u65F6\u6BB5", description: "night / day" },
|
|
898
|
+
{ name: "isFirstOfPhase", type: "boolean", label: "\u662F\u5426\u672C\u9636\u6BB5\u9996\u6B21" },
|
|
899
|
+
{ name: "isExecution", type: "boolean", label: "\u5F53\u524D\u65F6\u95F4\u7EBF\u542B\u5904\u51B3" }
|
|
900
|
+
];
|
|
901
|
+
var GLOBAL_FIELDS = [
|
|
902
|
+
{ name: "aliveCount", type: "number", label: "\u5B58\u6D3B\u73A9\u5BB6\u6570" },
|
|
903
|
+
{ name: "deadCount", type: "number", label: "\u6B7B\u4EA1\u73A9\u5BB6\u6570" },
|
|
904
|
+
{ name: "executedTodayCount", type: "number", label: "\u4ECA\u65E5\u88AB\u5904\u51B3\u4EBA\u6570" },
|
|
905
|
+
{ name: "nominatedTodayCount", type: "number", label: "\u4ECA\u65E5\u63D0\u540D\u603B\u6570" },
|
|
906
|
+
{ name: "executionHappenedToday", type: "boolean", label: "\u4ECA\u65E5\u662F\u5426\u5DF2\u53D1\u751F\u5904\u51B3" },
|
|
907
|
+
{ name: "playerDiedToday", type: "boolean", label: "\u4ECA\u65E5\u662F\u5426\u6709\u73A9\u5BB6\u6B7B\u4EA1" }
|
|
908
|
+
];
|
|
909
|
+
var OPERATIONS_FIELDS = [
|
|
910
|
+
{ name: "all", type: "operation[]", label: "\u6240\u6709\u64CD\u4F5C" },
|
|
911
|
+
{ name: "today", type: "operation[]", label: "\u4ECA\u65E5\u64CD\u4F5C" },
|
|
912
|
+
{ name: "tonight", type: "operation[]", label: "\u4ECA\u591C\u64CD\u4F5C" },
|
|
913
|
+
{ name: "yesterdayDay", type: "operation[]", label: "\u6628\u65E5\u767D\u5929\u64CD\u4F5C" },
|
|
914
|
+
{ name: "yesterdayNight", type: "operation[]", label: "\u6628\u591C\u64CD\u4F5C" }
|
|
915
|
+
];
|
|
916
|
+
var NOMINATIONS_FIELDS = [
|
|
917
|
+
{ name: "all", type: "nomination[]", label: "\u6240\u6709\u63D0\u540D" },
|
|
918
|
+
{ name: "today", type: "nomination[]", label: "\u4ECA\u65E5\u63D0\u540D" },
|
|
919
|
+
{ name: "yesterday", type: "nomination[]", label: "\u6628\u65E5\u63D0\u540D" }
|
|
920
|
+
];
|
|
921
|
+
var DSL_CATALOG = [
|
|
922
|
+
{
|
|
923
|
+
name: "effector",
|
|
924
|
+
type: "player",
|
|
925
|
+
label: "\u80FD\u529B\u53D1\u52A8\u8005",
|
|
926
|
+
description: "\u5F53\u524D\u8BC4\u4F30\u65F6\u7684\u80FD\u529B\u65BD\u653E\u8005\u5FEB\u7167",
|
|
927
|
+
fields: PLAYER_FIELDS
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
name: "players",
|
|
931
|
+
type: "player[]",
|
|
932
|
+
label: "\u5168\u90E8\u73A9\u5BB6",
|
|
933
|
+
description: "\u5F53\u524D\u5FEB\u7167\u4E0B\u6240\u6709\u73A9\u5BB6\uFF08\u5E94\u914D\u5408 EXISTS / FORALL / COUNT \u4F7F\u7528\uFF09"
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
name: "operations",
|
|
937
|
+
type: "object",
|
|
938
|
+
label: "\u5386\u53F2\u64CD\u4F5C\u96C6\u5408",
|
|
939
|
+
fields: OPERATIONS_FIELDS
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
name: "nominations",
|
|
943
|
+
type: "object",
|
|
944
|
+
label: "\u5386\u53F2\u63D0\u540D\u96C6\u5408",
|
|
945
|
+
fields: NOMINATIONS_FIELDS
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
name: "currentTimeline",
|
|
949
|
+
type: "object",
|
|
950
|
+
label: "\u5F53\u524D\u65F6\u95F4\u7EBF",
|
|
951
|
+
fields: CURRENT_TIMELINE_FIELDS
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
name: "global",
|
|
955
|
+
type: "object",
|
|
956
|
+
label: "\u5168\u5C40\u805A\u5408",
|
|
957
|
+
fields: GLOBAL_FIELDS
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
name: "payload",
|
|
961
|
+
type: "unknown[]",
|
|
962
|
+
label: "\u80FD\u529B Payload \u6570\u7EC4",
|
|
963
|
+
description: "\u5F53\u524D\u8C03\u7528 / \u64CD\u4F5C\u7684 payload \u539F\u59CB\u6570\u7EC4"
|
|
964
|
+
}
|
|
965
|
+
];
|
|
966
|
+
var DSL_ITEM_FIELDS = {
|
|
967
|
+
player: PLAYER_FIELDS,
|
|
968
|
+
operation: OPERATION_FIELDS,
|
|
969
|
+
nomination: NOMINATION_FIELDS
|
|
970
|
+
};
|
|
971
|
+
var DSL_OPERATORS = [
|
|
972
|
+
{ value: "EQ", label: "\u7B49\u4E8E", arity: "binary" },
|
|
973
|
+
{ value: "NE", label: "\u4E0D\u7B49\u4E8E", arity: "binary" },
|
|
974
|
+
{ value: "LT", label: "<", arity: "binary" },
|
|
975
|
+
{ value: "LTE", label: "<=", arity: "binary" },
|
|
976
|
+
{ value: "GT", label: ">", arity: "binary" },
|
|
977
|
+
{ value: "GTE", label: ">=", arity: "binary" },
|
|
978
|
+
{ value: "IN", label: "\u5C5E\u4E8E", arity: "binary" },
|
|
979
|
+
{ value: "NOT_IN", label: "\u4E0D\u5C5E\u4E8E", arity: "binary" },
|
|
980
|
+
{ value: "CONTAINS", label: "\u5305\u542B", arity: "binary" },
|
|
981
|
+
{ value: "MATCHES", label: "\u6B63\u5219\u5339\u914D", arity: "binary" },
|
|
982
|
+
{ value: "EXISTS", label: "\u5B58\u5728", arity: "unary" },
|
|
983
|
+
{ value: "TRUTHY", label: "\u4E3A\u771F", arity: "unary" }
|
|
984
|
+
];
|
|
985
|
+
|
|
986
|
+
// src/dsl/trace.ts
|
|
987
|
+
var MAX_QUANTIFIER_SIZE2 = 256;
|
|
988
|
+
var renderPath = (p) => p.steps.reduce((acc, s) => acc + (s.kind === "field" ? `.${s.name}` : `[${s.value}]`), p.root);
|
|
989
|
+
var resolveValue2 = (value, bindings) => {
|
|
990
|
+
if (!value) return void 0;
|
|
991
|
+
if (value.kind === "LITERAL") return value.value;
|
|
992
|
+
return evaluatePath(value.path, bindings);
|
|
993
|
+
};
|
|
994
|
+
var renderValue = (value, bindings) => {
|
|
995
|
+
if (!value) return void 0;
|
|
996
|
+
if (value.kind === "LITERAL") return value.value;
|
|
997
|
+
return { ref: renderPath(value.path), resolved: evaluatePath(value.path, bindings) };
|
|
998
|
+
};
|
|
999
|
+
var traceAtom = (atom, bindings) => {
|
|
1000
|
+
if (atom.type === "PATH") {
|
|
1001
|
+
const lhs = evaluatePath(atom.path, bindings);
|
|
1002
|
+
const rhs = resolveValue2(atom.value, bindings);
|
|
1003
|
+
const ok2 = applyOperator(atom.op, lhs, rhs);
|
|
1004
|
+
return {
|
|
1005
|
+
op: "ATOM",
|
|
1006
|
+
atomType: "PATH",
|
|
1007
|
+
ok: ok2,
|
|
1008
|
+
path: renderPath(atom.path),
|
|
1009
|
+
operator: atom.op,
|
|
1010
|
+
lhs,
|
|
1011
|
+
rhs: renderValue(atom.value, bindings)
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
const source = evaluatePath(atom.source, bindings);
|
|
1015
|
+
const sourceArr = Array.isArray(source) ? source : [];
|
|
1016
|
+
const sizeCapped = sourceArr.length > MAX_QUANTIFIER_SIZE2;
|
|
1017
|
+
let matched = 0;
|
|
1018
|
+
if (!sizeCapped) {
|
|
1019
|
+
for (const item of sourceArr) {
|
|
1020
|
+
const childBindings = extendBindings(bindings, atom.as, item);
|
|
1021
|
+
if (!atom.where || traceExprOk(atom.where, childBindings)) matched += 1;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (atom.type === "COUNT") {
|
|
1025
|
+
const ok2 = !sizeCapped && applyCountOperator(atom.op, matched, atom.value);
|
|
1026
|
+
return {
|
|
1027
|
+
op: "ATOM",
|
|
1028
|
+
atomType: "COUNT",
|
|
1029
|
+
ok: ok2,
|
|
1030
|
+
source: renderPath(atom.source),
|
|
1031
|
+
size: sourceArr.length,
|
|
1032
|
+
matched,
|
|
1033
|
+
operator: atom.op,
|
|
1034
|
+
expected: atom.value,
|
|
1035
|
+
bindings: atom.as
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
const ok = atom.type === "EXISTS" ? matched > 0 : !sizeCapped && (sourceArr.length === 0 || matched === sourceArr.length);
|
|
1039
|
+
return {
|
|
1040
|
+
op: "ATOM",
|
|
1041
|
+
atomType: atom.type,
|
|
1042
|
+
ok,
|
|
1043
|
+
source: renderPath(atom.source),
|
|
1044
|
+
size: sourceArr.length,
|
|
1045
|
+
matched,
|
|
1046
|
+
bindings: atom.as
|
|
1047
|
+
};
|
|
1048
|
+
};
|
|
1049
|
+
var traceExprOk = (expr, bindings) => {
|
|
1050
|
+
switch (expr.op) {
|
|
1051
|
+
case "AND":
|
|
1052
|
+
return expr.children.every((c) => traceExprOk(c, bindings));
|
|
1053
|
+
case "OR":
|
|
1054
|
+
return expr.children.some((c) => traceExprOk(c, bindings));
|
|
1055
|
+
case "NOT":
|
|
1056
|
+
return !traceExprOk(expr.child, bindings);
|
|
1057
|
+
case "ATOM":
|
|
1058
|
+
return traceAtom(expr.atom, bindings).ok;
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
var traceExpr = (expr, bindings) => {
|
|
1062
|
+
if (expr.op === "AND") {
|
|
1063
|
+
const children = expr.children.map((c) => traceExpr(c, bindings));
|
|
1064
|
+
return { op: "AND", ok: children.every((c) => c.ok), children };
|
|
1065
|
+
}
|
|
1066
|
+
if (expr.op === "OR") {
|
|
1067
|
+
const children = expr.children.map((c) => traceExpr(c, bindings));
|
|
1068
|
+
return { op: "OR", ok: children.some((c) => c.ok), children };
|
|
1069
|
+
}
|
|
1070
|
+
if (expr.op === "NOT") {
|
|
1071
|
+
const child = traceExpr(expr.child, bindings);
|
|
1072
|
+
return { op: "NOT", ok: !child.ok, child };
|
|
1073
|
+
}
|
|
1074
|
+
return traceAtom(expr.atom, bindings);
|
|
1075
|
+
};
|
|
1076
|
+
var traceDslExpr = (expr, ctx) => traceExpr(expr, rootBindings(ctx));
|
|
1077
|
+
|
|
548
1078
|
// src/effects/resolve-targets.ts
|
|
549
1079
|
var isPayloadRef = (value) => {
|
|
550
1080
|
if (!value || typeof value !== "object") {
|
|
@@ -590,7 +1120,23 @@ var resolveSelectorScope = (dynamicTarget, payloads) => {
|
|
|
590
1120
|
(value) => value === "BOTH_SIDES" || value === "LEFT_SIDE" || value === "RIGHT_SIDE"
|
|
591
1121
|
) ?? "BOTH_SIDES";
|
|
592
1122
|
};
|
|
593
|
-
var
|
|
1123
|
+
var matchesExprCondition = (player, condition, playerSeatMap, characterMap, effector, payloads) => {
|
|
1124
|
+
const dslCtx = buildDslContextFromLifetime({
|
|
1125
|
+
snapshotSeatMap: playerSeatMap,
|
|
1126
|
+
timelines: [],
|
|
1127
|
+
nowTimelineIndex: -1,
|
|
1128
|
+
characterMap,
|
|
1129
|
+
effector,
|
|
1130
|
+
payloads
|
|
1131
|
+
});
|
|
1132
|
+
const decoratedPlayer = dslCtx.players.find((p) => p.seat === player.seat);
|
|
1133
|
+
const bindings = extendBindings(rootBindings(dslCtx), "it", decoratedPlayer ?? player);
|
|
1134
|
+
return evalExpr(condition.expr, bindings);
|
|
1135
|
+
};
|
|
1136
|
+
var matchesCondition = (player, condition, payloads, characterMap, playerSeatMap, effector) => {
|
|
1137
|
+
if (condition.kind === "EXPR") {
|
|
1138
|
+
return matchesExprCondition(player, condition, playerSeatMap, characterMap, effector, payloads);
|
|
1139
|
+
}
|
|
594
1140
|
if (condition.field === "IS_DEAD") {
|
|
595
1141
|
const expected = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "boolean");
|
|
596
1142
|
if (typeof expected !== "boolean") {
|
|
@@ -606,6 +1152,18 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
|
606
1152
|
const expectedSeat = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "number");
|
|
607
1153
|
return typeof expectedSeat === "number" && player.seat === expectedSeat;
|
|
608
1154
|
}
|
|
1155
|
+
if (condition.field === "ALIGNMENT") {
|
|
1156
|
+
const alignment = player.alignment;
|
|
1157
|
+
if (!alignment) {
|
|
1158
|
+
return false;
|
|
1159
|
+
}
|
|
1160
|
+
if (condition.operator === "IN") {
|
|
1161
|
+
const expectedAlignments = resolveDynamicValue(condition.value, payloads, (value) => Array.isArray(value) && value.every((item) => typeof item === "string"));
|
|
1162
|
+
return Array.isArray(expectedAlignments) && expectedAlignments.includes(alignment);
|
|
1163
|
+
}
|
|
1164
|
+
const expectedAlignment = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
|
|
1165
|
+
return typeof expectedAlignment === "string" && alignment === expectedAlignment;
|
|
1166
|
+
}
|
|
609
1167
|
const character = characterMap.get(player.characterId);
|
|
610
1168
|
const kind = character?.kind;
|
|
611
1169
|
if (!kind) {
|
|
@@ -618,15 +1176,17 @@ var matchesCondition = (player, condition, payloads, characterMap) => {
|
|
|
618
1176
|
const expectedKind = resolveDynamicValue(condition.value, payloads, (value) => typeof value === "string");
|
|
619
1177
|
return typeof expectedKind === "string" && kind === expectedKind;
|
|
620
1178
|
};
|
|
621
|
-
var passesWhere = (player, dynamicTarget, payloads, characterMap) => {
|
|
1179
|
+
var passesWhere = (player, dynamicTarget, payloads, characterMap, playerSeatMap, effector) => {
|
|
622
1180
|
const where = dynamicTarget.where;
|
|
623
1181
|
const conditions = where?.conditions ?? [];
|
|
624
1182
|
if (conditions.length === 0) return true;
|
|
625
1183
|
const isAllMode = where?.mode !== "ANY";
|
|
626
|
-
const results = conditions.map(
|
|
1184
|
+
const results = conditions.map(
|
|
1185
|
+
(condition) => matchesCondition(player, condition, payloads, characterMap, playerSeatMap, effector)
|
|
1186
|
+
);
|
|
627
1187
|
return isAllMode ? results.every(Boolean) : results.some(Boolean);
|
|
628
1188
|
};
|
|
629
|
-
var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap) => {
|
|
1189
|
+
var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector) => {
|
|
630
1190
|
const scope = resolveSelectorScope(dynamicTarget, payloads);
|
|
631
1191
|
if (!anchorSeat) return { left: [], right: [] };
|
|
632
1192
|
const anchorIdx = sortedPlayers.findIndex((player) => player.seat === anchorSeat);
|
|
@@ -637,13 +1197,13 @@ var walkSidesWithFilter = (dynamicTarget, sortedPlayers, anchorSeat, payloads, c
|
|
|
637
1197
|
if (scope === "LEFT_SIDE" || scope === "BOTH_SIDES") {
|
|
638
1198
|
for (let step = 1; step < total; step += 1) {
|
|
639
1199
|
const player = sortedPlayers[(anchorIdx + step) % total];
|
|
640
|
-
if (passesWhere(player, dynamicTarget, payloads, characterMap)) left.push(player);
|
|
1200
|
+
if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) left.push(player);
|
|
641
1201
|
}
|
|
642
1202
|
}
|
|
643
1203
|
if (scope === "RIGHT_SIDE" || scope === "BOTH_SIDES") {
|
|
644
1204
|
for (let step = 1; step < total; step += 1) {
|
|
645
1205
|
const player = sortedPlayers[(anchorIdx - step + total) % total];
|
|
646
|
-
if (passesWhere(player, dynamicTarget, payloads, characterMap)) right.push(player);
|
|
1206
|
+
if (passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)) right.push(player);
|
|
647
1207
|
}
|
|
648
1208
|
}
|
|
649
1209
|
return { left, right };
|
|
@@ -717,10 +1277,12 @@ var resolveEffectTargets = ({
|
|
|
717
1277
|
const sortedPlayers = getSortedPlayers(playerSeatMap);
|
|
718
1278
|
const anchorSeat = getAnchorSeat(dynamicTarget, payloads, effector);
|
|
719
1279
|
if (!dynamicTarget.selector) {
|
|
720
|
-
const matches = sortedPlayers.filter(
|
|
1280
|
+
const matches = sortedPlayers.filter(
|
|
1281
|
+
(player) => passesWhere(player, dynamicTarget, payloads, characterMap, playerSeatMap, effector)
|
|
1282
|
+
);
|
|
721
1283
|
return pickBalanced({ left: matches, right: [] }, dynamicTarget, payloads);
|
|
722
1284
|
}
|
|
723
|
-
const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap);
|
|
1285
|
+
const sides = walkSidesWithFilter(dynamicTarget, sortedPlayers, anchorSeat, payloads, characterMap, playerSeatMap, effector);
|
|
724
1286
|
return pickBalanced(sides, dynamicTarget, payloads);
|
|
725
1287
|
}
|
|
726
1288
|
return [];
|
|
@@ -866,7 +1428,7 @@ var evalPlayerState = (atom, ctx) => {
|
|
|
866
1428
|
const single = players[0];
|
|
867
1429
|
return evalPlayerStateOnPlayer(single, atom, ctx);
|
|
868
1430
|
};
|
|
869
|
-
var
|
|
1431
|
+
var isExecutionTimeline2 = (tl) => {
|
|
870
1432
|
if (!tl) return false;
|
|
871
1433
|
return (tl.operations ?? []).some((op) => op.kind === "execution");
|
|
872
1434
|
};
|
|
@@ -894,7 +1456,7 @@ var evalTime = (atom, ctx) => {
|
|
|
894
1456
|
if (spec.event === "NEXT_NIGHT" && tl.time === "night") return false;
|
|
895
1457
|
if (spec.event === "NEXT_DUSK" && prevTime === "day" && tl.time === "night") return false;
|
|
896
1458
|
if (spec.event === "NEXT_DAWN" && prevTime === "night" && tl.time === "day") return false;
|
|
897
|
-
if (spec.event === "NEXT_EXECUTION" &&
|
|
1459
|
+
if (spec.event === "NEXT_EXECUTION" && isExecutionTimeline2(tl)) return false;
|
|
898
1460
|
prevTime = tl.time;
|
|
899
1461
|
}
|
|
900
1462
|
return true;
|
|
@@ -935,7 +1497,7 @@ var evalGameState = (atom, ctx) => {
|
|
|
935
1497
|
if (spec.kind === "EXECUTION_HAPPENED_TODAY") {
|
|
936
1498
|
const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
|
|
937
1499
|
if (typeof turn !== "number") return false;
|
|
938
|
-
return ctx.timelines.some((tl) => tl.turn === turn && tl.time === "day" &&
|
|
1500
|
+
return ctx.timelines.some((tl) => tl.turn === turn && tl.time === "day" && isExecutionTimeline2(tl));
|
|
939
1501
|
}
|
|
940
1502
|
if (spec.kind === "PLAYER_DIED_TODAY") {
|
|
941
1503
|
const turn = ctx.timelines[ctx.nowTimelineIndex]?.turn;
|
|
@@ -950,7 +1512,7 @@ var evalGameState = (atom, ctx) => {
|
|
|
950
1512
|
}
|
|
951
1513
|
return true;
|
|
952
1514
|
};
|
|
953
|
-
var
|
|
1515
|
+
var evalAtom2 = (atom, ctx) => {
|
|
954
1516
|
switch (atom.type) {
|
|
955
1517
|
case "TIME":
|
|
956
1518
|
return evalTime(atom, ctx);
|
|
@@ -969,18 +1531,30 @@ var evalAtom = (atom, ctx) => {
|
|
|
969
1531
|
target: ctx.target,
|
|
970
1532
|
snapshotSeatMap: ctx.snapshotSeatMap
|
|
971
1533
|
}) : false;
|
|
1534
|
+
case "DSL": {
|
|
1535
|
+
if (!ctx.snapshotSeatMap) return true;
|
|
1536
|
+
const dslCtx = buildDslContextFromLifetime({
|
|
1537
|
+
snapshotSeatMap: ctx.snapshotSeatMap,
|
|
1538
|
+
timelines: ctx.timelines,
|
|
1539
|
+
nowTimelineIndex: ctx.nowTimelineIndex,
|
|
1540
|
+
characterMap: ctx.characterMap,
|
|
1541
|
+
effector: ctx.effector,
|
|
1542
|
+
payloads: ctx.payloads
|
|
1543
|
+
});
|
|
1544
|
+
return evalDslExpr(atom.expr, dslCtx);
|
|
1545
|
+
}
|
|
972
1546
|
}
|
|
973
1547
|
};
|
|
974
|
-
var
|
|
1548
|
+
var evalExpr2 = (expr, ctx) => {
|
|
975
1549
|
switch (expr.op) {
|
|
976
1550
|
case "AND":
|
|
977
|
-
return expr.children.every((child) =>
|
|
1551
|
+
return expr.children.every((child) => evalExpr2(child, ctx));
|
|
978
1552
|
case "OR":
|
|
979
|
-
return expr.children.some((child) =>
|
|
1553
|
+
return expr.children.some((child) => evalExpr2(child, ctx));
|
|
980
1554
|
case "NOT":
|
|
981
|
-
return !
|
|
1555
|
+
return !evalExpr2(expr.child, ctx);
|
|
982
1556
|
case "ATOM":
|
|
983
|
-
return
|
|
1557
|
+
return evalAtom2(expr.atom, ctx);
|
|
984
1558
|
}
|
|
985
1559
|
};
|
|
986
1560
|
var expressionReferencesTarget = (expr) => {
|
|
@@ -1018,9 +1592,9 @@ var evaluateLifetime = ({
|
|
|
1018
1592
|
const expr = lifetime.expr;
|
|
1019
1593
|
const dependsOnTarget = expressionReferencesTarget(expr);
|
|
1020
1594
|
if (!dependsOnTarget) {
|
|
1021
|
-
return
|
|
1595
|
+
return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
|
|
1022
1596
|
}
|
|
1023
|
-
return targets.filter((target) =>
|
|
1597
|
+
return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
|
|
1024
1598
|
};
|
|
1025
1599
|
var evaluatePrecondition = ({
|
|
1026
1600
|
expr,
|
|
@@ -1046,9 +1620,9 @@ var evaluatePrecondition = ({
|
|
|
1046
1620
|
};
|
|
1047
1621
|
const dependsOnTarget = expressionReferencesTarget(expr);
|
|
1048
1622
|
if (!dependsOnTarget) {
|
|
1049
|
-
return
|
|
1623
|
+
return evalExpr2(expr, { ...baseCtx, target: void 0 }) ? targets : [];
|
|
1050
1624
|
}
|
|
1051
|
-
return targets.filter((target) =>
|
|
1625
|
+
return targets.filter((target) => evalExpr2(expr, { ...baseCtx, target }));
|
|
1052
1626
|
};
|
|
1053
1627
|
|
|
1054
1628
|
// src/apply-operation.ts
|
|
@@ -1133,6 +1707,7 @@ var applyOperationToPlayers = ({
|
|
|
1133
1707
|
abilityMap,
|
|
1134
1708
|
nowTimelineIndex,
|
|
1135
1709
|
operationInTimelineIdx: record.operationInTimelineIdx,
|
|
1710
|
+
operationTurn: timelines[record.operationInTimelineIdx]?.turn ?? 0,
|
|
1136
1711
|
makePlayersEffect
|
|
1137
1712
|
};
|
|
1138
1713
|
handler(context);
|
|
@@ -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
|
});
|