@myclaw163/clawclaw-cli 0.6.54

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 (198) hide show
  1. package/README.md +440 -0
  2. package/bin/clawclaw-cli.mjs +4 -0
  3. package/package.json +48 -0
  4. package/personas//347/220/206/346/231/272/346/270/251/345/222/214.md +23 -0
  5. package/personas//350/200/201/350/260/213/346/267/261/347/256/227.md +22 -0
  6. package/personas//350/257/232/346/201/263/347/233/264/347/216/207.md +22 -0
  7. package/personas//350/275/273/346/235/276/346/264/273/346/263/274.md +22 -0
  8. package/personas//351/207/216/346/200/247/345/217/233/351/200/206.md +23 -0
  9. package/scripts/postinstall.mjs +20 -0
  10. package/scripts/sync-bundled-skill.mjs +245 -0
  11. package/scripts/sync-bundled-skill.test.mjs +152 -0
  12. package/skills/clawclaw/SKILL.md +240 -0
  13. package/skills/clawclaw/references/CHATTERBOX.md +142 -0
  14. package/skills/clawclaw/references/COMMANDS.md +132 -0
  15. package/skills/clawclaw/references/GAME-MECHANICS.md +186 -0
  16. package/skills/clawclaw/references/HUB.md +48 -0
  17. package/skills/clawclaw/references/KNOWLEDGE.md +43 -0
  18. package/skills/clawclaw/references/STRATEGIES.md +57 -0
  19. package/skills/clawclaw/references/STREAM.md +58 -0
  20. package/skills/clawclaw/references/TACTICS.md +65 -0
  21. package/src/assets/clawclaw-ascii-map.txt +40 -0
  22. package/src/cli.ts +153 -0
  23. package/src/commands/_schema.ts +109 -0
  24. package/src/commands/account.ts +209 -0
  25. package/src/commands/config.ts +30 -0
  26. package/src/commands/do.test.ts +37 -0
  27. package/src/commands/do.ts +95 -0
  28. package/src/commands/events.ts +22 -0
  29. package/src/commands/game-map.test.ts +28 -0
  30. package/src/commands/game-start-plan.test.ts +142 -0
  31. package/src/commands/game.ts +882 -0
  32. package/src/commands/history-player.test.ts +102 -0
  33. package/src/commands/history.ts +573 -0
  34. package/src/commands/hub.test.ts +96 -0
  35. package/src/commands/hub.ts +234 -0
  36. package/src/commands/knowledge.test.ts +19 -0
  37. package/src/commands/knowledge.ts +168 -0
  38. package/src/commands/load.test.ts +51 -0
  39. package/src/commands/load.ts +13 -0
  40. package/src/commands/meeting-history.test.ts +106 -0
  41. package/src/commands/memory.ts +40 -0
  42. package/src/commands/peek.ts +38 -0
  43. package/src/commands/persona.ts +57 -0
  44. package/src/commands/setup/codex.ts +248 -0
  45. package/src/commands/setup/hermes.test.ts +96 -0
  46. package/src/commands/setup/hermes.ts +76 -0
  47. package/src/commands/setup/index.ts +13 -0
  48. package/src/commands/setup/openclaw.test.ts +114 -0
  49. package/src/commands/setup/openclaw.ts +147 -0
  50. package/src/commands/skill.ts +128 -0
  51. package/src/commands/state.ts +46 -0
  52. package/src/commands/strategy.test.ts +135 -0
  53. package/src/commands/strategy.ts +189 -0
  54. package/src/commands/tts.ts +128 -0
  55. package/src/commands/upgrade.test.ts +91 -0
  56. package/src/commands/upgrade.ts +154 -0
  57. package/src/commands/watch.test.ts +973 -0
  58. package/src/commands/watch.ts +709 -0
  59. package/src/lib/auth.test.ts +59 -0
  60. package/src/lib/auth.ts +186 -0
  61. package/src/lib/command-meta.ts +37 -0
  62. package/src/lib/game-client.ts +391 -0
  63. package/src/lib/host-config-patcher.test.ts +130 -0
  64. package/src/lib/host-config-patcher.ts +151 -0
  65. package/src/lib/http-keepalive.ts +15 -0
  66. package/src/lib/http-transport.test.ts +42 -0
  67. package/src/lib/http-transport.ts +113 -0
  68. package/src/lib/hub-client.test.ts +56 -0
  69. package/src/lib/hub-client.ts +88 -0
  70. package/src/lib/hub-install.test.ts +98 -0
  71. package/src/lib/hub-install.ts +121 -0
  72. package/src/lib/hub-reminder.ts +75 -0
  73. package/src/lib/hub-unzip.test.ts +69 -0
  74. package/src/lib/hub-unzip.ts +62 -0
  75. package/src/lib/init-command.test.ts +75 -0
  76. package/src/lib/init-command.ts +120 -0
  77. package/src/lib/knowledge-store.test.ts +180 -0
  78. package/src/lib/knowledge-store.ts +374 -0
  79. package/src/lib/load-context.test.ts +52 -0
  80. package/src/lib/load-context.ts +52 -0
  81. package/src/lib/match-state.test.ts +134 -0
  82. package/src/lib/match-state.ts +94 -0
  83. package/src/lib/netease-tts.ts +83 -0
  84. package/src/lib/normalize.ts +42 -0
  85. package/src/lib/persona.test.ts +41 -0
  86. package/src/lib/persona.ts +72 -0
  87. package/src/lib/server-registry.ts +152 -0
  88. package/src/lib/skill-version.test.ts +48 -0
  89. package/src/lib/skill-version.ts +19 -0
  90. package/src/lib/strategy-export.test.ts +232 -0
  91. package/src/lib/strategy-export.ts +242 -0
  92. package/src/lib/tts-keys.ts +7 -0
  93. package/src/lib/tts-speech.test.ts +63 -0
  94. package/src/lib/tts-speech.ts +76 -0
  95. package/src/lib/workspace-argv.test.ts +49 -0
  96. package/src/lib/workspace-argv.ts +44 -0
  97. package/src/perception/player-history-store.test.ts +87 -0
  98. package/src/perception/player-history-store.ts +194 -0
  99. package/src/pipeline/event-store.ts +124 -0
  100. package/src/pipeline/pipeline.ts +35 -0
  101. package/src/runtime/auto-upgrade.test.ts +66 -0
  102. package/src/runtime/auto-upgrade.ts +31 -0
  103. package/src/runtime/daemon.ts +100 -0
  104. package/src/runtime/event-daemon.test.ts +28 -0
  105. package/src/runtime/event-daemon.ts +371 -0
  106. package/src/runtime/opening-mover.ts +303 -0
  107. package/src/runtime/raw-ws-log.test.ts +33 -0
  108. package/src/runtime/raw-ws-log.ts +32 -0
  109. package/src/runtime/runtime-logger.ts +99 -0
  110. package/src/runtime/ws-client.test.ts +47 -0
  111. package/src/runtime/ws-client.ts +272 -0
  112. package/src/sdk/action.ts +166 -0
  113. package/src/sdk/index.ts +110 -0
  114. package/src/sdk/types.ts +146 -0
  115. package/src/strategies/avoid-lone.ts +11 -0
  116. package/src/strategies/avoid-players.knowledge.md +20 -0
  117. package/src/strategies/avoid-players.ts +15 -0
  118. package/src/strategies/corpse-patrol.ts +22 -0
  119. package/src/strategies/crab-sabotage.ts +21 -0
  120. package/src/strategies/custom-module.test.ts +269 -0
  121. package/src/strategies/find-player.ts +16 -0
  122. package/src/strategies/game-utils.test.ts +164 -0
  123. package/src/strategies/game-utils.ts +721 -0
  124. package/src/strategies/goals/avoid-lone-top.ts +168 -0
  125. package/src/strategies/goals/avoid-players-top.test.ts +83 -0
  126. package/src/strategies/goals/avoid-players-top.ts +121 -0
  127. package/src/strategies/goals/conversation-goal.ts +51 -0
  128. package/src/strategies/goals/corpse-patrol-top.ts +91 -0
  129. package/src/strategies/goals/crab-octopus-reflexes.ts +93 -0
  130. package/src/strategies/goals/crab-sabotage-top.ts +197 -0
  131. package/src/strategies/goals/emergency-hunt-goal.ts +28 -0
  132. package/src/strategies/goals/find-player-top.ts +93 -0
  133. package/src/strategies/goals/flee-players-goal.ts +53 -0
  134. package/src/strategies/goals/goal-manager.ts +41 -0
  135. package/src/strategies/goals/goal-root-strategy.ts +49 -0
  136. package/src/strategies/goals/goal.ts +28 -0
  137. package/src/strategies/goals/keep-away-goal.ts +206 -0
  138. package/src/strategies/goals/kill-frenzy-top.ts +80 -0
  139. package/src/strategies/goals/kill-lone-top.ts +160 -0
  140. package/src/strategies/goals/kill-target-goal.ts +59 -0
  141. package/src/strategies/goals/kill-target-top.ts +109 -0
  142. package/src/strategies/goals/leaf-goal.ts +25 -0
  143. package/src/strategies/goals/linger-corpse-goal.ts +79 -0
  144. package/src/strategies/goals/lone-kill-core.ts +82 -0
  145. package/src/strategies/goals/lone-kill-goal.ts +24 -0
  146. package/src/strategies/goals/lone-kill-task-top.test.ts +85 -0
  147. package/src/strategies/goals/lone-kill-task-top.ts +86 -0
  148. package/src/strategies/goals/move-room-goal.ts +60 -0
  149. package/src/strategies/goals/normal-shrimp-top.test.ts +80 -0
  150. package/src/strategies/goals/normal-shrimp-top.ts +242 -0
  151. package/src/strategies/goals/paradise-fish-top.test.ts +126 -0
  152. package/src/strategies/goals/paradise-fish-top.ts +219 -0
  153. package/src/strategies/goals/patrol-top.ts +57 -0
  154. package/src/strategies/goals/report-patrol-top.ts +80 -0
  155. package/src/strategies/goals/safe-task-goal.ts +102 -0
  156. package/src/strategies/goals/social-task-top.ts +161 -0
  157. package/src/strategies/goals/task-kill-report-top.ts +163 -0
  158. package/src/strategies/goals/task-only-top.ts +57 -0
  159. package/src/strategies/goals/task-or-patrol-goal.ts +41 -0
  160. package/src/strategies/goals/task-report-top.ts +57 -0
  161. package/src/strategies/goals/wander-task-goal.ts +33 -0
  162. package/src/strategies/goals/warrior-shrimp-top.test.ts +86 -0
  163. package/src/strategies/goals/warrior-shrimp-top.ts +248 -0
  164. package/src/strategies/greeting.ts +53 -0
  165. package/src/strategies/kill-frenzy.ts +12 -0
  166. package/src/strategies/kill-lone.knowledge.md +20 -0
  167. package/src/strategies/kill-lone.ts +13 -0
  168. package/src/strategies/kill-target.ts +18 -0
  169. package/src/strategies/loader.test.ts +678 -0
  170. package/src/strategies/loader.ts +172 -0
  171. package/src/strategies/lone-kill-task.ts +21 -0
  172. package/src/strategies/meeting-gate.test.ts +59 -0
  173. package/src/strategies/meeting-gate.ts +23 -0
  174. package/src/strategies/move-room.ts +15 -0
  175. package/src/strategies/new-events-backfill.ts +98 -0
  176. package/src/strategies/paradise-fish.knowledge.md +20 -0
  177. package/src/strategies/paradise-fish.ts +25 -0
  178. package/src/strategies/pathfind/clawclaw-walkable.bin +0 -0
  179. package/src/strategies/pathfind/distance-field.ts +150 -0
  180. package/src/strategies/pathfind/escape-planner.test.ts +197 -0
  181. package/src/strategies/pathfind/escape-planner.ts +348 -0
  182. package/src/strategies/pathfind/walkable-grid.ts +117 -0
  183. package/src/strategies/patrol.ts +11 -0
  184. package/src/strategies/player-targets.ts +13 -0
  185. package/src/strategies/report-patrol.ts +11 -0
  186. package/src/strategies/shrimp-memory.knowledge.md +20 -0
  187. package/src/strategies/shrimp-memory.ts +25 -0
  188. package/src/strategies/social-task.test.ts +28 -0
  189. package/src/strategies/social-task.ts +49 -0
  190. package/src/strategies/spawn.ts +71 -0
  191. package/src/strategies/speech-module.ts +123 -0
  192. package/src/strategies/strategy-loop.ts +757 -0
  193. package/src/strategies/task-kill-report.ts +17 -0
  194. package/src/strategies/task-only.ts +11 -0
  195. package/src/strategies/task-report.ts +22 -0
  196. package/src/strategies/types.ts +96 -0
  197. package/src/strategies/warrior-memory.knowledge.md +20 -0
  198. package/src/strategies/warrior-memory.ts +16 -0
