@myclaw163/clawclaw-cli 0.6.76 → 0.6.77

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