@myclaw163/clawclaw-cli 0.6.70 → 0.6.74

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 (212) hide show
  1. package/README.md +377 -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/check-skill-command-surface.mjs +116 -0
  9. package/scripts/find-hide-spots.py +157 -157
  10. package/scripts/postinstall.mjs +20 -20
  11. package/scripts/sync-bundled-skill.mjs +244 -244
  12. package/scripts/sync-bundled-skill.test.mjs +152 -152
  13. package/skills/clawclaw/SKILL.md +246 -244
  14. package/skills/clawclaw/references/CHATTERBOX.md +141 -142
  15. package/skills/clawclaw/references/COMMANDS.md +155 -148
  16. package/skills/clawclaw/references/GAME-MECHANICS.md +188 -188
  17. package/skills/clawclaw/references/HUB.md +48 -48
  18. package/skills/clawclaw/references/KNOWLEDGE.md +42 -45
  19. package/skills/clawclaw/references/STRATEGIES.md +59 -59
  20. package/skills/clawclaw/references/STREAM.md +93 -91
  21. package/skills/clawclaw/references/TACTICS.md +65 -65
  22. package/src/assets/clawclaw-ascii-map.txt +40 -40
  23. package/src/cli.ts +110 -110
  24. package/src/commands/_schema.ts +124 -109
  25. package/src/commands/account.ts +209 -209
  26. package/src/commands/config.ts +30 -30
  27. package/src/commands/do.test.ts +84 -73
  28. package/src/commands/do.ts +130 -126
  29. package/src/commands/events.test.ts +71 -71
  30. package/src/commands/events.ts +221 -155
  31. package/src/commands/game-map.test.ts +28 -28
  32. package/src/commands/game-start-plan.test.ts +84 -84
  33. package/src/commands/game.ts +1113 -1042
  34. package/src/commands/history-player.test.ts +102 -102
  35. package/src/commands/history.ts +573 -573
  36. package/src/commands/hub.test.ts +96 -96
  37. package/src/commands/hub.ts +234 -234
  38. package/src/commands/knowledge.test.ts +13 -13
  39. package/src/commands/knowledge.ts +139 -139
  40. package/src/commands/load.test.ts +51 -51
  41. package/src/commands/load.ts +13 -13
  42. package/src/commands/meeting-history.test.ts +106 -106
  43. package/src/commands/memory.ts +40 -40
  44. package/src/commands/peek.ts +45 -45
  45. package/src/commands/persona.ts +57 -57
  46. package/src/commands/setup/codex.ts +266 -266
  47. package/src/commands/setup/hermes.test.ts +96 -96
  48. package/src/commands/setup/hermes.ts +76 -76
  49. package/src/commands/setup/index.ts +13 -13
  50. package/src/commands/setup/openclaw.test.ts +114 -114
  51. package/src/commands/setup/openclaw.ts +147 -147
  52. package/src/commands/skill.ts +128 -128
  53. package/src/commands/state.ts +46 -46
  54. package/src/commands/strategy.test.ts +145 -145
  55. package/src/commands/strategy.ts +181 -181
  56. package/src/commands/tts.ts +128 -128
  57. package/src/commands/upgrade.test.ts +82 -82
  58. package/src/commands/upgrade.ts +148 -148
  59. package/src/commands/watch.test.ts +999 -977
  60. package/src/commands/watch.ts +660 -658
  61. package/src/lib/auth.test.ts +74 -74
  62. package/src/lib/auth.ts +186 -186
  63. package/src/lib/command-meta.ts +37 -37
  64. package/src/lib/game-client.ts +403 -391
  65. package/src/lib/game-context.ts +92 -0
  66. package/src/lib/host-config-patcher.test.ts +130 -130
  67. package/src/lib/host-config-patcher.ts +151 -151
  68. package/src/lib/http-keepalive.ts +15 -15
  69. package/src/lib/http-transport.test.ts +42 -42
  70. package/src/lib/http-transport.ts +113 -113
  71. package/src/lib/hub-client.test.ts +56 -56
  72. package/src/lib/hub-client.ts +88 -88
  73. package/src/lib/hub-install.test.ts +98 -98
  74. package/src/lib/hub-install.ts +121 -121
  75. package/src/lib/hub-reminder.ts +56 -56
  76. package/src/lib/hub-unzip.test.ts +69 -69
  77. package/src/lib/hub-unzip.ts +62 -62
  78. package/src/lib/init-command.test.ts +75 -75
  79. package/src/lib/init-command.ts +120 -120
  80. package/src/lib/knowledge-store.test.ts +170 -170
  81. package/src/lib/knowledge-store.ts +369 -369
  82. package/src/lib/load-context.test.ts +52 -52
  83. package/src/lib/load-context.ts +52 -52
  84. package/src/lib/match-state.test.ts +134 -134
  85. package/src/lib/match-state.ts +94 -94
  86. package/src/lib/netease-tts.ts +83 -83
  87. package/src/lib/normalize.ts +42 -42
  88. package/src/lib/persona.test.ts +41 -41
  89. package/src/lib/persona.ts +72 -72
  90. package/src/lib/server-registry.ts +152 -152
  91. package/src/lib/skill-version.test.ts +48 -48
  92. package/src/lib/skill-version.ts +19 -19
  93. package/src/lib/strategy-export.test.ts +232 -232
  94. package/src/lib/strategy-export.ts +242 -242
  95. package/src/lib/tts-keys.ts +7 -7
  96. package/src/lib/tts-speech.test.ts +63 -63
  97. package/src/lib/tts-speech.ts +76 -76
  98. package/src/lib/workspace-argv.test.ts +49 -49
  99. package/src/lib/workspace-argv.ts +44 -44
  100. package/src/perception/player-history-store.test.ts +87 -87
  101. package/src/perception/player-history-store.ts +194 -194
  102. package/src/pipeline/event-format.test.ts +243 -215
  103. package/src/pipeline/event-format.ts +501 -485
  104. package/src/pipeline/event-hints.ts +195 -190
  105. package/src/pipeline/event-store.test.ts +28 -28
  106. package/src/pipeline/event-store.ts +193 -193
  107. package/src/pipeline/pipeline.ts +35 -35
  108. package/src/pipeline/player-projection.test.ts +119 -0
  109. package/src/pipeline/player-projection.ts +380 -0
  110. package/src/runtime/auto-upgrade.test.ts +66 -66
  111. package/src/runtime/auto-upgrade.ts +31 -31
  112. package/src/runtime/event-daemon.test.ts +209 -141
  113. package/src/runtime/event-daemon.ts +519 -457
  114. package/src/runtime/owner-control.ts +150 -150
  115. package/src/runtime/raw-ws-log.test.ts +33 -33
  116. package/src/runtime/raw-ws-log.ts +32 -32
  117. package/src/runtime/runtime-logger.ts +107 -107
  118. package/src/runtime/ws-client.test.ts +125 -104
  119. package/src/runtime/ws-client.ts +287 -272
  120. package/src/sdk/action.ts +166 -166
  121. package/src/sdk/index.ts +110 -110
  122. package/src/sdk/types.ts +161 -161
  123. package/src/strategies/avoid-lone.ts +12 -12
  124. package/src/strategies/avoid-players.knowledge.md +19 -19
  125. package/src/strategies/avoid-players.ts +16 -16
  126. package/src/strategies/corpse-patrol.ts +23 -23
  127. package/src/strategies/crab-sabotage.ts +22 -22
  128. package/src/strategies/custom-module.test.ts +270 -270
  129. package/src/strategies/find-player.ts +17 -17
  130. package/src/strategies/game-utils.test.ts +242 -242
  131. package/src/strategies/game-utils.ts +846 -846
  132. package/src/strategies/goals/anchor-linger.ts +77 -77
  133. package/src/strategies/goals/avoid-lone-top.ts +168 -168
  134. package/src/strategies/goals/avoid-players-top.test.ts +83 -83
  135. package/src/strategies/goals/avoid-players-top.ts +121 -121
  136. package/src/strategies/goals/conversation-goal.ts +51 -51
  137. package/src/strategies/goals/corpse-patrol-top.ts +113 -113
  138. package/src/strategies/goals/crab-octopus-reflexes.ts +101 -101
  139. package/src/strategies/goals/crab-sabotage-top.ts +197 -197
  140. package/src/strategies/goals/emergency-hunt-goal.ts +28 -28
  141. package/src/strategies/goals/find-player-top.ts +93 -93
  142. package/src/strategies/goals/flee-players-goal.ts +53 -53
  143. package/src/strategies/goals/follow-companion-goal.ts +106 -106
  144. package/src/strategies/goals/goal-manager.ts +41 -41
  145. package/src/strategies/goals/goal-root-strategy.ts +49 -49
  146. package/src/strategies/goals/goal.ts +28 -28
  147. package/src/strategies/goals/hide-top.ts +197 -197
  148. package/src/strategies/goals/keep-away-goal.ts +221 -221
  149. package/src/strategies/goals/kill-frenzy-top.ts +80 -80
  150. package/src/strategies/goals/kill-lone-top.ts +160 -160
  151. package/src/strategies/goals/kill-target-goal.ts +59 -59
  152. package/src/strategies/goals/kill-target-top.ts +109 -109
  153. package/src/strategies/goals/leaf-goal.ts +27 -27
  154. package/src/strategies/goals/linger-corpse-goal.ts +35 -35
  155. package/src/strategies/goals/lone-kill-core.ts +82 -82
  156. package/src/strategies/goals/lone-kill-goal.ts +24 -24
  157. package/src/strategies/goals/lone-kill-task-top.test.ts +85 -85
  158. package/src/strategies/goals/lone-kill-task-top.ts +133 -133
  159. package/src/strategies/goals/move-room-goal.ts +60 -60
  160. package/src/strategies/goals/normal-shrimp-top.test.ts +80 -80
  161. package/src/strategies/goals/normal-shrimp-top.ts +242 -242
  162. package/src/strategies/goals/paradise-fish-top.test.ts +126 -126
  163. package/src/strategies/goals/paradise-fish-top.ts +224 -224
  164. package/src/strategies/goals/patrol-top.ts +57 -57
  165. package/src/strategies/goals/report-patrol-top.ts +80 -80
  166. package/src/strategies/goals/safe-task-goal.ts +102 -102
  167. package/src/strategies/goals/social-task-top.ts +161 -161
  168. package/src/strategies/goals/task-kill-report-top.ts +163 -163
  169. package/src/strategies/goals/task-only-top.ts +57 -57
  170. package/src/strategies/goals/task-or-patrol-goal.ts +41 -41
  171. package/src/strategies/goals/task-report-top.ts +57 -57
  172. package/src/strategies/goals/wander-task-goal.ts +33 -33
  173. package/src/strategies/goals/warrior-shrimp-top.test.ts +87 -87
  174. package/src/strategies/goals/warrior-shrimp-top.ts +267 -267
  175. package/src/strategies/greeting.ts +53 -53
  176. package/src/strategies/hide-spots.ts +59 -59
  177. package/src/strategies/hide.ts +24 -24
  178. package/src/strategies/kill-frenzy.ts +13 -13
  179. package/src/strategies/kill-lone.knowledge.md +17 -17
  180. package/src/strategies/kill-lone.ts +14 -14
  181. package/src/strategies/kill-target.ts +19 -19
  182. package/src/strategies/loader.test.ts +678 -678
  183. package/src/strategies/loader.ts +179 -179
  184. package/src/strategies/lone-kill-task.ts +22 -22
  185. package/src/strategies/meeting-gate.test.ts +59 -59
  186. package/src/strategies/meeting-gate.ts +23 -23
  187. package/src/strategies/move-room.ts +16 -16
  188. package/src/strategies/new-events-backfill.ts +98 -98
  189. package/src/strategies/off-route-points.ts +105 -105
  190. package/src/strategies/paradise-fish.knowledge.md +19 -19
  191. package/src/strategies/paradise-fish.ts +26 -26
  192. package/src/strategies/pathfind/distance-field.ts +150 -150
  193. package/src/strategies/pathfind/escape-planner.test.ts +197 -197
  194. package/src/strategies/pathfind/escape-planner.ts +355 -355
  195. package/src/strategies/pathfind/walkable-grid.ts +117 -117
  196. package/src/strategies/patrol.ts +12 -12
  197. package/src/strategies/player-targets.ts +13 -13
  198. package/src/strategies/report-patrol.ts +12 -12
  199. package/src/strategies/shrimp-memory.knowledge.md +19 -19
  200. package/src/strategies/shrimp-memory.ts +26 -26
  201. package/src/strategies/social-task.test.ts +28 -28
  202. package/src/strategies/social-task.ts +50 -50
  203. package/src/strategies/spawn.ts +82 -82
  204. package/src/strategies/speech-module.ts +123 -123
  205. package/src/strategies/strategy-loop.test.ts +15 -0
  206. package/src/strategies/strategy-loop.ts +776 -771
  207. package/src/strategies/task-kill-report.ts +18 -18
  208. package/src/strategies/task-only.ts +12 -12
  209. package/src/strategies/task-report.ts +23 -23
  210. package/src/strategies/types.ts +109 -109
  211. package/src/strategies/warrior-memory.knowledge.md +21 -21
  212. package/src/strategies/warrior-memory.ts +17 -17
@@ -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
+ }