@myclaw163/clawclaw-cli 0.6.66 → 0.6.68

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.
Files changed (206) hide show
  1. package/README.md +427 -427
  2. package/package.json +48 -48
  3. package/personas//347/220/206/346/231/272/346/270/251/345/222/214.md +23 -23
  4. package/personas//350/200/201/350/260/213/346/267/261/347/256/227.md +22 -22
  5. package/personas//350/257/232/346/201/263/347/233/264/347/216/207.md +22 -22
  6. package/personas//350/275/273/346/235/276/346/264/273/346/263/274.md +22 -22
  7. package/personas//351/207/216/346/200/247/345/217/233/351/200/206.md +23 -23
  8. package/scripts/find-hide-spots.py +157 -157
  9. package/scripts/postinstall.mjs +20 -20
  10. package/scripts/sync-bundled-skill.mjs +244 -244
  11. package/scripts/sync-bundled-skill.test.mjs +152 -152
  12. package/skills/clawclaw/SKILL.md +244 -245
  13. package/skills/clawclaw/references/CHATTERBOX.md +142 -142
  14. package/skills/clawclaw/references/COMMANDS.md +148 -148
  15. package/skills/clawclaw/references/GAME-MECHANICS.md +188 -188
  16. package/skills/clawclaw/references/HUB.md +48 -48
  17. package/skills/clawclaw/references/KNOWLEDGE.md +45 -43
  18. package/skills/clawclaw/references/STRATEGIES.md +57 -57
  19. package/skills/clawclaw/references/STREAM.md +91 -92
  20. package/skills/clawclaw/references/TACTICS.md +65 -65
  21. package/src/assets/clawclaw-ascii-map.txt +40 -40
  22. package/src/cli.ts +110 -110
  23. package/src/commands/_schema.ts +109 -109
  24. package/src/commands/account.ts +209 -209
  25. package/src/commands/config.ts +30 -30
  26. package/src/commands/do.test.ts +73 -73
  27. package/src/commands/do.ts +126 -126
  28. package/src/commands/events.test.ts +71 -71
  29. package/src/commands/events.ts +155 -155
  30. package/src/commands/game-map.test.ts +28 -28
  31. package/src/commands/game-start-plan.test.ts +84 -84
  32. package/src/commands/game.ts +1027 -1027
  33. package/src/commands/history-player.test.ts +102 -102
  34. package/src/commands/history.ts +573 -573
  35. package/src/commands/hub.test.ts +96 -96
  36. package/src/commands/hub.ts +234 -234
  37. package/src/commands/knowledge.test.ts +13 -19
  38. package/src/commands/knowledge.ts +139 -168
  39. package/src/commands/load.test.ts +51 -51
  40. package/src/commands/load.ts +13 -13
  41. package/src/commands/meeting-history.test.ts +106 -106
  42. package/src/commands/memory.ts +40 -40
  43. package/src/commands/peek.ts +45 -45
  44. package/src/commands/persona.ts +57 -57
  45. package/src/commands/setup/codex.ts +266 -248
  46. package/src/commands/setup/hermes.test.ts +96 -96
  47. package/src/commands/setup/hermes.ts +76 -76
  48. package/src/commands/setup/index.ts +13 -13
  49. package/src/commands/setup/openclaw.test.ts +114 -114
  50. package/src/commands/setup/openclaw.ts +147 -147
  51. package/src/commands/skill.ts +128 -128
  52. package/src/commands/state.ts +46 -46
  53. package/src/commands/strategy.test.ts +135 -135
  54. package/src/commands/strategy.ts +180 -180
  55. package/src/commands/tts.ts +128 -128
  56. package/src/commands/upgrade.test.ts +82 -82
  57. package/src/commands/upgrade.ts +148 -148
  58. package/src/commands/watch.test.ts +966 -966
  59. package/src/commands/watch.ts +659 -659
  60. package/src/lib/auth.test.ts +59 -59
  61. package/src/lib/auth.ts +186 -186
  62. package/src/lib/command-meta.ts +37 -37
  63. package/src/lib/game-client.ts +391 -391
  64. package/src/lib/host-config-patcher.test.ts +130 -130
  65. package/src/lib/host-config-patcher.ts +151 -151
  66. package/src/lib/http-keepalive.ts +15 -15
  67. package/src/lib/http-transport.test.ts +42 -42
  68. package/src/lib/http-transport.ts +113 -113
  69. package/src/lib/hub-client.test.ts +56 -56
  70. package/src/lib/hub-client.ts +88 -88
  71. package/src/lib/hub-install.test.ts +98 -98
  72. package/src/lib/hub-install.ts +121 -121
  73. package/src/lib/hub-reminder.ts +56 -56
  74. package/src/lib/hub-unzip.test.ts +69 -69
  75. package/src/lib/hub-unzip.ts +62 -62
  76. package/src/lib/init-command.test.ts +75 -75
  77. package/src/lib/init-command.ts +120 -120
  78. package/src/lib/knowledge-store.test.ts +170 -180
  79. package/src/lib/knowledge-store.ts +369 -374
  80. package/src/lib/load-context.test.ts +52 -52
  81. package/src/lib/load-context.ts +52 -52
  82. package/src/lib/match-state.test.ts +134 -134
  83. package/src/lib/match-state.ts +94 -94
  84. package/src/lib/netease-tts.ts +83 -83
  85. package/src/lib/normalize.ts +42 -42
  86. package/src/lib/persona.test.ts +41 -41
  87. package/src/lib/persona.ts +72 -72
  88. package/src/lib/server-registry.ts +152 -152
  89. package/src/lib/skill-version.test.ts +48 -48
  90. package/src/lib/skill-version.ts +19 -19
  91. package/src/lib/strategy-export.test.ts +232 -232
  92. package/src/lib/strategy-export.ts +242 -242
  93. package/src/lib/tts-keys.ts +7 -7
  94. package/src/lib/tts-speech.test.ts +63 -63
  95. package/src/lib/tts-speech.ts +76 -76
  96. package/src/lib/workspace-argv.test.ts +49 -49
  97. package/src/lib/workspace-argv.ts +44 -44
  98. package/src/perception/player-history-store.test.ts +87 -87
  99. package/src/perception/player-history-store.ts +194 -194
  100. package/src/pipeline/event-format.test.ts +135 -135
  101. package/src/pipeline/event-format.ts +376 -376
  102. package/src/pipeline/event-hints.ts +173 -173
  103. package/src/pipeline/event-store.test.ts +28 -28
  104. package/src/pipeline/event-store.ts +193 -193
  105. package/src/pipeline/pipeline.ts +35 -35
  106. package/src/runtime/auto-upgrade.test.ts +66 -66
  107. package/src/runtime/auto-upgrade.ts +31 -31
  108. package/src/runtime/event-daemon.test.ts +107 -107
  109. package/src/runtime/event-daemon.ts +409 -409
  110. package/src/runtime/owner-control.ts +150 -150
  111. package/src/runtime/raw-ws-log.test.ts +33 -33
  112. package/src/runtime/raw-ws-log.ts +32 -32
  113. package/src/runtime/runtime-logger.ts +107 -107
  114. package/src/runtime/ws-client.test.ts +104 -104
  115. package/src/runtime/ws-client.ts +272 -272
  116. package/src/sdk/action.ts +166 -166
  117. package/src/sdk/index.ts +110 -111
  118. package/src/sdk/types.ts +161 -159
  119. package/src/strategies/avoid-lone.ts +11 -11
  120. package/src/strategies/avoid-players.knowledge.md +19 -20
  121. package/src/strategies/avoid-players.ts +15 -15
  122. package/src/strategies/corpse-patrol.ts +22 -22
  123. package/src/strategies/crab-sabotage.ts +21 -21
  124. package/src/strategies/custom-module.test.ts +269 -269
  125. package/src/strategies/find-player.ts +16 -16
  126. package/src/strategies/game-utils.test.ts +190 -190
  127. package/src/strategies/game-utils.ts +794 -782
  128. package/src/strategies/goals/anchor-linger.ts +77 -77
  129. package/src/strategies/goals/avoid-lone-top.ts +168 -168
  130. package/src/strategies/goals/avoid-players-top.test.ts +83 -83
  131. package/src/strategies/goals/avoid-players-top.ts +121 -121
  132. package/src/strategies/goals/conversation-goal.ts +51 -51
  133. package/src/strategies/goals/corpse-patrol-top.ts +113 -91
  134. package/src/strategies/goals/crab-octopus-reflexes.ts +93 -93
  135. package/src/strategies/goals/crab-sabotage-top.ts +197 -197
  136. package/src/strategies/goals/emergency-hunt-goal.ts +28 -28
  137. package/src/strategies/goals/find-player-top.ts +93 -93
  138. package/src/strategies/goals/flee-players-goal.ts +53 -53
  139. package/src/strategies/goals/follow-companion-goal.ts +106 -106
  140. package/src/strategies/goals/goal-manager.ts +41 -41
  141. package/src/strategies/goals/goal-root-strategy.ts +49 -49
  142. package/src/strategies/goals/goal.ts +28 -28
  143. package/src/strategies/goals/hide-top.ts +197 -197
  144. package/src/strategies/goals/keep-away-goal.ts +217 -217
  145. package/src/strategies/goals/kill-frenzy-top.ts +80 -80
  146. package/src/strategies/goals/kill-lone-top.ts +160 -160
  147. package/src/strategies/goals/kill-target-goal.ts +59 -59
  148. package/src/strategies/goals/kill-target-top.ts +109 -109
  149. package/src/strategies/goals/leaf-goal.ts +27 -25
  150. package/src/strategies/goals/linger-corpse-goal.ts +35 -35
  151. package/src/strategies/goals/lone-kill-core.ts +82 -82
  152. package/src/strategies/goals/lone-kill-goal.ts +24 -24
  153. package/src/strategies/goals/lone-kill-task-top.test.ts +85 -85
  154. package/src/strategies/goals/lone-kill-task-top.ts +117 -86
  155. package/src/strategies/goals/move-room-goal.ts +60 -60
  156. package/src/strategies/goals/normal-shrimp-top.test.ts +80 -80
  157. package/src/strategies/goals/normal-shrimp-top.ts +242 -242
  158. package/src/strategies/goals/paradise-fish-top.test.ts +126 -126
  159. package/src/strategies/goals/paradise-fish-top.ts +224 -207
  160. package/src/strategies/goals/patrol-top.ts +57 -57
  161. package/src/strategies/goals/report-patrol-top.ts +80 -80
  162. package/src/strategies/goals/safe-task-goal.ts +102 -102
  163. package/src/strategies/goals/social-task-top.ts +161 -161
  164. package/src/strategies/goals/task-kill-report-top.ts +163 -163
  165. package/src/strategies/goals/task-only-top.ts +57 -57
  166. package/src/strategies/goals/task-or-patrol-goal.ts +41 -41
  167. package/src/strategies/goals/task-report-top.ts +57 -57
  168. package/src/strategies/goals/wander-task-goal.ts +33 -33
  169. package/src/strategies/goals/warrior-shrimp-top.test.ts +87 -86
  170. package/src/strategies/goals/warrior-shrimp-top.ts +560 -500
  171. package/src/strategies/greeting.ts +53 -53
  172. package/src/strategies/hide-spots.ts +123 -123
  173. package/src/strategies/hide.ts +23 -23
  174. package/src/strategies/kill-frenzy.ts +12 -12
  175. package/src/strategies/kill-lone.knowledge.md +17 -20
  176. package/src/strategies/kill-lone.ts +13 -13
  177. package/src/strategies/kill-target.ts +18 -18
  178. package/src/strategies/loader.test.ts +678 -678
  179. package/src/strategies/loader.ts +172 -172
  180. package/src/strategies/lone-kill-task.ts +21 -21
  181. package/src/strategies/meeting-gate.test.ts +59 -59
  182. package/src/strategies/meeting-gate.ts +23 -23
  183. package/src/strategies/move-room.ts +15 -15
  184. package/src/strategies/new-events-backfill.ts +98 -98
  185. package/src/strategies/paradise-fish.knowledge.md +19 -20
  186. package/src/strategies/paradise-fish.ts +25 -25
  187. package/src/strategies/pathfind/distance-field.ts +150 -150
  188. package/src/strategies/pathfind/escape-planner.test.ts +197 -197
  189. package/src/strategies/pathfind/escape-planner.ts +355 -355
  190. package/src/strategies/pathfind/walkable-grid.ts +117 -117
  191. package/src/strategies/patrol.ts +11 -11
  192. package/src/strategies/player-targets.ts +13 -13
  193. package/src/strategies/report-patrol.ts +11 -11
  194. package/src/strategies/shrimp-memory.knowledge.md +19 -20
  195. package/src/strategies/shrimp-memory.ts +25 -25
  196. package/src/strategies/social-task.test.ts +28 -28
  197. package/src/strategies/social-task.ts +49 -49
  198. package/src/strategies/spawn.ts +82 -82
  199. package/src/strategies/speech-module.ts +123 -123
  200. package/src/strategies/strategy-loop.ts +771 -771
  201. package/src/strategies/task-kill-report.ts +17 -17
  202. package/src/strategies/task-only.ts +11 -11
  203. package/src/strategies/task-report.ts +22 -22
  204. package/src/strategies/types.ts +102 -102
  205. package/src/strategies/warrior-memory.knowledge.md +21 -22
  206. package/src/strategies/warrior-memory.ts +16 -16
