@myclaw163/clawclaw-cli 0.6.56 → 0.6.57
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/README.md +440 -440
- package/bin/clawclaw-cli.mjs +3 -3
- package/package.json +48 -48
- package/personas//347/220/206/346/231/272/346/270/251/345/222/214.md +23 -23
- package/personas//350/200/201/350/260/213/346/267/261/347/256/227.md +22 -22
- package/personas//350/257/232/346/201/263/347/233/264/347/216/207.md +22 -22
- package/personas//350/275/273/346/235/276/346/264/273/346/263/274.md +22 -22
- package/personas//351/207/216/346/200/247/345/217/233/351/200/206.md +23 -23
- package/scripts/postinstall.mjs +20 -20
- package/scripts/sync-bundled-skill.mjs +245 -245
- package/scripts/sync-bundled-skill.test.mjs +152 -152
- package/skills/clawclaw/SKILL.md +244 -240
- package/skills/clawclaw/references/CHATTERBOX.md +142 -142
- package/skills/clawclaw/references/COMMANDS.md +132 -132
- package/skills/clawclaw/references/GAME-MECHANICS.md +186 -186
- package/skills/clawclaw/references/HUB.md +48 -48
- package/skills/clawclaw/references/KNOWLEDGE.md +43 -43
- package/skills/clawclaw/references/STRATEGIES.md +57 -57
- package/skills/clawclaw/references/STREAM.md +59 -58
- package/skills/clawclaw/references/TACTICS.md +65 -65
- package/src/assets/clawclaw-ascii-map.txt +40 -40
- package/src/cli.ts +110 -153
- package/src/commands/_schema.ts +109 -109
- package/src/commands/account.ts +209 -209
- package/src/commands/do.test.ts +37 -37
- package/src/commands/do.ts +95 -95
- package/src/commands/events.ts +22 -22
- package/src/commands/game-map.test.ts +28 -28
- package/src/commands/game-start-plan.test.ts +84 -142
- package/src/commands/game.ts +1027 -882
- package/src/commands/history-player.test.ts +102 -102
- package/src/commands/history.ts +573 -573
- package/src/commands/hub.test.ts +96 -96
- package/src/commands/hub.ts +234 -234
- package/src/commands/knowledge.test.ts +19 -19
- package/src/commands/knowledge.ts +168 -168
- package/src/commands/load.test.ts +51 -51
- package/src/commands/load.ts +13 -13
- package/src/commands/meeting-history.test.ts +106 -106
- package/src/commands/memory.ts +40 -40
- package/src/commands/peek.ts +45 -38
- package/src/commands/persona.ts +57 -57
- package/src/commands/skill.ts +128 -128
- package/src/commands/state.ts +46 -46
- package/src/commands/strategy.test.ts +135 -135
- package/src/commands/strategy.ts +180 -189
- package/src/commands/tts.ts +128 -128
- package/src/commands/upgrade.test.ts +82 -82
- package/src/commands/upgrade.ts +148 -148
- package/src/commands/watch.test.ts +969 -973
- package/src/commands/watch.ts +720 -709
- package/src/lib/auth.test.ts +59 -59
- package/src/lib/auth.ts +186 -186
- package/src/lib/command-meta.ts +37 -37
- package/src/lib/game-client.ts +391 -391
- package/src/lib/http-keepalive.ts +15 -15
- package/src/lib/http-transport.test.ts +42 -42
- package/src/lib/http-transport.ts +113 -113
- package/src/lib/hub-client.test.ts +56 -56
- package/src/lib/hub-client.ts +88 -88
- package/src/lib/hub-install.test.ts +98 -98
- package/src/lib/hub-install.ts +121 -121
- package/src/lib/hub-reminder.ts +75 -75
- package/src/lib/hub-unzip.test.ts +69 -69
- package/src/lib/hub-unzip.ts +62 -62
- package/src/lib/init-command.test.ts +75 -75
- package/src/lib/init-command.ts +120 -120
- package/src/lib/knowledge-store.test.ts +180 -180
- package/src/lib/knowledge-store.ts +374 -374
- package/src/lib/load-context.test.ts +52 -52
- package/src/lib/load-context.ts +52 -52
- package/src/lib/match-state.test.ts +134 -134
- package/src/lib/match-state.ts +94 -94
- package/src/lib/netease-tts.ts +83 -83
- package/src/lib/normalize.ts +42 -42
- package/src/lib/persona.test.ts +41 -41
- package/src/lib/persona.ts +72 -72
- package/src/lib/server-registry.ts +152 -152
- package/src/lib/skill-version.test.ts +48 -48
- package/src/lib/skill-version.ts +19 -19
- package/src/lib/strategy-export.test.ts +232 -232
- package/src/lib/strategy-export.ts +242 -242
- package/src/lib/tts-keys.ts +7 -7
- package/src/lib/tts-speech.test.ts +63 -63
- package/src/lib/tts-speech.ts +76 -76
- package/src/lib/workspace-argv.test.ts +49 -49
- package/src/lib/workspace-argv.ts +44 -44
- package/src/perception/player-history-store.test.ts +87 -87
- package/src/perception/player-history-store.ts +194 -194
- package/src/pipeline/event-store.ts +124 -124
- package/src/pipeline/pipeline.ts +35 -35
- package/src/runtime/auto-upgrade.test.ts +66 -66
- package/src/runtime/auto-upgrade.ts +31 -31
- package/src/runtime/event-daemon.test.ts +107 -28
- package/src/runtime/event-daemon.ts +409 -371
- package/src/runtime/owner-control.ts +150 -0
- package/src/runtime/raw-ws-log.test.ts +33 -33
- package/src/runtime/raw-ws-log.ts +32 -32
- package/src/runtime/runtime-logger.ts +107 -99
- package/src/runtime/ws-client.test.ts +104 -47
- package/src/runtime/ws-client.ts +272 -272
- package/src/sdk/action.ts +166 -166
- package/src/sdk/index.ts +110 -110
- package/src/sdk/types.ts +146 -146
- package/src/strategies/avoid-lone.ts +11 -11
- package/src/strategies/avoid-players.knowledge.md +20 -20
- package/src/strategies/avoid-players.ts +15 -15
- package/src/strategies/corpse-patrol.ts +22 -22
- package/src/strategies/crab-sabotage.ts +21 -21
- package/src/strategies/custom-module.test.ts +269 -269
- package/src/strategies/find-player.ts +16 -16
- package/src/strategies/game-utils.test.ts +164 -164
- package/src/strategies/game-utils.ts +737 -721
- package/src/strategies/goals/avoid-lone-top.ts +168 -168
- package/src/strategies/goals/avoid-players-top.test.ts +83 -83
- package/src/strategies/goals/avoid-players-top.ts +121 -121
- package/src/strategies/goals/conversation-goal.ts +51 -51
- package/src/strategies/goals/corpse-patrol-top.ts +91 -91
- package/src/strategies/goals/crab-octopus-reflexes.ts +93 -93
- package/src/strategies/goals/crab-sabotage-top.ts +197 -197
- package/src/strategies/goals/emergency-hunt-goal.ts +28 -28
- package/src/strategies/goals/find-player-top.ts +93 -93
- package/src/strategies/goals/flee-players-goal.ts +53 -53
- package/src/strategies/goals/goal-manager.ts +41 -41
- package/src/strategies/goals/goal-root-strategy.ts +49 -49
- package/src/strategies/goals/goal.ts +28 -28
- package/src/strategies/goals/keep-away-goal.ts +206 -206
- package/src/strategies/goals/kill-frenzy-top.ts +80 -80
- package/src/strategies/goals/kill-lone-top.ts +160 -160
- package/src/strategies/goals/kill-target-goal.ts +59 -59
- package/src/strategies/goals/kill-target-top.ts +109 -109
- package/src/strategies/goals/leaf-goal.ts +25 -25
- package/src/strategies/goals/linger-corpse-goal.ts +79 -79
- package/src/strategies/goals/lone-kill-core.ts +82 -82
- package/src/strategies/goals/lone-kill-goal.ts +24 -24
- package/src/strategies/goals/lone-kill-task-top.test.ts +85 -85
- package/src/strategies/goals/lone-kill-task-top.ts +86 -86
- package/src/strategies/goals/move-room-goal.ts +60 -60
- package/src/strategies/goals/normal-shrimp-top.test.ts +80 -80
- package/src/strategies/goals/normal-shrimp-top.ts +242 -242
- package/src/strategies/goals/paradise-fish-top.test.ts +126 -126
- package/src/strategies/goals/paradise-fish-top.ts +219 -219
- package/src/strategies/goals/patrol-top.ts +57 -57
- package/src/strategies/goals/report-patrol-top.ts +80 -80
- package/src/strategies/goals/safe-task-goal.ts +102 -102
- package/src/strategies/goals/social-task-top.ts +161 -161
- package/src/strategies/goals/task-kill-report-top.ts +163 -163
- package/src/strategies/goals/task-only-top.ts +57 -57
- package/src/strategies/goals/task-or-patrol-goal.ts +41 -41
- package/src/strategies/goals/task-report-top.ts +57 -57
- package/src/strategies/goals/wander-task-goal.ts +33 -33
- package/src/strategies/goals/warrior-shrimp-top.test.ts +86 -86
- package/src/strategies/goals/warrior-shrimp-top.ts +248 -248
- package/src/strategies/greeting.ts +53 -53
- package/src/strategies/kill-frenzy.ts +12 -12
- package/src/strategies/kill-lone.knowledge.md +20 -20
- package/src/strategies/kill-lone.ts +13 -13
- package/src/strategies/kill-target.ts +18 -18
- package/src/strategies/loader.test.ts +678 -678
- package/src/strategies/loader.ts +172 -172
- package/src/strategies/lone-kill-task.ts +21 -21
- package/src/strategies/meeting-gate.test.ts +59 -59
- package/src/strategies/meeting-gate.ts +23 -23
- package/src/strategies/move-room.ts +15 -15
- package/src/strategies/new-events-backfill.ts +98 -98
- package/src/strategies/paradise-fish.knowledge.md +20 -20
- package/src/strategies/paradise-fish.ts +25 -25
- package/src/strategies/pathfind/distance-field.ts +150 -150
- package/src/strategies/pathfind/escape-planner.test.ts +197 -197
- package/src/strategies/pathfind/escape-planner.ts +348 -348
- package/src/strategies/pathfind/walkable-grid.ts +117 -117
- package/src/strategies/patrol.ts +11 -11
- package/src/strategies/player-targets.ts +13 -13
- package/src/strategies/report-patrol.ts +11 -11
- package/src/strategies/shrimp-memory.knowledge.md +20 -20
- package/src/strategies/shrimp-memory.ts +25 -25
- package/src/strategies/social-task.test.ts +28 -28
- package/src/strategies/social-task.ts +49 -49
- package/src/strategies/spawn.ts +82 -71
- package/src/strategies/speech-module.ts +123 -123
- package/src/strategies/strategy-loop.ts +763 -757
- package/src/strategies/task-kill-report.ts +17 -17
- package/src/strategies/task-only.ts +11 -11
- package/src/strategies/task-report.ts +22 -22
- package/src/strategies/types.ts +96 -96
- package/src/strategies/warrior-memory.knowledge.md +20 -20
- package/src/strategies/warrior-memory.ts +16 -16
- package/src/runtime/daemon.ts +0 -100
- package/src/runtime/opening-mover.ts +0 -303
|
@@ -1,242 +1,242 @@
|
|
|
1
|
-
import type { GameState, PlayerInfo } from '../../sdk/types.js';
|
|
2
|
-
import {
|
|
3
|
-
corpseAtScene,
|
|
4
|
-
dist,
|
|
5
|
-
firstAvailableTask,
|
|
6
|
-
hasKnownCorpse,
|
|
7
|
-
isKnowledgeThreat,
|
|
8
|
-
isKnowledgeTrusted,
|
|
9
|
-
nonTeammatesVisible,
|
|
10
|
-
PROGRESS_INTERVAL_MS,
|
|
11
|
-
reportCorpseDecision,
|
|
12
|
-
SHRIMP_VISION_RANGE,
|
|
13
|
-
SHRIMP_VISION_RELEASE_RANGE,
|
|
14
|
-
taskMoveDecision,
|
|
15
|
-
} from '../game-utils.js';
|
|
16
|
-
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
17
|
-
import { GreetingTracker } from '../greeting.js';
|
|
18
|
-
import { Goal } from './goal.js';
|
|
19
|
-
import { KeepAwayGoal, type KeepAwayGoalOptions, type ThreatResolver } from './keep-away-goal.js';
|
|
20
|
-
import { SafeTaskOrPatrolGoal } from './safe-task-goal.js';
|
|
21
|
-
|
|
22
|
-
const SUB_PRIORITY = 0.5;
|
|
23
|
-
/** 陌生人贴近到这个距离才触发拉开(B 档入口)。 */
|
|
24
|
-
const STRANGER_TRIGGER_RADIUS = SHRIMP_VISION_RANGE;
|
|
25
|
-
/** B 档 KeepAwayGoal 的交战兼结束半径:270 进 / 300 出,天然滞回。 */
|
|
26
|
-
const STRANGER_RELEASE_RADIUS = SHRIMP_VISION_RELEASE_RANGE;
|
|
27
|
-
/** 目击者记忆 TTL;0 表示只按当前轮可见玩家判断。 */
|
|
28
|
-
const WITNESS_MEMORY_MS = 0;
|
|
29
|
-
/** 近期见过这么多个陌生人即豁免 B 档——蟹不敢当众动手,独处单个陌生人才危险。 */
|
|
30
|
-
const WITNESS_EXEMPT_COUNT = 2;
|
|
31
|
-
const TASK_RETURN_INERTIA_MS = 2_000;
|
|
32
|
-
const FLEE_BAD_KEY = 'shrimp-flee-bad';
|
|
33
|
-
const STRANGER_KEY = 'shrimp-stranger-distance';
|
|
34
|
-
|
|
35
|
-
type Tier = 'flee-bad' | 'keep-distance' | 'task';
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 普通虾·记忆进阶版(好人、不带刀)的核心 Goal,按行为表逐条判断,命中即执行:
|
|
39
|
-
*
|
|
40
|
-
* - P0 发现尸体:一律报警(reportCorpseDecision),必要时提示 Agent 更新尸体旁玩家记忆。
|
|
41
|
-
* - P1 规避危险:
|
|
42
|
-
* · A 视野里有威胁(知识库 suspect/hostile)→ 寻路躲避(KeepAwayGoal,不限距离)。
|
|
43
|
-
* · B 无可信同伴且陌生人贴近(≤270px)→ 先拉开距离(KeepAwayGoal,拉开到 300px 即回任务)。
|
|
44
|
-
* 目击者豁免:当前见到 ≥2 个陌生人时不触发后撤(蟹不敢当众动手,独处才危险);
|
|
45
|
-
* 进行中的后撤也会重新判定,只有仍是单陌生人危险时才拉满到 300px。豁免期间陌生人也不算任务威胁点。
|
|
46
|
-
* - P2 紧急任务:无危险时优先处理。
|
|
47
|
-
* - P3 否则做任务 / 巡逻(SafeTaskOrPatrolGoal:测地最近、威胁旁/途经威胁的任务硬排除、带粘性)。
|
|
48
|
-
*
|
|
49
|
-
* 「认定坏人」「可信同伴」全部来自 ctx.knowledge(见 shrimp-memory.knowledge.md)。
|
|
50
|
-
* 队友由游戏事实判定(nonTeammatesVisible)。有可信同伴在场时不对陌生人触发 B 档。
|
|
51
|
-
*/
|
|
52
|
-
export class NormalShrimpTop extends Goal {
|
|
53
|
-
private readonly taskGoal = new SafeTaskOrPatrolGoal(
|
|
54
|
-
// 豁免期间陌生人不算威胁点(与 B 档不触发保持视野半径对齐),认定坏人始终算。
|
|
55
|
-
(s, c) => nonTeammatesVisible(s, c)
|
|
56
|
-
.filter(p => !this.isTrusted(p, c))
|
|
57
|
-
.filter(p => this.isThreat(p, c) || !this.witnessExempt)
|
|
58
|
-
.map(p => ({ x: p.x, y: p.y })),
|
|
59
|
-
);
|
|
60
|
-
private readonly strangerSeenAt = new Map<string, number>();
|
|
61
|
-
private witnessExempt = false;
|
|
62
|
-
private readonly corpseWitnessNotified = new Set<string>();
|
|
63
|
-
private readonly greeting: GreetingTracker;
|
|
64
|
-
|
|
65
|
-
constructor(greetingPhrases: string[] = []) {
|
|
66
|
-
super();
|
|
67
|
-
this.greeting = new GreetingTracker(greetingPhrases);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
71
|
-
const visible = nonTeammatesVisible(state, ctx);
|
|
72
|
-
this.updateWitnessMemory(visible, ctx);
|
|
73
|
-
const decisions: BehaviorDecision[] = [];
|
|
74
|
-
const greeting = this.greeting.tryGreeting(state);
|
|
75
|
-
if (greeting) decisions.push(greeting);
|
|
76
|
-
|
|
77
|
-
// ── P0 发现尸体 → 报警 ──
|
|
78
|
-
if (hasKnownCorpse(ctx)) {
|
|
79
|
-
// 「尸体旁目击者」只在此刻确实站在命案现场(当帧视野有尸体)时才标注——否则会把后来路上
|
|
80
|
-
// 遇见的人误报成目击者。报尸本身可以追着记忆里已离开视野的尸体走回去。
|
|
81
|
-
if (corpseAtScene(state)) this.notifyCorpseWitnesses(visible, ctx);
|
|
82
|
-
else this.corpseWitnessNotified.clear();
|
|
83
|
-
const report = reportCorpseDecision(state, ctx, { respectBlock: true });
|
|
84
|
-
if (report) {
|
|
85
|
-
this.clearSub();
|
|
86
|
-
return [report, ...decisions];
|
|
87
|
-
}
|
|
88
|
-
// 报告短暂受阻时继续执行后续分支,避免在尸体旁空站。
|
|
89
|
-
} else {
|
|
90
|
-
this.corpseWitnessNotified.clear();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ── P1-A 视野里有认定坏人 → 寻路躲避 ──
|
|
94
|
-
const threats = visible.filter(p => this.isThreat(p, ctx));
|
|
95
|
-
if (threats.length > 0) {
|
|
96
|
-
this.taskGoal.planTask(state, ctx, { holdUnsafeCurrentForMs: TASK_RETURN_INERTIA_MS });
|
|
97
|
-
this.setKeepAway(FLEE_BAD_KEY, (s, c) => nonTeammatesVisible(s, c).filter(p => this.isThreat(p, c)), {
|
|
98
|
-
threatRadius: Infinity,
|
|
99
|
-
noun: '认定坏人',
|
|
100
|
-
});
|
|
101
|
-
this.emitProgress(state, ctx, visible, 'flee-bad');
|
|
102
|
-
return decisions;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ── P1-B 无可信同伴且陌生人贴近 → 拉开距离再做任务 ──
|
|
106
|
-
const trustedVisible = visible.some(p => this.isTrusted(p, ctx));
|
|
107
|
-
if (!trustedVisible) {
|
|
108
|
-
const you = state.you;
|
|
109
|
-
const strangerClose = visible.some(p =>
|
|
110
|
-
!this.isTrusted(p, ctx) && (p.distance ?? dist(p.x, p.y, you.x, you.y)) <= STRANGER_TRIGGER_RADIUS);
|
|
111
|
-
const worker = this.subGoal;
|
|
112
|
-
const stillBackingOff = worker instanceof KeepAwayGoal && worker.key === STRANGER_KEY && !worker.isFinish(state, ctx);
|
|
113
|
-
// 第二个陌生人加入时重新判定,豁免生效后不继续旧的后撤。
|
|
114
|
-
if (!this.witnessExempt && (strangerClose || stillBackingOff)) {
|
|
115
|
-
this.taskGoal.planTask(state, ctx, { holdUnsafeCurrentForMs: TASK_RETURN_INERTIA_MS });
|
|
116
|
-
this.setKeepAway(STRANGER_KEY, (s, c) => nonTeammatesVisible(s, c).filter(p => !this.isTrusted(p, c)), {
|
|
117
|
-
threatRadius: STRANGER_RELEASE_RADIUS,
|
|
118
|
-
noun: '陌生人',
|
|
119
|
-
planOpts: { steps: 4 },
|
|
120
|
-
});
|
|
121
|
-
this.emitProgress(state, ctx, visible, 'keep-distance');
|
|
122
|
-
return decisions;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// ── P2 紧急任务 ──
|
|
127
|
-
const emergency = firstAvailableTask([], t => t.faction !== 'crab', ctx.emergency, ctx.blockedMoveTarget);
|
|
128
|
-
if (emergency) {
|
|
129
|
-
this.clearSub();
|
|
130
|
-
return [taskMoveDecision(state, ctx, emergency), ...decisions];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ── P3 兜底 → 做任务 / 巡逻 ──
|
|
134
|
-
this.clearSub();
|
|
135
|
-
const work = this.taskGoal.tick(state, ctx);
|
|
136
|
-
this.emitProgress(state, ctx, visible, 'task');
|
|
137
|
-
return [...work, ...decisions];
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* 目击者豁免:用 TTL 记忆平滑陌生人计数;当前设为 0,只看本轮可见玩家。
|
|
142
|
-
*/
|
|
143
|
-
private updateWitnessMemory(visible: PlayerInfo[], ctx: StrategyContext): void {
|
|
144
|
-
const now = Date.now();
|
|
145
|
-
for (const p of visible) {
|
|
146
|
-
if (this.isTrusted(p, ctx)) continue;
|
|
147
|
-
const key = p.name || String(p.seat ?? '');
|
|
148
|
-
if (key) this.strangerSeenAt.set(key, now);
|
|
149
|
-
}
|
|
150
|
-
for (const [key, at] of this.strangerSeenAt) {
|
|
151
|
-
if (now - at > WITNESS_MEMORY_MS) this.strangerSeenAt.delete(key);
|
|
152
|
-
}
|
|
153
|
-
this.witnessExempt = this.strangerSeenAt.size >= WITNESS_EXEMPT_COUNT;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** 威胁:知识库 suspect/hostile,且未被标记为 trusted。 */
|
|
157
|
-
private isThreat(p: PlayerInfo, ctx: StrategyContext): boolean {
|
|
158
|
-
return isKnowledgeThreat(p, ctx) && !this.isTrusted(p, ctx);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** 可信同伴:知识库 trusted。 */
|
|
162
|
-
private isTrusted(p: PlayerInfo, ctx: StrategyContext): boolean {
|
|
163
|
-
return isKnowledgeTrusted(p, ctx);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
private setKeepAway(key: string, resolve: ThreatResolver, options: KeepAwayGoalOptions): void {
|
|
167
|
-
const worker = this.subGoal;
|
|
168
|
-
if (worker instanceof KeepAwayGoal && worker.key === key) return;
|
|
169
|
-
if (this.subGoal) this.removeSubGoal();
|
|
170
|
-
this.setSubGoal(new KeepAwayGoal(key, resolve, options), SUB_PRIORITY);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private clearSub(): void {
|
|
174
|
-
if (this.subGoal) this.removeSubGoal();
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
private notifyCorpseWitnesses(visible: PlayerInfo[], ctx: StrategyContext): void {
|
|
178
|
-
const bad: string[] = [];
|
|
179
|
-
const paradise: string[] = [];
|
|
180
|
-
const unknown: string[] = [];
|
|
181
|
-
const trusted: string[] = [];
|
|
182
|
-
|
|
183
|
-
for (const player of visible) {
|
|
184
|
-
const key = player.name || String(player.seat ?? '');
|
|
185
|
-
if (!key || this.corpseWitnessNotified.has(key)) continue;
|
|
186
|
-
this.corpseWitnessNotified.add(key);
|
|
187
|
-
|
|
188
|
-
if (this.isThreat(player, ctx)) bad.push(player.name);
|
|
189
|
-
else if (this.isKnownParadiseFish(player, ctx)) paradise.push(player.name);
|
|
190
|
-
else if (this.isTrusted(player, ctx)) trusted.push(player.name);
|
|
191
|
-
else unknown.push(player.name);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (bad.length > 0) ctx.notifications.push(`尸体旁发现认定坏人${bad.join('、')},直接报警。`);
|
|
195
|
-
if (paradise.length > 0) ctx.notifications.push(`尸体旁发现天堂鱼${paradise.join('、')},直接报警。`);
|
|
196
|
-
if (unknown.length > 0) ctx.notifications.push(`尸体旁发现身份不明者${unknown.join('、')},直接报警;建议将其记忆为坏人。`);
|
|
197
|
-
if (trusted.length > 0) ctx.notifications.push(`尸体旁发现可信同伴${trusted.join('、')},仍直接报警;建议重新怀疑并复核记忆。`);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
private isKnownParadiseFish(p: PlayerInfo, ctx: StrategyContext): boolean {
|
|
201
|
-
const role = ctx.knowledge?.roleOf(p);
|
|
202
|
-
return role === 'neutral_paradise_fish'
|
|
203
|
-
|| role === 'paradise_fish'
|
|
204
|
-
|| role === '天堂鱼'
|
|
205
|
-
|| (ctx.knowledge?.hasTag(p, 'paradise_fish') ?? false);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
private emitProgress(state: GameState, ctx: StrategyContext, visible: PlayerInfo[], tier: Tier): void {
|
|
209
|
-
const now = Date.now();
|
|
210
|
-
if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
|
|
211
|
-
ctx.lastProgressNotifyAt = now;
|
|
212
|
-
|
|
213
|
-
const room = state.you.room ?? '未知';
|
|
214
|
-
const completed = ctx.taskData.filter(t => t.status === 'completed').length;
|
|
215
|
-
const total = ctx.taskData.filter(t => t.faction !== 'crab').length;
|
|
216
|
-
const current = this.taskGoal.currentTask;
|
|
217
|
-
|
|
218
|
-
let msg = `[进度] 当前在${room}`;
|
|
219
|
-
if (tier === 'flee-bad') {
|
|
220
|
-
const names = visible.filter(p => this.isThreat(p, ctx)).map(p => p.name).join('、');
|
|
221
|
-
msg += `,发现认定坏人${names},正在远离。`;
|
|
222
|
-
} else if (tier === 'keep-distance') {
|
|
223
|
-
msg += `,陌生人靠得太近,先拉开距离再做任务。`;
|
|
224
|
-
} else if (visible.length === 0) {
|
|
225
|
-
msg += ',附近无人,做任务/巡逻。';
|
|
226
|
-
} else if (visible.some(p => this.isTrusted(p, ctx))) {
|
|
227
|
-
msg += `,附近有可信同伴,结伴做任务。`;
|
|
228
|
-
} else if (this.witnessExempt && visible.length >= 2) {
|
|
229
|
-
msg += `,附近${visible.length}个陌生人互为目击者,照常做任务。`;
|
|
230
|
-
} else {
|
|
231
|
-
msg += `,附近有${visible.length}人,继续做任务/巡逻。`;
|
|
232
|
-
}
|
|
233
|
-
if (current) {
|
|
234
|
-
msg += ` 当前任务目标:${current.room}「${current.task_name}」。`;
|
|
235
|
-
}
|
|
236
|
-
msg += ` 已完成 ${completed}/${total} 个任务。`;
|
|
237
|
-
if (state.emergency) {
|
|
238
|
-
msg += ` 紧急任务进行中!`;
|
|
239
|
-
}
|
|
240
|
-
ctx.notifications.push(msg);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
1
|
+
import type { GameState, PlayerInfo } from '../../sdk/types.js';
|
|
2
|
+
import {
|
|
3
|
+
corpseAtScene,
|
|
4
|
+
dist,
|
|
5
|
+
firstAvailableTask,
|
|
6
|
+
hasKnownCorpse,
|
|
7
|
+
isKnowledgeThreat,
|
|
8
|
+
isKnowledgeTrusted,
|
|
9
|
+
nonTeammatesVisible,
|
|
10
|
+
PROGRESS_INTERVAL_MS,
|
|
11
|
+
reportCorpseDecision,
|
|
12
|
+
SHRIMP_VISION_RANGE,
|
|
13
|
+
SHRIMP_VISION_RELEASE_RANGE,
|
|
14
|
+
taskMoveDecision,
|
|
15
|
+
} from '../game-utils.js';
|
|
16
|
+
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
17
|
+
import { GreetingTracker } from '../greeting.js';
|
|
18
|
+
import { Goal } from './goal.js';
|
|
19
|
+
import { KeepAwayGoal, type KeepAwayGoalOptions, type ThreatResolver } from './keep-away-goal.js';
|
|
20
|
+
import { SafeTaskOrPatrolGoal } from './safe-task-goal.js';
|
|
21
|
+
|
|
22
|
+
const SUB_PRIORITY = 0.5;
|
|
23
|
+
/** 陌生人贴近到这个距离才触发拉开(B 档入口)。 */
|
|
24
|
+
const STRANGER_TRIGGER_RADIUS = SHRIMP_VISION_RANGE;
|
|
25
|
+
/** B 档 KeepAwayGoal 的交战兼结束半径:270 进 / 300 出,天然滞回。 */
|
|
26
|
+
const STRANGER_RELEASE_RADIUS = SHRIMP_VISION_RELEASE_RANGE;
|
|
27
|
+
/** 目击者记忆 TTL;0 表示只按当前轮可见玩家判断。 */
|
|
28
|
+
const WITNESS_MEMORY_MS = 0;
|
|
29
|
+
/** 近期见过这么多个陌生人即豁免 B 档——蟹不敢当众动手,独处单个陌生人才危险。 */
|
|
30
|
+
const WITNESS_EXEMPT_COUNT = 2;
|
|
31
|
+
const TASK_RETURN_INERTIA_MS = 2_000;
|
|
32
|
+
const FLEE_BAD_KEY = 'shrimp-flee-bad';
|
|
33
|
+
const STRANGER_KEY = 'shrimp-stranger-distance';
|
|
34
|
+
|
|
35
|
+
type Tier = 'flee-bad' | 'keep-distance' | 'task';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 普通虾·记忆进阶版(好人、不带刀)的核心 Goal,按行为表逐条判断,命中即执行:
|
|
39
|
+
*
|
|
40
|
+
* - P0 发现尸体:一律报警(reportCorpseDecision),必要时提示 Agent 更新尸体旁玩家记忆。
|
|
41
|
+
* - P1 规避危险:
|
|
42
|
+
* · A 视野里有威胁(知识库 suspect/hostile)→ 寻路躲避(KeepAwayGoal,不限距离)。
|
|
43
|
+
* · B 无可信同伴且陌生人贴近(≤270px)→ 先拉开距离(KeepAwayGoal,拉开到 300px 即回任务)。
|
|
44
|
+
* 目击者豁免:当前见到 ≥2 个陌生人时不触发后撤(蟹不敢当众动手,独处才危险);
|
|
45
|
+
* 进行中的后撤也会重新判定,只有仍是单陌生人危险时才拉满到 300px。豁免期间陌生人也不算任务威胁点。
|
|
46
|
+
* - P2 紧急任务:无危险时优先处理。
|
|
47
|
+
* - P3 否则做任务 / 巡逻(SafeTaskOrPatrolGoal:测地最近、威胁旁/途经威胁的任务硬排除、带粘性)。
|
|
48
|
+
*
|
|
49
|
+
* 「认定坏人」「可信同伴」全部来自 ctx.knowledge(见 shrimp-memory.knowledge.md)。
|
|
50
|
+
* 队友由游戏事实判定(nonTeammatesVisible)。有可信同伴在场时不对陌生人触发 B 档。
|
|
51
|
+
*/
|
|
52
|
+
export class NormalShrimpTop extends Goal {
|
|
53
|
+
private readonly taskGoal = new SafeTaskOrPatrolGoal(
|
|
54
|
+
// 豁免期间陌生人不算威胁点(与 B 档不触发保持视野半径对齐),认定坏人始终算。
|
|
55
|
+
(s, c) => nonTeammatesVisible(s, c)
|
|
56
|
+
.filter(p => !this.isTrusted(p, c))
|
|
57
|
+
.filter(p => this.isThreat(p, c) || !this.witnessExempt)
|
|
58
|
+
.map(p => ({ x: p.x, y: p.y })),
|
|
59
|
+
);
|
|
60
|
+
private readonly strangerSeenAt = new Map<string, number>();
|
|
61
|
+
private witnessExempt = false;
|
|
62
|
+
private readonly corpseWitnessNotified = new Set<string>();
|
|
63
|
+
private readonly greeting: GreetingTracker;
|
|
64
|
+
|
|
65
|
+
constructor(greetingPhrases: string[] = []) {
|
|
66
|
+
super();
|
|
67
|
+
this.greeting = new GreetingTracker(greetingPhrases);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
71
|
+
const visible = nonTeammatesVisible(state, ctx);
|
|
72
|
+
this.updateWitnessMemory(visible, ctx);
|
|
73
|
+
const decisions: BehaviorDecision[] = [];
|
|
74
|
+
const greeting = this.greeting.tryGreeting(state);
|
|
75
|
+
if (greeting) decisions.push(greeting);
|
|
76
|
+
|
|
77
|
+
// ── P0 发现尸体 → 报警 ──
|
|
78
|
+
if (hasKnownCorpse(ctx)) {
|
|
79
|
+
// 「尸体旁目击者」只在此刻确实站在命案现场(当帧视野有尸体)时才标注——否则会把后来路上
|
|
80
|
+
// 遇见的人误报成目击者。报尸本身可以追着记忆里已离开视野的尸体走回去。
|
|
81
|
+
if (corpseAtScene(state)) this.notifyCorpseWitnesses(visible, ctx);
|
|
82
|
+
else this.corpseWitnessNotified.clear();
|
|
83
|
+
const report = reportCorpseDecision(state, ctx, { respectBlock: true });
|
|
84
|
+
if (report) {
|
|
85
|
+
this.clearSub();
|
|
86
|
+
return [report, ...decisions];
|
|
87
|
+
}
|
|
88
|
+
// 报告短暂受阻时继续执行后续分支,避免在尸体旁空站。
|
|
89
|
+
} else {
|
|
90
|
+
this.corpseWitnessNotified.clear();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── P1-A 视野里有认定坏人 → 寻路躲避 ──
|
|
94
|
+
const threats = visible.filter(p => this.isThreat(p, ctx));
|
|
95
|
+
if (threats.length > 0) {
|
|
96
|
+
this.taskGoal.planTask(state, ctx, { holdUnsafeCurrentForMs: TASK_RETURN_INERTIA_MS });
|
|
97
|
+
this.setKeepAway(FLEE_BAD_KEY, (s, c) => nonTeammatesVisible(s, c).filter(p => this.isThreat(p, c)), {
|
|
98
|
+
threatRadius: Infinity,
|
|
99
|
+
noun: '认定坏人',
|
|
100
|
+
});
|
|
101
|
+
this.emitProgress(state, ctx, visible, 'flee-bad');
|
|
102
|
+
return decisions;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── P1-B 无可信同伴且陌生人贴近 → 拉开距离再做任务 ──
|
|
106
|
+
const trustedVisible = visible.some(p => this.isTrusted(p, ctx));
|
|
107
|
+
if (!trustedVisible) {
|
|
108
|
+
const you = state.you;
|
|
109
|
+
const strangerClose = visible.some(p =>
|
|
110
|
+
!this.isTrusted(p, ctx) && (p.distance ?? dist(p.x, p.y, you.x, you.y)) <= STRANGER_TRIGGER_RADIUS);
|
|
111
|
+
const worker = this.subGoal;
|
|
112
|
+
const stillBackingOff = worker instanceof KeepAwayGoal && worker.key === STRANGER_KEY && !worker.isFinish(state, ctx);
|
|
113
|
+
// 第二个陌生人加入时重新判定,豁免生效后不继续旧的后撤。
|
|
114
|
+
if (!this.witnessExempt && (strangerClose || stillBackingOff)) {
|
|
115
|
+
this.taskGoal.planTask(state, ctx, { holdUnsafeCurrentForMs: TASK_RETURN_INERTIA_MS });
|
|
116
|
+
this.setKeepAway(STRANGER_KEY, (s, c) => nonTeammatesVisible(s, c).filter(p => !this.isTrusted(p, c)), {
|
|
117
|
+
threatRadius: STRANGER_RELEASE_RADIUS,
|
|
118
|
+
noun: '陌生人',
|
|
119
|
+
planOpts: { steps: 4 },
|
|
120
|
+
});
|
|
121
|
+
this.emitProgress(state, ctx, visible, 'keep-distance');
|
|
122
|
+
return decisions;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── P2 紧急任务 ──
|
|
127
|
+
const emergency = firstAvailableTask([], t => t.faction !== 'crab', ctx.emergency, ctx.blockedMoveTarget);
|
|
128
|
+
if (emergency) {
|
|
129
|
+
this.clearSub();
|
|
130
|
+
return [taskMoveDecision(state, ctx, emergency), ...decisions];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── P3 兜底 → 做任务 / 巡逻 ──
|
|
134
|
+
this.clearSub();
|
|
135
|
+
const work = this.taskGoal.tick(state, ctx);
|
|
136
|
+
this.emitProgress(state, ctx, visible, 'task');
|
|
137
|
+
return [...work, ...decisions];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 目击者豁免:用 TTL 记忆平滑陌生人计数;当前设为 0,只看本轮可见玩家。
|
|
142
|
+
*/
|
|
143
|
+
private updateWitnessMemory(visible: PlayerInfo[], ctx: StrategyContext): void {
|
|
144
|
+
const now = Date.now();
|
|
145
|
+
for (const p of visible) {
|
|
146
|
+
if (this.isTrusted(p, ctx)) continue;
|
|
147
|
+
const key = p.name || String(p.seat ?? '');
|
|
148
|
+
if (key) this.strangerSeenAt.set(key, now);
|
|
149
|
+
}
|
|
150
|
+
for (const [key, at] of this.strangerSeenAt) {
|
|
151
|
+
if (now - at > WITNESS_MEMORY_MS) this.strangerSeenAt.delete(key);
|
|
152
|
+
}
|
|
153
|
+
this.witnessExempt = this.strangerSeenAt.size >= WITNESS_EXEMPT_COUNT;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** 威胁:知识库 suspect/hostile,且未被标记为 trusted。 */
|
|
157
|
+
private isThreat(p: PlayerInfo, ctx: StrategyContext): boolean {
|
|
158
|
+
return isKnowledgeThreat(p, ctx) && !this.isTrusted(p, ctx);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** 可信同伴:知识库 trusted。 */
|
|
162
|
+
private isTrusted(p: PlayerInfo, ctx: StrategyContext): boolean {
|
|
163
|
+
return isKnowledgeTrusted(p, ctx);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private setKeepAway(key: string, resolve: ThreatResolver, options: KeepAwayGoalOptions): void {
|
|
167
|
+
const worker = this.subGoal;
|
|
168
|
+
if (worker instanceof KeepAwayGoal && worker.key === key) return;
|
|
169
|
+
if (this.subGoal) this.removeSubGoal();
|
|
170
|
+
this.setSubGoal(new KeepAwayGoal(key, resolve, options), SUB_PRIORITY);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private clearSub(): void {
|
|
174
|
+
if (this.subGoal) this.removeSubGoal();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private notifyCorpseWitnesses(visible: PlayerInfo[], ctx: StrategyContext): void {
|
|
178
|
+
const bad: string[] = [];
|
|
179
|
+
const paradise: string[] = [];
|
|
180
|
+
const unknown: string[] = [];
|
|
181
|
+
const trusted: string[] = [];
|
|
182
|
+
|
|
183
|
+
for (const player of visible) {
|
|
184
|
+
const key = player.name || String(player.seat ?? '');
|
|
185
|
+
if (!key || this.corpseWitnessNotified.has(key)) continue;
|
|
186
|
+
this.corpseWitnessNotified.add(key);
|
|
187
|
+
|
|
188
|
+
if (this.isThreat(player, ctx)) bad.push(player.name);
|
|
189
|
+
else if (this.isKnownParadiseFish(player, ctx)) paradise.push(player.name);
|
|
190
|
+
else if (this.isTrusted(player, ctx)) trusted.push(player.name);
|
|
191
|
+
else unknown.push(player.name);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (bad.length > 0) ctx.notifications.push(`尸体旁发现认定坏人${bad.join('、')},直接报警。`);
|
|
195
|
+
if (paradise.length > 0) ctx.notifications.push(`尸体旁发现天堂鱼${paradise.join('、')},直接报警。`);
|
|
196
|
+
if (unknown.length > 0) ctx.notifications.push(`尸体旁发现身份不明者${unknown.join('、')},直接报警;建议将其记忆为坏人。`);
|
|
197
|
+
if (trusted.length > 0) ctx.notifications.push(`尸体旁发现可信同伴${trusted.join('、')},仍直接报警;建议重新怀疑并复核记忆。`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private isKnownParadiseFish(p: PlayerInfo, ctx: StrategyContext): boolean {
|
|
201
|
+
const role = ctx.knowledge?.roleOf(p);
|
|
202
|
+
return role === 'neutral_paradise_fish'
|
|
203
|
+
|| role === 'paradise_fish'
|
|
204
|
+
|| role === '天堂鱼'
|
|
205
|
+
|| (ctx.knowledge?.hasTag(p, 'paradise_fish') ?? false);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private emitProgress(state: GameState, ctx: StrategyContext, visible: PlayerInfo[], tier: Tier): void {
|
|
209
|
+
const now = Date.now();
|
|
210
|
+
if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
|
|
211
|
+
ctx.lastProgressNotifyAt = now;
|
|
212
|
+
|
|
213
|
+
const room = state.you.room ?? '未知';
|
|
214
|
+
const completed = ctx.taskData.filter(t => t.status === 'completed').length;
|
|
215
|
+
const total = ctx.taskData.filter(t => t.faction !== 'crab').length;
|
|
216
|
+
const current = this.taskGoal.currentTask;
|
|
217
|
+
|
|
218
|
+
let msg = `[进度] 当前在${room}`;
|
|
219
|
+
if (tier === 'flee-bad') {
|
|
220
|
+
const names = visible.filter(p => this.isThreat(p, ctx)).map(p => p.name).join('、');
|
|
221
|
+
msg += `,发现认定坏人${names},正在远离。`;
|
|
222
|
+
} else if (tier === 'keep-distance') {
|
|
223
|
+
msg += `,陌生人靠得太近,先拉开距离再做任务。`;
|
|
224
|
+
} else if (visible.length === 0) {
|
|
225
|
+
msg += ',附近无人,做任务/巡逻。';
|
|
226
|
+
} else if (visible.some(p => this.isTrusted(p, ctx))) {
|
|
227
|
+
msg += `,附近有可信同伴,结伴做任务。`;
|
|
228
|
+
} else if (this.witnessExempt && visible.length >= 2) {
|
|
229
|
+
msg += `,附近${visible.length}个陌生人互为目击者,照常做任务。`;
|
|
230
|
+
} else {
|
|
231
|
+
msg += `,附近有${visible.length}人,继续做任务/巡逻。`;
|
|
232
|
+
}
|
|
233
|
+
if (current) {
|
|
234
|
+
msg += ` 当前任务目标:${current.room}「${current.task_name}」。`;
|
|
235
|
+
}
|
|
236
|
+
msg += ` 已完成 ${completed}/${total} 个任务。`;
|
|
237
|
+
if (state.emergency) {
|
|
238
|
+
msg += ` 紧急任务进行中!`;
|
|
239
|
+
}
|
|
240
|
+
ctx.notifications.push(msg);
|
|
241
|
+
}
|
|
242
|
+
}
|