@@ -0,0 +1,80 @@
1
+ import type { GameState } from '../../sdk/types.js';
2
+ import { Action } from '../../sdk/action.js';
3
+ import {
4
+ nearestKnownCorpse,
5
+ nearestReportableCorpse,
6
+ patrolStep,
7
+ PatrolState,
8
+ PROGRESS_INTERVAL_MS,
9
+ } from '../game-utils.js';
10
+ import type { BehaviorDecision, StrategyContext } from '../types.js';
11
+ import { Goal } from './goal.js';
12
+
13
+ export class ReportPatrolTop extends Goal {
14
+ private readonly patrol = new PatrolState();
15
+ private lastCorpseNotified = false;
16
+
17
+ tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
18
+ const reportDecision = this.tryReport(state, ctx);
19
+ if (reportDecision) {
20
+ this.emitProgress(state, ctx);
21
+ return [reportDecision];
22
+ }
23
+
24
+ const decisions = this.patrolDecision(state, ctx);
25
+ this.emitProgress(state, ctx);
26
+ return decisions;
27
+ }
28
+
29
+ private tryReport(state: GameState, ctx: StrategyContext): BehaviorDecision | null {
30
+ const reportable = nearestReportableCorpse(state);
31
+ if (reportable) {
32
+ ctx.reportCorpseTarget = null;
33
+ this.lastCorpseNotified = false;
34
+ ctx.notifications.push('发现尸体,正在报告!');
35
+ return { action: Action.report(reportable.name, reportable.room) };
36
+ }
37
+
38
+ const corpse = nearestKnownCorpse(state, ctx);
39
+ if (corpse) {
40
+ if (!this.lastCorpseNotified) {
41
+ this.lastCorpseNotified = true;
42
+ const label = corpse.name ? `${corpse.name}的尸体` : '尸体';
43
+ ctx.notifications.push(`视野内发现${label},正在靠近准备报告。`);
44
+ }
45
+ ctx.reportCorpseTarget = corpse;
46
+ return { action: Action.move({ x: corpse.x, y: corpse.y }) };
47
+ }
48
+
49
+ this.lastCorpseNotified = false;
50
+ ctx.reportCorpseTarget = null;
51
+ return null;
52
+ }
53
+
54
+ private patrolDecision(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
55
+ if (state.you.doing_task) return [];
56
+ return patrolStep(state, ctx, this.patrol, false);
57
+ }
58
+
59
+ private emitProgress(state: GameState, ctx: StrategyContext): void {
60
+ const now = Date.now();
61
+ if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
62
+ ctx.lastProgressNotifyAt = now;
63
+
64
+ const room = state.you.room ?? '未知';
65
+ const target = this.patrol.nextRoom(ctx);
66
+
67
+ let msg = `[进度] 当前在${room}`;
68
+ if (ctx.reportCorpseTarget) {
69
+ const label = ctx.reportCorpseTarget.name ?? '未知';
70
+ msg += `,正在靠近${label}的尸体准备报告。`;
71
+ } else if (target) {
72
+ msg += `,巡逻中,正在前往${target.name}寻找尸体。`;
73
+ } else {
74
+ msg += ',巡逻中。';
75
+ }
76
+ msg += ` 共${ctx.rooms.length}个房间。`;
77
+
78
+ ctx.notifications.push(msg);
79
+ }
80
+ }
@@ -0,0 +1,102 @@
1
+ import type { GameState, Position, TaskInfo } from '../../sdk/types.js';
2
+ import { Action } from '../../sdk/action.js';
3
+ import { dist, nearestSafeTask, PATROL_REACHED_DISTANCE, PatrolState, taskMoveDecision } from '../game-utils.js';
4
+ import type { BehaviorDecision, StrategyContext } from '../types.js';
5
+ import { Goal } from './goal.js';
6
+
7
+ export type ThreatPointResolver = (state: GameState, ctx: StrategyContext) => Position[];
8
+ const NON_CRAB = (t: TaskInfo) => t.faction !== 'crab';
9
+
10
+ export interface SafeTaskPlanOptions {
11
+ holdUnsafeCurrentForMs?: number;
12
+ }
13
+
14
+ /**
15
+ * 进阶普通虾的劳作循环:与 TaskOrPatrolGoal 同构,但任务选择换成 nearestSafeTask——
16
+ * 测地最近、威胁硬排除(任务点旁有威胁,或必经之路经过威胁附近都不选)、带粘性
17
+ * (当前任务仍合法就不换,杜绝摇摆;路被把守时粘性失效自动换任务)。
18
+ * 全部任务被排除或做完时按房间顺序巡逻。
19
+ */
20
+ export class SafeTaskOrPatrolGoal extends Goal {
21
+ private readonly patrol = new PatrolState();
22
+ private current: TaskInfo | null = null;
23
+ private holdUnsafeCurrentUntil = 0;
24
+
25
+ constructor(private readonly threatPoints: ThreatPointResolver) {
26
+ super();
27
+ }
28
+
29
+ /** 当前粘住的任务,供调用方播报进度(逃跑期间也能看到将回归的任务)。 */
30
+ get currentTask(): TaskInfo | null {
31
+ return this.current;
32
+ }
33
+
34
+ /** Refresh the remembered task without issuing a task move. Safe to call while fleeing. */
35
+ planTask(state: GameState, ctx: StrategyContext, opts: SafeTaskPlanOptions = {}): TaskInfo | null {
36
+ if (opts.holdUnsafeCurrentForMs != null) {
37
+ this.holdUnsafeCurrentUntil = Math.max(this.holdUnsafeCurrentUntil, Date.now() + opts.holdUnsafeCurrentForMs);
38
+ }
39
+ if (state.you.doing_task) return this.current;
40
+
41
+ const task = nearestSafeTask(state, ctx.taskData, NON_CRAB, ctx.emergency, ctx.blockedMoveTarget, ctx.knownCorpses, {
42
+ threatPoints: this.threatPoints(state, ctx),
43
+ stickyTaskName: this.current?.task_name ?? null,
44
+ });
45
+ this.current = task;
46
+ return task;
47
+ }
48
+
49
+ tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
50
+ if (state.you.doing_task) return [];
51
+
52
+ const locked = this.current ? this.safeCurrentTask(state, ctx) : this.planTask(state, ctx);
53
+ if (locked) {
54
+ this.current = locked;
55
+ return [taskMoveDecision(state, ctx, locked)];
56
+ }
57
+
58
+ if (this.current) {
59
+ const remembered = this.findCurrentTask(ctx);
60
+ if (remembered && this.isStillAssignable(remembered, ctx) && Date.now() < this.holdUnsafeCurrentUntil) return [];
61
+ this.current = null;
62
+ const replanned = this.planTask(state, ctx);
63
+ if (replanned) return [taskMoveDecision(state, ctx, replanned)];
64
+ }
65
+
66
+ let room = this.patrol.nextRoom(ctx);
67
+ if (!room) return [];
68
+ if (dist(state.you.x, state.you.y, room.x, room.y) <= PATROL_REACHED_DISTANCE) {
69
+ this.patrol.advance(ctx);
70
+ const next = this.patrol.nextRoom(ctx);
71
+ if (next) room = next;
72
+ }
73
+ return [{ action: Action.move({ x: room.x, y: room.y }) }];
74
+ }
75
+
76
+ private safeCurrentTask(state: GameState, ctx: StrategyContext): TaskInfo | null {
77
+ const current = this.findCurrentTask(ctx);
78
+ if (!current) return null;
79
+
80
+ const safe = nearestSafeTask(state, [current], NON_CRAB, null, ctx.blockedMoveTarget, ctx.knownCorpses, {
81
+ threatPoints: this.threatPoints(state, ctx),
82
+ stickyTaskName: current.task_name,
83
+ });
84
+ return safe;
85
+ }
86
+
87
+ private findCurrentTask(ctx: StrategyContext): TaskInfo | null {
88
+ if (!this.current) return null;
89
+ return ctx.taskData.find(t => t.task_id === this.current?.task_id)
90
+ ?? ctx.taskData.find(t => t.task_name === this.current?.task_name)
91
+ ?? null;
92
+ }
93
+
94
+ private isStillAssignable(task: TaskInfo, ctx: StrategyContext): boolean {
95
+ return task.status !== 'completed'
96
+ && task.status !== 'in_progress'
97
+ && task.x != null
98
+ && task.y != null
99
+ && (!ctx.blockedMoveTarget || dist(task.x, task.y, ctx.blockedMoveTarget.x, ctx.blockedMoveTarget.y) > 10)
100
+ && NON_CRAB(task);
101
+ }
102
+ }
@@ -0,0 +1,161 @@
1
+ import type { GameState } from '../../sdk/types.js';
2
+ import { Action } from '../../sdk/action.js';
3
+ import {
4
+ dist,
5
+ firstAvailableTask,
6
+ isTargetAlive,
7
+ matchesTarget,
8
+ PROGRESS_INTERVAL_MS,
9
+ reportCorpseDecision,
10
+ TASK_SUBMIT_RADIUS,
11
+ } from '../game-utils.js';
12
+ import type { BehaviorDecision, StrategyContext } from '../types.js';
13
+ import { Goal } from './goal.js';
14
+
15
+ export interface SocialTarget {
16
+ target: string;
17
+ greeting: string | null;
18
+ }
19
+
20
+ interface SocialTargetState {
21
+ greetingSent: boolean;
22
+ lastSpeechTime: number;
23
+ silenceNotified: boolean;
24
+ }
25
+
26
+ const GREETING_DISTANCE = 200;
27
+ const SILENCE_TIMEOUT_MS = 10_000;
28
+
29
+ export class SocialTaskTop extends Goal {
30
+ private readonly socialTargets: SocialTarget[];
31
+ private readonly targetStates = new Map<string, SocialTargetState>();
32
+
33
+ constructor(socialTargets: SocialTarget[]) {
34
+ super();
35
+ this.socialTargets = socialTargets;
36
+ for (const st of socialTargets) {
37
+ this.targetStates.set(st.target, {
38
+ greetingSent: false,
39
+ lastSpeechTime: 0,
40
+ silenceNotified: false,
41
+ });
42
+ }
43
+ }
44
+
45
+ tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
46
+ const decisions: BehaviorDecision[] = [];
47
+ const now = Date.now();
48
+
49
+ const corpseDecision = reportCorpseDecision(state, ctx);
50
+ if (corpseDecision) {
51
+ this.emitProgress(state, ctx);
52
+ return [corpseDecision];
53
+ }
54
+
55
+ for (const st of this.socialTargets) {
56
+ if (!isTargetAlive(state, st.target, ctx)) continue;
57
+
58
+ const visiblePlayer = state.players.find(p => matchesTarget(st.target, p, ctx));
59
+ if (!visiblePlayer) continue;
60
+
61
+ const targetState = this.targetStates.get(st.target)!;
62
+ const d = visiblePlayer.distance ?? dist(state.you.x, state.you.y, visiblePlayer.x, visiblePlayer.y);
63
+
64
+ const targetSpeechEvent = (state.new_events ?? []).find(
65
+ evt => evt.type === 'wandering_speech' && evt.actor_name === visiblePlayer.name,
66
+ );
67
+
68
+ if (targetSpeechEvent) {
69
+ targetState.lastSpeechTime = now;
70
+ targetState.silenceNotified = false;
71
+
72
+ const speechText = targetSpeechEvent.text ?? targetSpeechEvent.content ?? '';
73
+ const seatNum = visiblePlayer.seat ?? st.target;
74
+ ctx.notifications.push(`${seatNum}号跟我说话了:「${speechText}」`);
75
+ this.emitProgress(state, ctx);
76
+ return decisions;
77
+ }
78
+
79
+ if (st.greeting != null && !targetState.greetingSent) {
80
+ if (d > GREETING_DISTANCE) {
81
+ if (state.you.doing_task) {
82
+ this.emitProgress(state, ctx);
83
+ return decisions;
84
+ }
85
+ decisions.push({ action: Action.move({ x: visiblePlayer.x, y: visiblePlayer.y }) });
86
+ this.emitProgress(state, ctx);
87
+ return decisions;
88
+ }
89
+ decisions.push({ action: Action.speech(st.greeting) });
90
+ targetState.greetingSent = true;
91
+ targetState.lastSpeechTime = now;
92
+ targetState.silenceNotified = false;
93
+ const seatNum = visiblePlayer.seat ?? st.target;
94
+ ctx.notifications.push(`已向${seatNum}号打招呼:「${st.greeting}」`);
95
+ this.emitProgress(state, ctx);
96
+ return decisions;
97
+ }
98
+
99
+ if (targetState.greetingSent && targetState.lastSpeechTime > 0) {
100
+ if (now - targetState.lastSpeechTime >= SILENCE_TIMEOUT_MS) {
101
+ if (!targetState.silenceNotified) {
102
+ targetState.silenceNotified = true;
103
+ const seatNum = visiblePlayer.seat ?? st.target;
104
+ ctx.notifications.push(`${seatNum}号没回话,放弃等待,去做任务。`);
105
+ }
106
+ continue;
107
+ }
108
+ }
109
+
110
+ if (targetState.greetingSent) {
111
+ this.emitProgress(state, ctx);
112
+ return decisions;
113
+ }
114
+
115
+ }
116
+
117
+ const taskDecision = this.tryTask(state, ctx);
118
+ if (taskDecision) decisions.push(taskDecision);
119
+
120
+ this.emitProgress(state, ctx);
121
+ return decisions;
122
+ }
123
+
124
+ private tryTask(state: GameState, ctx: StrategyContext): BehaviorDecision | null {
125
+ const task = firstAvailableTask(ctx.taskData, t => t.faction !== 'crab', ctx.emergency, ctx.blockedMoveTarget);
126
+ if (!task) return null;
127
+
128
+ const isEmergencyTask = task === ctx.emergency;
129
+ if (state.you.doing_task && !isEmergencyTask) return null;
130
+
131
+ const d = dist(state.you.x, state.you.y, task.x!, task.y!);
132
+ if (d <= TASK_SUBMIT_RADIUS && Date.now() >= ctx.taskLocalBlockedUntil) {
133
+ return { action: Action.doTask(task.task_name) };
134
+ }
135
+ return { action: Action.move({ x: task.x!, y: task.y! }) };
136
+ }
137
+
138
+ private emitProgress(state: GameState, ctx: StrategyContext): void {
139
+ const now = Date.now();
140
+ if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
141
+ ctx.lastProgressNotifyAt = now;
142
+
143
+ const room = state.you.room ?? '未知';
144
+ const completed = ctx.taskData.filter(t => t.status === 'completed').length;
145
+ const total = ctx.taskData.filter(t => t.faction !== 'crab').length;
146
+
147
+ const visibleTargetNames = this.socialTargets
148
+ .filter(st => state.players.some(p => matchesTarget(st.target, p, ctx)))
149
+ .map(st => st.target);
150
+
151
+ let msg = `[进度] 当前在${room}。`;
152
+ if (visibleTargetNames.length > 0) {
153
+ msg += `社交目标在视野内: ${visibleTargetNames.join(', ')}。`;
154
+ } else {
155
+ msg += `社交目标不在视野内,正在做任务。`;
156
+ }
157
+ msg += ` 任务进度 ${completed}/${total}。`;
158
+
159
+ ctx.notifications.push(msg);
160
+ }
161
+ }
@@ -0,0 +1,163 @@
1
+ import type { GameState } from '../../sdk/types.js';
2
+ import { Action } from '../../sdk/action.js';
3
+ import {
4
+ canUseKill,
5
+ dist,
6
+ firstAvailableTask,
7
+ hasKillUseRemaining,
8
+ isTargetAlive,
9
+ killCooldownSecs,
10
+ KILL_RANGE,
11
+ LONE_FOLLOW_DISTANCE,
12
+ matchesTarget,
13
+ PROGRESS_INTERVAL_MS,
14
+ reportCorpseDecision,
15
+ TASK_SUBMIT_RADIUS,
16
+ } from '../game-utils.js';
17
+ import type { BehaviorDecision, StrategyContext } from '../types.js';
18
+ import { Goal } from './goal.js';
19
+ import { emitLeaf, URGENT_GOAL_PRIORITY, WANDER_GOAL_PRIORITY } from './leaf-goal.js';
20
+
21
+ type VisibleTargetMatch = {
22
+ player: GameState['players'][number];
23
+ dist: number;
24
+ };
25
+
26
+ export class TaskKillReportTop extends Goal {
27
+ private readonly targets: string[];
28
+ private readonly targetSpottedNotified = new Set<string>();
29
+
30
+ constructor(targets: string[]) {
31
+ super();
32
+ this.targets = [...targets];
33
+ }
34
+
35
+ tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
36
+ const allDead = this.targets.every(t => !isTargetAlive(state, t, ctx));
37
+ if (allDead) {
38
+ ctx.notifications.push('所有嫌疑目标都已死亡,策略结束。');
39
+ this.emitProgress(state, ctx);
40
+ this.clearSub();
41
+ return [];
42
+ }
43
+
44
+ const visibleMatches = this.visibleTargets(state, ctx);
45
+ const cd = killCooldownSecs(state);
46
+ const hasKill = hasKillUseRemaining(state);
47
+ const killReady = canUseKill(state);
48
+
49
+ if (visibleMatches.length > 0 && hasKill) {
50
+ const nearest = visibleMatches[0];
51
+
52
+ if (!this.targetSpottedNotified.has(nearest.player.name)) {
53
+ this.targetSpottedNotified.add(nearest.player.name);
54
+ ctx.notifications.push(`发现嫌疑目标${nearest.player.name}!`);
55
+ }
56
+
57
+ if (nearest.dist <= KILL_RANGE && killReady) {
58
+ ctx.notifications.push(`嫌疑目标${nearest.player.name}在攻击范围内,出刀!`);
59
+ return this.emitDecisions(state, ctx, [{ action: Action.kill(nearest.player.name) }], URGENT_GOAL_PRIORITY);
60
+ }
61
+
62
+ if (nearest.dist > KILL_RANGE && killReady) {
63
+ return this.emitDecisions(
64
+ state,
65
+ ctx,
66
+ [{ action: Action.move({ x: nearest.player.x, y: nearest.player.y }) }],
67
+ URGENT_GOAL_PRIORITY,
68
+ );
69
+ }
70
+
71
+ if (!killReady) {
72
+ if (nearest.dist > LONE_FOLLOW_DISTANCE) {
73
+ return this.emitDecisions(
74
+ state,
75
+ ctx,
76
+ [{ action: Action.move({ x: nearest.player.x, y: nearest.player.y }) }],
77
+ URGENT_GOAL_PRIORITY,
78
+ );
79
+ }
80
+ this.emitProgress(state, ctx);
81
+ this.clearSub();
82
+ return [];
83
+ }
84
+ } else {
85
+ this.targetSpottedNotified.clear();
86
+ }
87
+
88
+ const reportDecision = reportCorpseDecision(state, ctx);
89
+ if (reportDecision) {
90
+ return this.emitDecisions(state, ctx, [reportDecision], URGENT_GOAL_PRIORITY);
91
+ }
92
+
93
+ const taskDecision = this.tryTask(state, ctx);
94
+ if (taskDecision) {
95
+ return this.emitDecisions(state, ctx, [taskDecision], WANDER_GOAL_PRIORITY);
96
+ }
97
+
98
+ this.emitProgress(state, ctx);
99
+ this.clearSub();
100
+ return [];
101
+ }
102
+
103
+ private visibleTargets(state: GameState, ctx: StrategyContext): VisibleTargetMatch[] {
104
+ const aliveTargets = this.targets.filter(t => isTargetAlive(state, t, ctx));
105
+ return state.players
106
+ .map(player => {
107
+ if (!aliveTargets.some(t => matchesTarget(t, player, ctx))) return null;
108
+ const d = player.distance ?? dist(state.you.x, state.you.y, player.x, player.y);
109
+ return { player, dist: d };
110
+ })
111
+ .filter((match): match is VisibleTargetMatch => match !== null)
112
+ .sort((a, b) => a.dist - b.dist);
113
+ }
114
+
115
+ private tryTask(state: GameState, ctx: StrategyContext): BehaviorDecision | null {
116
+ if (state.you.doing_task) return null;
117
+
118
+ const task = firstAvailableTask(ctx.taskData, t => t.faction !== 'crab', ctx.emergency, ctx.blockedMoveTarget);
119
+ if (!task) return null;
120
+
121
+ const d = dist(state.you.x, state.you.y, task.x!, task.y!);
122
+ if (d <= TASK_SUBMIT_RADIUS && Date.now() >= ctx.taskLocalBlockedUntil) {
123
+ return { action: Action.doTask(task.task_name) };
124
+ }
125
+ return { action: Action.move({ x: task.x!, y: task.y! }) };
126
+ }
127
+
128
+ private emitDecisions(
129
+ state: GameState,
130
+ ctx: StrategyContext,
131
+ decisions: BehaviorDecision[],
132
+ priority: number,
133
+ ): BehaviorDecision[] {
134
+ this.emitProgress(state, ctx);
135
+ return emitLeaf(this, decisions, priority);
136
+ }
137
+
138
+ private clearSub(): void {
139
+ if (this.subGoal) this.removeSubGoal();
140
+ }
141
+
142
+ private emitProgress(state: GameState, ctx: StrategyContext): void {
143
+ const now = Date.now();
144
+ if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
145
+ ctx.lastProgressNotifyAt = now;
146
+
147
+ const room = state.you.room ?? '未知';
148
+ const cd = killCooldownSecs(state);
149
+ const killsRemaining = state.you.kills_remaining;
150
+ const targetNames = this.targets.join(', ');
151
+ const aliveCount = this.targets.filter(t => isTargetAlive(state, t, ctx)).length;
152
+
153
+ const completed = ctx.taskData.filter(t => t.status === 'completed').length;
154
+ const total = ctx.taskData.filter(t => t.faction !== 'crab').length;
155
+
156
+ let msg = `[进度] 当前在${room},嫌疑目标: ${targetNames}(存活${aliveCount}/${this.targets.length})。`;
157
+ if (cd > 0) msg += `攻击冷却中(${cd}s)。`;
158
+ if (killsRemaining === 0) msg += '出刀次数已用完。';
159
+ msg += ` 任务进度 ${completed}/${total}。`;
160
+
161
+ ctx.notifications.push(msg);
162
+ }
163
+ }
@@ -0,0 +1,57 @@
1
+ import type { GameState } from '../../sdk/types.js';
2
+ import { firstAvailableTask, PROGRESS_INTERVAL_MS, taskMoveDecision } from '../game-utils.js';
3
+ import type { BehaviorDecision, StrategyContext } from '../types.js';
4
+ import { Goal } from './goal.js';
5
+
6
+ export class TaskOnlyTop extends Goal {
7
+ private lastEmergencyNotified = false;
8
+
9
+ tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
10
+ if (state.emergency && !this.lastEmergencyNotified) {
11
+ this.lastEmergencyNotified = true;
12
+ ctx.notifications.push(`紧急任务出现!优先处理。`);
13
+ } else if (!state.emergency) {
14
+ this.lastEmergencyNotified = false;
15
+ }
16
+
17
+ const task = firstAvailableTask(ctx.taskData, () => true, ctx.emergency, ctx.blockedMoveTarget);
18
+ if (!task) {
19
+ this.emitProgress(state, ctx);
20
+ return [];
21
+ }
22
+
23
+ if (state.you.doing_task && task !== ctx.emergency) {
24
+ this.emitProgress(state, ctx);
25
+ return [];
26
+ }
27
+
28
+ const decisions = [taskMoveDecision(state, ctx, task)];
29
+ this.emitProgress(state, ctx);
30
+ return decisions;
31
+ }
32
+
33
+ private emitProgress(state: GameState, ctx: StrategyContext): void {
34
+ const now = Date.now();
35
+ if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
36
+ ctx.lastProgressNotifyAt = now;
37
+
38
+ const room = state.you.room ?? '未知';
39
+ const completed = ctx.taskData.filter(t => t.status === 'completed').length;
40
+ const total = ctx.taskData.length;
41
+ const current = firstAvailableTask(ctx.taskData, () => true, ctx.emergency);
42
+
43
+ let msg = `[进度] 当前在${room}`;
44
+ if (current) {
45
+ msg += `,正在前往${current.room}做任务「${current.task_name}」`;
46
+ } else {
47
+ msg += `,没有可用任务`;
48
+ }
49
+ msg += `。已完成 ${completed}/${total} 个任务。`;
50
+
51
+ if (state.emergency) {
52
+ msg += ` 紧急任务进行中!`;
53
+ }
54
+
55
+ ctx.notifications.push(msg);
56
+ }
57
+ }
@@ -0,0 +1,41 @@
1
+ import type { GameState, TaskInfo } from '../../sdk/types.js';
2
+ import { Action } from '../../sdk/action.js';
3
+ import { dist, firstAvailableTask, PATROL_REACHED_DISTANCE, PatrolState, taskMoveDecision } from '../game-utils.js';
4
+ import type { BehaviorDecision, StrategyContext } from '../types.js';
5
+ import { Goal } from './goal.js';
6
+
7
+ export type TaskPredicate = (task: TaskInfo) => boolean;
8
+ const NON_CRAB: TaskPredicate = t => t.faction !== 'crab';
9
+
10
+ /**
11
+ * 好人虾的默认劳作循环:有可做任务(含紧急任务,由 firstAvailableTask 自动优先)就去做,
12
+ * 没任务就按房间顺序巡逻。抽自 task-report.tryTask,供 task-report / 武士虾共用
13
+ * (进阶普通虾用其安全选任务变体 SafeTaskOrPatrolGoal)。
14
+ *
15
+ * 直接产出 move/doTask 决策、不挂子目标,因此既能作为子目标接入 GoalManager,
16
+ * 也能被编排型 Goal 持有并直接 tick(见 WarriorShrimpTop)。
17
+ * 自身不发进度通知,进度由调用方按场景统一播报。
18
+ */
19
+ export class TaskOrPatrolGoal extends Goal {
20
+ private readonly patrol = new PatrolState();
21
+
22
+ constructor(private readonly predicate: TaskPredicate = NON_CRAB) {
23
+ super();
24
+ }
25
+
26
+ tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
27
+ if (state.you.doing_task) return [];
28
+
29
+ const task = firstAvailableTask(ctx.taskData, this.predicate, ctx.emergency, ctx.blockedMoveTarget);
30
+ if (task) return [taskMoveDecision(state, ctx, task)];
31
+
32
+ let room = this.patrol.nextRoom(ctx);
33
+ if (!room) return [];
34
+ if (dist(state.you.x, state.you.y, room.x, room.y) <= PATROL_REACHED_DISTANCE) {
35
+ this.patrol.advance(ctx);
36
+ const next = this.patrol.nextRoom(ctx);
37
+ if (next) room = next;
38
+ }
39
+ return [{ action: Action.move({ x: room.x, y: room.y }) }];
40
+ }
41
+ }
@@ -0,0 +1,57 @@
1
+ import type { GameState } from '../../sdk/types.js';
2
+ import type { BehaviorDecision, StrategyContext } from '../types.js';
3
+ import { firstAvailableTask, reportCorpseDecision, PROGRESS_INTERVAL_MS } from '../game-utils.js';
4
+ import { GreetingTracker } from '../greeting.js';
5
+ import { Goal } from './goal.js';
6
+ import { TaskOrPatrolGoal } from './task-or-patrol-goal.js';
7
+
8
+ export class TaskReportTop extends Goal {
9
+ private readonly taskGoal = new TaskOrPatrolGoal();
10
+ private readonly greeting: GreetingTracker;
11
+
12
+ constructor(greetingPhrases: string[]) {
13
+ super();
14
+ this.greeting = new GreetingTracker(greetingPhrases);
15
+ }
16
+
17
+ tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
18
+ const decisions: BehaviorDecision[] = [];
19
+
20
+ const greetingDecision = this.greeting.tryGreeting(state);
21
+ if (greetingDecision) decisions.push(greetingDecision);
22
+
23
+ const reportDecision = reportCorpseDecision(state, ctx, { respectBlock: true });
24
+ if (reportDecision) {
25
+ decisions.unshift(reportDecision);
26
+ return decisions;
27
+ }
28
+
29
+ decisions.push(...this.taskGoal.tick(state, ctx));
30
+
31
+ this.emitProgress(state, ctx);
32
+ return decisions;
33
+ }
34
+
35
+ private emitProgress(state: GameState, ctx: StrategyContext): void {
36
+ const now = Date.now();
37
+ if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
38
+ ctx.lastProgressNotifyAt = now;
39
+
40
+ const completed = ctx.taskData.filter(t => t.status === 'completed').length;
41
+ const total = ctx.taskData.filter(t => t.faction !== 'crab').length;
42
+ const current = firstAvailableTask(ctx.taskData, t => t.faction !== 'crab', ctx.emergency, ctx.blockedMoveTarget);
43
+ const room = state.you.room ?? '未知';
44
+
45
+ let msg = `[进度] 当前在${room}`;
46
+ if (current) {
47
+ msg += `,正在前往${current.room}做任务「${current.task_name}」`;
48
+ }
49
+ msg += `。已完成 ${completed}/${total} 个任务。`;
50
+
51
+ if (state.emergency) {
52
+ msg += ` 紧急任务进行中!`;
53
+ }
54
+
55
+ ctx.notifications.push(msg);
56
+ }
57
+ }