@@ -1,49 +1,49 @@
1
- import type { GameState } from '../../sdk/types.js';
2
- import type { BehaviorDecision, CustomModule, Strategy, StrategyContext } from '../types.js';
3
- import type { Goal } from './goal.js';
4
- import { GoalManager } from './goal-manager.js';
5
-
6
- export interface GoalRootStrategyOptions {
7
- resetOnMeetingResume?: boolean;
8
- customModules?: CustomModule[];
9
- onUpdateRole?: (role: string) => void;
10
- }
11
-
12
- export class GoalRootStrategy implements Strategy {
13
- readonly name: string;
14
- private readonly mgr = new GoalManager();
15
- private readonly makeTop: () => Goal;
16
- private readonly resetOnMeetingResume: boolean;
17
- private readonly modules: CustomModule[];
18
- private readonly onUpdateRole?: (role: string) => void;
19
-
20
- constructor(name: string, makeTop: () => Goal, options?: GoalRootStrategyOptions) {
21
- this.name = name;
22
- this.makeTop = makeTop;
23
- this.resetOnMeetingResume = options?.resetOnMeetingResume ?? true;
24
- this.modules = options?.customModules ?? [];
25
- this.onUpdateRole = options?.onUpdateRole;
26
- this.mgr.setTop(makeTop());
27
- }
28
-
29
- decide(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
30
- if (this.mgr.getTop() === null) this.mgr.setTop(this.makeTop());
31
- return this.mgr.tick(state, ctx);
32
- }
33
-
34
- onMeetingResume(): void {
35
- if (this.resetOnMeetingResume) {
36
- this.mgr.setTop(this.makeTop());
37
- return;
38
- }
39
- this.mgr.getTop()?.onMeetingResume();
40
- }
41
-
42
- customModules(): CustomModule[] {
43
- return this.modules;
44
- }
45
-
46
- updateRole(role: string): void {
47
- this.onUpdateRole?.(role);
48
- }
49
- }
1
+ import type { GameState } from '../../sdk/types.js';
2
+ import type { BehaviorDecision, CustomModule, Strategy, StrategyContext } from '../types.js';
3
+ import type { Goal } from './goal.js';
4
+ import { GoalManager } from './goal-manager.js';
5
+
6
+ export interface GoalRootStrategyOptions {
7
+ resetOnMeetingResume?: boolean;
8
+ customModules?: CustomModule[];
9
+ onUpdateRole?: (role: string) => void;
10
+ }
11
+
12
+ export class GoalRootStrategy implements Strategy {
13
+ readonly name: string;
14
+ private readonly mgr = new GoalManager();
15
+ private readonly makeTop: () => Goal;
16
+ private readonly resetOnMeetingResume: boolean;
17
+ private readonly modules: CustomModule[];
18
+ private readonly onUpdateRole?: (role: string) => void;
19
+
20
+ constructor(name: string, makeTop: () => Goal, options?: GoalRootStrategyOptions) {
21
+ this.name = name;
22
+ this.makeTop = makeTop;
23
+ this.resetOnMeetingResume = options?.resetOnMeetingResume ?? true;
24
+ this.modules = options?.customModules ?? [];
25
+ this.onUpdateRole = options?.onUpdateRole;
26
+ this.mgr.setTop(makeTop());
27
+ }
28
+
29
+ decide(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
30
+ if (this.mgr.getTop() === null) this.mgr.setTop(this.makeTop());
31
+ return this.mgr.tick(state, ctx);
32
+ }
33
+
34
+ onMeetingResume(): void {
35
+ if (this.resetOnMeetingResume) {
36
+ this.mgr.setTop(this.makeTop());
37
+ return;
38
+ }
39
+ this.mgr.getTop()?.onMeetingResume();
40
+ }
41
+
42
+ customModules(): CustomModule[] {
43
+ return this.modules;
44
+ }
45
+
46
+ updateRole(role: string): void {
47
+ this.onUpdateRole?.(role);
48
+ }
49
+ }
@@ -1,28 +1,28 @@
1
- import type { GameState } from '../../sdk/types.js';
2
- import type { BehaviorDecision, StrategyContext } from '../types.js';
3
-
4
- export abstract class Goal {
5
- subGoal: Goal | null = null;
6
- priority = 0;
7
-
8
- abstract tick(state: GameState, ctx: StrategyContext): BehaviorDecision[];
9
-
10
- isFinish(_state: GameState, _ctx: StrategyContext): boolean {
11
- return false;
12
- }
13
-
14
- onMeetingResume(): void {}
15
-
16
- setSubGoal(child: Goal, priority: number): boolean {
17
- if (this.subGoal !== null && this.subGoal.priority >= priority) {
18
- return false;
19
- }
20
- child.priority = priority;
21
- this.subGoal = child;
22
- return true;
23
- }
24
-
25
- removeSubGoal(): void {
26
- this.subGoal = null;
27
- }
28
- }
1
+ import type { GameState } from '../../sdk/types.js';
2
+ import type { BehaviorDecision, StrategyContext } from '../types.js';
3
+
4
+ export abstract class Goal {
5
+ subGoal: Goal | null = null;
6
+ priority = 0;
7
+
8
+ abstract tick(state: GameState, ctx: StrategyContext): BehaviorDecision[];
9
+
10
+ isFinish(_state: GameState, _ctx: StrategyContext): boolean {
11
+ return false;
12
+ }
13
+
14
+ onMeetingResume(): void {}
15
+
16
+ setSubGoal(child: Goal, priority: number): boolean {
17
+ if (this.subGoal !== null && this.subGoal.priority >= priority) {
18
+ return false;
19
+ }
20
+ child.priority = priority;
21
+ this.subGoal = child;
22
+ return true;
23
+ }
24
+
25
+ removeSubGoal(): void {
26
+ this.subGoal = null;
27
+ }
28
+ }
@@ -1,197 +1,197 @@
1
- import type { GameState, PlayerInfo, Position } from '../../sdk/types.js';
2
- import { Action } from '../../sdk/action.js';
3
- import {
4
- canUseKill,
5
- dist,
6
- killCommitRange,
7
- nonTeammatesVisible,
8
- PROGRESS_INTERVAL_MS,
9
- SHRIMP_KILL_RANGE,
10
- } from '../game-utils.js';
11
- import { nearestSafeHideSpot, type HideSpot } from '../hide-spots.js';
12
- import { planEscape, type EscapeOptions } from '../pathfind/escape-planner.js';
13
- import type { BehaviorDecision, StrategyContext } from '../types.js';
14
- import { Goal } from './goal.js';
15
- import { KeepAwayGoal } from './keep-away-goal.js';
16
-
17
- const SUB_PRIORITY = 0.5;
18
- const FLEE_KEY = 'hide-flee';
19
- /** 走到躲藏点这个距离内就算到位,原地潜伏不动。 */
20
- const REACHED_DISTANCE = 40;
21
- /** 威胁离开视野后,其位置仍在选点时被回避这么久——逃完不会立刻又选回威胁刚待过的点。 */
22
- const THREAT_MEMORY_MS = 5_000;
23
- /**
24
- * 「无处可逃」用的逃跑推演参数:与 KeepAwayGoal 同款保守模型(killRange=160,按枪虾/武士虾兜底)。
25
- * 理由是「连我自己用来逃跑的同一套推演都判定必被追上,逃就没意义,才出刀自保」,判据自洽。
26
- */
27
- const ESCAPE_OPTS: EscapeOptions = { killRange: SHRIMP_KILL_RANGE, steps: 7, beamWidth: 8, directions: 16, fieldRadius: 900 };
28
-
29
- type ProgressTier = 'flee' | 'relocate' | 'hidden';
30
- type ThreatPointResolver = () => Position[];
31
-
32
- /**
33
- * 躲藏点劳作循环:与 SafeTaskOrPatrolGoal 同构——planSpot 边逃边预重算(粘性、威胁硬排除),
34
- * tick 执行(走向躲藏点;到位则原地不动)。没有可用点时不动。
35
- */
36
- class HideSpotGoal extends Goal {
37
- private current: HideSpot | null = null;
38
-
39
- constructor(private readonly threatPoints: ThreatPointResolver) {
40
- super();
41
- }
42
-
43
- get currentSpot(): HideSpot | null {
44
- return this.current;
45
- }
46
-
47
- reset(): void {
48
- this.current = null;
49
- }
50
-
51
- /** 重算并记住安全躲藏点,不发移动指令(逃跑期间可安全调用)。 */
52
- planSpot(state: GameState, ctx: StrategyContext): HideSpot | null {
53
- const spot = nearestSafeHideSpot(
54
- { x: state.you.x, y: state.you.y },
55
- this.threatPoints(),
56
- { stickyTo: this.current, blockedTarget: ctx.blockedMoveTarget },
57
- );
58
- this.current = spot;
59
- return spot;
60
- }
61
-
62
- tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
63
- const spot = this.planSpot(state, ctx);
64
- if (!spot) return [];
65
- if (dist(state.you.x, state.you.y, spot.x, spot.y) <= REACHED_DISTANCE) return []; // 到位,原地潜伏
66
- return [{ action: Action.move({ x: spot.x, y: spot.y }).withThinking(`前往躲藏点${spot.room}潜伏`) }];
67
- }
68
- }
69
-
70
- /**
71
- * 躲藏策略顶层:在离线算好的藏身角落里选测地最近且不挨威胁的一个潜伏不动,直到有人进入视野。
72
- * 有人来就 ① 用 KeepAwayGoal 保持距离甩开,② 边逃边重算排除该威胁的新躲藏点——与 shrimp-memory 的
73
- * 「躲人 + 重选任务」同构,只是把任务换成躲藏点。蟹不躲队友(威胁统一用 nonTeammatesVisible 过滤队友)。
74
- *
75
- * killWhenCornered:被**单个**对手逼进出刀距离、且 planEscape 判定无路可逃时才出刀自保。
76
- * null = 按角色默认:蟹 / 章鱼开,武士虾 / 枪虾(及无刀角色)关。
77
- */
78
- export class HideTop extends Goal {
79
- private readonly spotGoal = new HideSpotGoal(() => this.threatPointsFor());
80
- private readonly recentThreats = new Map<string, { x: number; y: number; until: number }>();
81
-
82
- constructor(private readonly killWhenCornered: boolean | null = null) {
83
- super();
84
- }
85
-
86
- onMeetingResume(): void {
87
- this.spotGoal.reset();
88
- this.recentThreats.clear();
89
- if (this.subGoal) this.removeSubGoal();
90
- }
91
-
92
- tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
93
- const visible = nonTeammatesVisible(state, ctx);
94
- this.rememberThreats(visible);
95
-
96
- // ── 被单个对手逼到死角且刀好 → 出刀自保(按角色 / 参数开关) ──
97
- if (this.killEnabled(state) && canUseKill(state)) {
98
- const victim = this.corneredVictim(state, visible);
99
- if (victim) {
100
- if (this.subGoal) this.removeSubGoal();
101
- ctx.notifications.push(`被${victim.name}逼到无处可逃,出刀自保!`);
102
- return [{ action: Action.kill(victim.name).withThinking(`被${victim.name}逼进出刀距离又逃不掉,先下手自保。`) }];
103
- }
104
- }
105
-
106
- // ── 视野里有人 → 保持距离甩开 + 边逃边重算安全躲藏点 ──
107
- if (visible.length > 0) {
108
- this.spotGoal.planSpot(state, ctx); // 重算(排除威胁),逃完落到新点
109
- this.setFlee();
110
- this.emitProgress(state, ctx, visible, 'flee');
111
- return [];
112
- }
113
-
114
- // ── 无人 → 走向最近安全躲藏点并潜伏 ──
115
- if (this.subGoal) this.removeSubGoal();
116
- const decisions = this.spotGoal.tick(state, ctx);
117
- this.emitProgress(state, ctx, visible, decisions.length === 0 ? 'hidden' : 'relocate');
118
- return decisions;
119
- }
120
-
121
- private killEnabled(state: GameState): boolean {
122
- if (!roleHasKnife(state)) return false; // 无刀角色(普通虾)永不出刀,传 kill 参数也不行
123
- return this.killWhenCornered ?? defaultKillWhenCornered(state);
124
- }
125
-
126
- /** 单个对手、进入出刀距离、且 planEscape 判定必被追上 → 返回该对手,否则 null。 */
127
- private corneredVictim(state: GameState, visible: PlayerInfo[]): PlayerInfo | null {
128
- // 只在单个追兵时自保:2 人以上当众动手仍会被另一人举报,逃跑(推演偏悲观,真人有视野盲区)反而更优。
129
- if (visible.length !== 1) return null;
130
- const target = visible[0];
131
- const d = target.distance ?? dist(state.you.x, state.you.y, target.x, target.y);
132
- if (d > killCommitRange(state.you.role)) return null;
133
- const plan = planEscape({ x: state.you.x, y: state.you.y }, [{ x: target.x, y: target.y }], ESCAPE_OPTS);
134
- return plan.caught ? target : null;
135
- }
136
-
137
- private setFlee(): void {
138
- const worker = this.subGoal;
139
- if (worker instanceof KeepAwayGoal && worker.key === FLEE_KEY) return;
140
- if (this.subGoal) this.removeSubGoal();
141
- this.setSubGoal(new KeepAwayGoal(FLEE_KEY, (s, c) => nonTeammatesVisible(s, c), {
142
- threatRadius: Infinity,
143
- noun: '靠近的人',
144
- }), SUB_PRIORITY);
145
- }
146
-
147
- /** 记住近期见过的非队友位置(按名字去重 + TTL),供威胁离开视野后选点继续回避。 */
148
- private rememberThreats(visible: PlayerInfo[]): void {
149
- const now = Date.now();
150
- for (const p of visible) {
151
- const key = p.name || String(p.seat ?? '');
152
- if (key) this.recentThreats.set(key, { x: p.x, y: p.y, until: now + THREAT_MEMORY_MS });
153
- }
154
- for (const [key, v] of this.recentThreats) if (v.until <= now) this.recentThreats.delete(key);
155
- }
156
-
157
- private threatPointsFor(): Position[] {
158
- const now = Date.now();
159
- const out: Position[] = [];
160
- for (const v of this.recentThreats.values()) if (v.until > now) out.push({ x: v.x, y: v.y });
161
- return out;
162
- }
163
-
164
- private emitProgress(state: GameState, ctx: StrategyContext, visible: PlayerInfo[], tier: ProgressTier): void {
165
- const now = Date.now();
166
- if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
167
- ctx.lastProgressNotifyAt = now;
168
-
169
- const room = state.you.room ?? '未知';
170
- const spot = this.spotGoal.currentSpot;
171
- let msg = `[进度] 当前在${room}`;
172
- if (tier === 'flee') {
173
- const names = visible.map(p => p.name).join('、');
174
- msg += `,${names}靠近,保持距离甩开${spot ? `,改藏到${spot.room}` : ''}。`;
175
- } else if (tier === 'relocate') {
176
- msg += spot ? `,前往躲藏点${spot.room}潜伏。` : ',寻找躲藏点。';
177
- } else {
178
- msg += spot ? `,已在${spot.room}潜伏,按兵不动。` : ',附近无可用躲藏点,原地待命。';
179
- }
180
- ctx.notifications.push(msg);
181
- }
182
- }
183
-
184
- /** 是否带刀(有击杀能力):蟹 / 章鱼 / 武士虾 / 枪虾。普通虾无刀。 */
185
- function roleHasKnife(state: GameState): boolean {
186
- const role = state.you.role ?? '';
187
- const faction = state.you.faction ?? '';
188
- return faction === 'crab' || role.includes('crab') || role.includes('octopus')
189
- || role === 'shrimp_warrior' || role === 'shrimp_pistol';
190
- }
191
-
192
- /** 角色默认是否「死角自保出刀」:蟹 / 章鱼开;武士虾 / 枪虾及其余关。 */
193
- function defaultKillWhenCornered(state: GameState): boolean {
194
- const role = state.you.role ?? '';
195
- const faction = state.you.faction ?? '';
196
- return faction === 'crab' || role.includes('crab') || role.includes('octopus');
197
- }
1
+ import type { GameState, PlayerInfo, Position } from '../../sdk/types.js';
2
+ import { Action } from '../../sdk/action.js';
3
+ import {
4
+ canUseKill,
5
+ dist,
6
+ killCommitRange,
7
+ nonTeammatesVisible,
8
+ PROGRESS_INTERVAL_MS,
9
+ SHRIMP_KILL_RANGE,
10
+ } from '../game-utils.js';
11
+ import { nearestSafeHideSpot, type HideSpot } from '../hide-spots.js';
12
+ import { planEscape, type EscapeOptions } from '../pathfind/escape-planner.js';
13
+ import type { BehaviorDecision, StrategyContext } from '../types.js';
14
+ import { Goal } from './goal.js';
15
+ import { KeepAwayGoal } from './keep-away-goal.js';
16
+
17
+ const SUB_PRIORITY = 0.5;
18
+ const FLEE_KEY = 'hide-flee';
19
+ /** 走到躲藏点这个距离内就算到位,原地潜伏不动。 */
20
+ const REACHED_DISTANCE = 40;
21
+ /** 威胁离开视野后,其位置仍在选点时被回避这么久——逃完不会立刻又选回威胁刚待过的点。 */
22
+ const THREAT_MEMORY_MS = 5_000;
23
+ /**
24
+ * 「无处可逃」用的逃跑推演参数:与 KeepAwayGoal 同款保守模型(killRange=160,按枪虾/武士虾兜底)。
25
+ * 理由是「连我自己用来逃跑的同一套推演都判定必被追上,逃就没意义,才出刀自保」,判据自洽。
26
+ */
27
+ const ESCAPE_OPTS: EscapeOptions = { killRange: SHRIMP_KILL_RANGE, steps: 7, beamWidth: 8, directions: 16, fieldRadius: 900 };
28
+
29
+ type ProgressTier = 'flee' | 'relocate' | 'hidden';
30
+ type ThreatPointResolver = () => Position[];
31
+
32
+ /**
33
+ * 躲藏点劳作循环:与 SafeTaskOrPatrolGoal 同构——planSpot 边逃边预重算(粘性、威胁硬排除),
34
+ * tick 执行(走向躲藏点;到位则原地不动)。没有可用点时不动。
35
+ */
36
+ class HideSpotGoal extends Goal {
37
+ private current: HideSpot | null = null;
38
+
39
+ constructor(private readonly threatPoints: ThreatPointResolver) {
40
+ super();
41
+ }
42
+
43
+ get currentSpot(): HideSpot | null {
44
+ return this.current;
45
+ }
46
+
47
+ reset(): void {
48
+ this.current = null;
49
+ }
50
+
51
+ /** 重算并记住安全躲藏点,不发移动指令(逃跑期间可安全调用)。 */
52
+ planSpot(state: GameState, ctx: StrategyContext): HideSpot | null {
53
+ const spot = nearestSafeHideSpot(
54
+ { x: state.you.x, y: state.you.y },
55
+ this.threatPoints(),
56
+ { stickyTo: this.current, blockedTarget: ctx.blockedMoveTarget },
57
+ );
58
+ this.current = spot;
59
+ return spot;
60
+ }
61
+
62
+ tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
63
+ const spot = this.planSpot(state, ctx);
64
+ if (!spot) return [];
65
+ if (dist(state.you.x, state.you.y, spot.x, spot.y) <= REACHED_DISTANCE) return []; // 到位,原地潜伏
66
+ return [{ action: Action.move({ x: spot.x, y: spot.y }).withThinking(`前往躲藏点${spot.room}潜伏`) }];
67
+ }
68
+ }
69
+
70
+ /**
71
+ * 躲藏策略顶层:在离线算好的藏身角落里选测地最近且不挨威胁的一个潜伏不动,直到有人进入视野。
72
+ * 有人来就 ① 用 KeepAwayGoal 保持距离甩开,② 边逃边重算排除该威胁的新躲藏点——与 shrimp-memory 的
73
+ * 「躲人 + 重选任务」同构,只是把任务换成躲藏点。蟹不躲队友(威胁统一用 nonTeammatesVisible 过滤队友)。
74
+ *
75
+ * killWhenCornered:被**单个**对手逼进出刀距离、且 planEscape 判定无路可逃时才出刀自保。
76
+ * null = 按角色默认:蟹 / 章鱼开,武士虾 / 枪虾(及无刀角色)关。
77
+ */
78
+ export class HideTop extends Goal {
79
+ private readonly spotGoal = new HideSpotGoal(() => this.threatPointsFor());
80
+ private readonly recentThreats = new Map<string, { x: number; y: number; until: number }>();
81
+
82
+ constructor(private readonly killWhenCornered: boolean | null = null) {
83
+ super();
84
+ }
85
+
86
+ onMeetingResume(): void {
87
+ this.spotGoal.reset();
88
+ this.recentThreats.clear();
89
+ if (this.subGoal) this.removeSubGoal();
90
+ }
91
+
92
+ tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
93
+ const visible = nonTeammatesVisible(state, ctx);
94
+ this.rememberThreats(visible);
95
+
96
+ // ── 被单个对手逼到死角且刀好 → 出刀自保(按角色 / 参数开关) ──
97
+ if (this.killEnabled(state) && canUseKill(state)) {
98
+ const victim = this.corneredVictim(state, visible);
99
+ if (victim) {
100
+ if (this.subGoal) this.removeSubGoal();
101
+ ctx.notifications.push(`被${victim.name}逼到无处可逃,出刀自保!`);
102
+ return [{ action: Action.kill(victim.name).withThinking(`被${victim.name}逼进出刀距离又逃不掉,先下手自保。`) }];
103
+ }
104
+ }
105
+
106
+ // ── 视野里有人 → 保持距离甩开 + 边逃边重算安全躲藏点 ──
107
+ if (visible.length > 0) {
108
+ this.spotGoal.planSpot(state, ctx); // 重算(排除威胁),逃完落到新点
109
+ this.setFlee();
110
+ this.emitProgress(state, ctx, visible, 'flee');
111
+ return [];
112
+ }
113
+
114
+ // ── 无人 → 走向最近安全躲藏点并潜伏 ──
115
+ if (this.subGoal) this.removeSubGoal();
116
+ const decisions = this.spotGoal.tick(state, ctx);
117
+ this.emitProgress(state, ctx, visible, decisions.length === 0 ? 'hidden' : 'relocate');
118
+ return decisions;
119
+ }
120
+
121
+ private killEnabled(state: GameState): boolean {
122
+ if (!roleHasKnife(state)) return false; // 无刀角色(普通虾)永不出刀,传 kill 参数也不行
123
+ return this.killWhenCornered ?? defaultKillWhenCornered(state);
124
+ }
125
+
126
+ /** 单个对手、进入出刀距离、且 planEscape 判定必被追上 → 返回该对手,否则 null。 */
127
+ private corneredVictim(state: GameState, visible: PlayerInfo[]): PlayerInfo | null {
128
+ // 只在单个追兵时自保:2 人以上当众动手仍会被另一人举报,逃跑(推演偏悲观,真人有视野盲区)反而更优。
129
+ if (visible.length !== 1) return null;
130
+ const target = visible[0];
131
+ const d = target.distance ?? dist(state.you.x, state.you.y, target.x, target.y);
132
+ if (d > killCommitRange(state.you.role)) return null;
133
+ const plan = planEscape({ x: state.you.x, y: state.you.y }, [{ x: target.x, y: target.y }], ESCAPE_OPTS);
134
+ return plan.caught ? target : null;
135
+ }
136
+
137
+ private setFlee(): void {
138
+ const worker = this.subGoal;
139
+ if (worker instanceof KeepAwayGoal && worker.key === FLEE_KEY) return;
140
+ if (this.subGoal) this.removeSubGoal();
141
+ this.setSubGoal(new KeepAwayGoal(FLEE_KEY, (s, c) => nonTeammatesVisible(s, c), {
142
+ threatRadius: Infinity,
143
+ noun: '靠近的人',
144
+ }), SUB_PRIORITY);
145
+ }
146
+
147
+ /** 记住近期见过的非队友位置(按名字去重 + TTL),供威胁离开视野后选点继续回避。 */
148
+ private rememberThreats(visible: PlayerInfo[]): void {
149
+ const now = Date.now();
150
+ for (const p of visible) {
151
+ const key = p.name || String(p.seat ?? '');
152
+ if (key) this.recentThreats.set(key, { x: p.x, y: p.y, until: now + THREAT_MEMORY_MS });
153
+ }
154
+ for (const [key, v] of this.recentThreats) if (v.until <= now) this.recentThreats.delete(key);
155
+ }
156
+
157
+ private threatPointsFor(): Position[] {
158
+ const now = Date.now();
159
+ const out: Position[] = [];
160
+ for (const v of this.recentThreats.values()) if (v.until > now) out.push({ x: v.x, y: v.y });
161
+ return out;
162
+ }
163
+
164
+ private emitProgress(state: GameState, ctx: StrategyContext, visible: PlayerInfo[], tier: ProgressTier): void {
165
+ const now = Date.now();
166
+ if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
167
+ ctx.lastProgressNotifyAt = now;
168
+
169
+ const room = state.you.room ?? '未知';
170
+ const spot = this.spotGoal.currentSpot;
171
+ let msg = `[进度] 当前在${room}`;
172
+ if (tier === 'flee') {
173
+ const names = visible.map(p => p.name).join('、');
174
+ msg += `,${names}靠近,保持距离甩开${spot ? `,改藏到${spot.room}` : ''}。`;
175
+ } else if (tier === 'relocate') {
176
+ msg += spot ? `,前往躲藏点${spot.room}潜伏。` : ',寻找躲藏点。';
177
+ } else {
178
+ msg += spot ? `,已在${spot.room}潜伏,按兵不动。` : ',附近无可用躲藏点,原地待命。';
179
+ }
180
+ ctx.notifications.push(msg);
181
+ }
182
+ }
183
+
184
+ /** 是否带刀(有击杀能力):蟹 / 章鱼 / 武士虾 / 枪虾。普通虾无刀。 */
185
+ function roleHasKnife(state: GameState): boolean {
186
+ const role = state.you.role ?? '';
187
+ const faction = state.you.faction ?? '';
188
+ return faction === 'crab' || role.includes('crab') || role.includes('octopus')
189
+ || role === 'shrimp_warrior' || role === 'shrimp_pistol';
190
+ }
191
+
192
+ /** 角色默认是否「死角自保出刀」:蟹 / 章鱼开;武士虾 / 枪虾及其余关。 */
193
+ function defaultKillWhenCornered(state: GameState): boolean {
194
+ const role = state.you.role ?? '';
195
+ const faction = state.you.faction ?? '';
196
+ return faction === 'crab' || role.includes('crab') || role.includes('octopus');
197
+ }