@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.
- package/README.md +440 -0
- package/bin/clawclaw-cli.mjs +4 -0
- package/package.json +48 -0
- package/personas//347/220/206/346/231/272/346/270/251/345/222/214.md +23 -0
- package/personas//350/200/201/350/260/213/346/267/261/347/256/227.md +22 -0
- package/personas//350/257/232/346/201/263/347/233/264/347/216/207.md +22 -0
- package/personas//350/275/273/346/235/276/346/264/273/346/263/274.md +22 -0
- package/personas//351/207/216/346/200/247/345/217/233/351/200/206.md +23 -0
- package/scripts/postinstall.mjs +20 -0
- package/scripts/sync-bundled-skill.mjs +245 -0
- package/scripts/sync-bundled-skill.test.mjs +152 -0
- package/skills/clawclaw/SKILL.md +240 -0
- package/skills/clawclaw/references/CHATTERBOX.md +142 -0
- package/skills/clawclaw/references/COMMANDS.md +132 -0
- package/skills/clawclaw/references/GAME-MECHANICS.md +186 -0
- package/skills/clawclaw/references/HUB.md +48 -0
- package/skills/clawclaw/references/KNOWLEDGE.md +43 -0
- package/skills/clawclaw/references/STRATEGIES.md +57 -0
- package/skills/clawclaw/references/STREAM.md +58 -0
- package/skills/clawclaw/references/TACTICS.md +65 -0
- package/src/assets/clawclaw-ascii-map.txt +40 -0
- package/src/cli.ts +153 -0
- package/src/commands/_schema.ts +109 -0
- package/src/commands/account.ts +209 -0
- package/src/commands/config.ts +30 -0
- package/src/commands/do.test.ts +37 -0
- package/src/commands/do.ts +95 -0
- package/src/commands/events.ts +22 -0
- package/src/commands/game-map.test.ts +28 -0
- package/src/commands/game-start-plan.test.ts +142 -0
- package/src/commands/game.ts +882 -0
- package/src/commands/history-player.test.ts +102 -0
- package/src/commands/history.ts +573 -0
- package/src/commands/hub.test.ts +96 -0
- package/src/commands/hub.ts +234 -0
- package/src/commands/knowledge.test.ts +19 -0
- package/src/commands/knowledge.ts +168 -0
- package/src/commands/load.test.ts +51 -0
- package/src/commands/load.ts +13 -0
- package/src/commands/meeting-history.test.ts +106 -0
- package/src/commands/memory.ts +40 -0
- package/src/commands/peek.ts +38 -0
- package/src/commands/persona.ts +57 -0
- package/src/commands/setup/codex.ts +248 -0
- package/src/commands/setup/hermes.test.ts +96 -0
- package/src/commands/setup/hermes.ts +76 -0
- package/src/commands/setup/index.ts +13 -0
- package/src/commands/setup/openclaw.test.ts +114 -0
- package/src/commands/setup/openclaw.ts +147 -0
- package/src/commands/skill.ts +128 -0
- package/src/commands/state.ts +46 -0
- package/src/commands/strategy.test.ts +135 -0
- package/src/commands/strategy.ts +189 -0
- package/src/commands/tts.ts +128 -0
- package/src/commands/upgrade.test.ts +91 -0
- package/src/commands/upgrade.ts +154 -0
- package/src/commands/watch.test.ts +973 -0
- package/src/commands/watch.ts +709 -0
- package/src/lib/auth.test.ts +59 -0
- package/src/lib/auth.ts +186 -0
- package/src/lib/command-meta.ts +37 -0
- package/src/lib/game-client.ts +391 -0
- package/src/lib/host-config-patcher.test.ts +130 -0
- package/src/lib/host-config-patcher.ts +151 -0
- package/src/lib/http-keepalive.ts +15 -0
- package/src/lib/http-transport.test.ts +42 -0
- package/src/lib/http-transport.ts +113 -0
- package/src/lib/hub-client.test.ts +56 -0
- package/src/lib/hub-client.ts +88 -0
- package/src/lib/hub-install.test.ts +98 -0
- package/src/lib/hub-install.ts +121 -0
- package/src/lib/hub-reminder.ts +75 -0
- package/src/lib/hub-unzip.test.ts +69 -0
- package/src/lib/hub-unzip.ts +62 -0
- package/src/lib/init-command.test.ts +75 -0
- package/src/lib/init-command.ts +120 -0
- package/src/lib/knowledge-store.test.ts +180 -0
- package/src/lib/knowledge-store.ts +374 -0
- package/src/lib/load-context.test.ts +52 -0
- package/src/lib/load-context.ts +52 -0
- package/src/lib/match-state.test.ts +134 -0
- package/src/lib/match-state.ts +94 -0
- package/src/lib/netease-tts.ts +83 -0
- package/src/lib/normalize.ts +42 -0
- package/src/lib/persona.test.ts +41 -0
- package/src/lib/persona.ts +72 -0
- package/src/lib/server-registry.ts +152 -0
- package/src/lib/skill-version.test.ts +48 -0
- package/src/lib/skill-version.ts +19 -0
- package/src/lib/strategy-export.test.ts +232 -0
- package/src/lib/strategy-export.ts +242 -0
- package/src/lib/tts-keys.ts +7 -0
- package/src/lib/tts-speech.test.ts +63 -0
- package/src/lib/tts-speech.ts +76 -0
- package/src/lib/workspace-argv.test.ts +49 -0
- package/src/lib/workspace-argv.ts +44 -0
- package/src/perception/player-history-store.test.ts +87 -0
- package/src/perception/player-history-store.ts +194 -0
- package/src/pipeline/event-store.ts +124 -0
- package/src/pipeline/pipeline.ts +35 -0
- package/src/runtime/auto-upgrade.test.ts +66 -0
- package/src/runtime/auto-upgrade.ts +31 -0
- package/src/runtime/daemon.ts +100 -0
- package/src/runtime/event-daemon.test.ts +28 -0
- package/src/runtime/event-daemon.ts +371 -0
- package/src/runtime/opening-mover.ts +303 -0
- package/src/runtime/raw-ws-log.test.ts +33 -0
- package/src/runtime/raw-ws-log.ts +32 -0
- package/src/runtime/runtime-logger.ts +99 -0
- package/src/runtime/ws-client.test.ts +47 -0
- package/src/runtime/ws-client.ts +272 -0
- package/src/sdk/action.ts +166 -0
- package/src/sdk/index.ts +110 -0
- package/src/sdk/types.ts +146 -0
- package/src/strategies/avoid-lone.ts +11 -0
- package/src/strategies/avoid-players.knowledge.md +20 -0
- package/src/strategies/avoid-players.ts +15 -0
- package/src/strategies/corpse-patrol.ts +22 -0
- package/src/strategies/crab-sabotage.ts +21 -0
- package/src/strategies/custom-module.test.ts +269 -0
- package/src/strategies/find-player.ts +16 -0
- package/src/strategies/game-utils.test.ts +164 -0
- package/src/strategies/game-utils.ts +721 -0
- package/src/strategies/goals/avoid-lone-top.ts +168 -0
- package/src/strategies/goals/avoid-players-top.test.ts +83 -0
- package/src/strategies/goals/avoid-players-top.ts +121 -0
- package/src/strategies/goals/conversation-goal.ts +51 -0
- package/src/strategies/goals/corpse-patrol-top.ts +91 -0
- package/src/strategies/goals/crab-octopus-reflexes.ts +93 -0
- package/src/strategies/goals/crab-sabotage-top.ts +197 -0
- package/src/strategies/goals/emergency-hunt-goal.ts +28 -0
- package/src/strategies/goals/find-player-top.ts +93 -0
- package/src/strategies/goals/flee-players-goal.ts +53 -0
- package/src/strategies/goals/goal-manager.ts +41 -0
- package/src/strategies/goals/goal-root-strategy.ts +49 -0
- package/src/strategies/goals/goal.ts +28 -0
- package/src/strategies/goals/keep-away-goal.ts +206 -0
- package/src/strategies/goals/kill-frenzy-top.ts +80 -0
- package/src/strategies/goals/kill-lone-top.ts +160 -0
- package/src/strategies/goals/kill-target-goal.ts +59 -0
- package/src/strategies/goals/kill-target-top.ts +109 -0
- package/src/strategies/goals/leaf-goal.ts +25 -0
- package/src/strategies/goals/linger-corpse-goal.ts +79 -0
- package/src/strategies/goals/lone-kill-core.ts +82 -0
- package/src/strategies/goals/lone-kill-goal.ts +24 -0
- package/src/strategies/goals/lone-kill-task-top.test.ts +85 -0
- package/src/strategies/goals/lone-kill-task-top.ts +86 -0
- package/src/strategies/goals/move-room-goal.ts +60 -0
- package/src/strategies/goals/normal-shrimp-top.test.ts +80 -0
- package/src/strategies/goals/normal-shrimp-top.ts +242 -0
- package/src/strategies/goals/paradise-fish-top.test.ts +126 -0
- package/src/strategies/goals/paradise-fish-top.ts +219 -0
- package/src/strategies/goals/patrol-top.ts +57 -0
- package/src/strategies/goals/report-patrol-top.ts +80 -0
- package/src/strategies/goals/safe-task-goal.ts +102 -0
- package/src/strategies/goals/social-task-top.ts +161 -0
- package/src/strategies/goals/task-kill-report-top.ts +163 -0
- package/src/strategies/goals/task-only-top.ts +57 -0
- package/src/strategies/goals/task-or-patrol-goal.ts +41 -0
- package/src/strategies/goals/task-report-top.ts +57 -0
- package/src/strategies/goals/wander-task-goal.ts +33 -0
- package/src/strategies/goals/warrior-shrimp-top.test.ts +86 -0
- package/src/strategies/goals/warrior-shrimp-top.ts +248 -0
- package/src/strategies/greeting.ts +53 -0
- package/src/strategies/kill-frenzy.ts +12 -0
- package/src/strategies/kill-lone.knowledge.md +20 -0
- package/src/strategies/kill-lone.ts +13 -0
- package/src/strategies/kill-target.ts +18 -0
- package/src/strategies/loader.test.ts +678 -0
- package/src/strategies/loader.ts +172 -0
- package/src/strategies/lone-kill-task.ts +21 -0
- package/src/strategies/meeting-gate.test.ts +59 -0
- package/src/strategies/meeting-gate.ts +23 -0
- package/src/strategies/move-room.ts +15 -0
- package/src/strategies/new-events-backfill.ts +98 -0
- package/src/strategies/paradise-fish.knowledge.md +20 -0
- package/src/strategies/paradise-fish.ts +25 -0
- package/src/strategies/pathfind/clawclaw-walkable.bin +0 -0
- package/src/strategies/pathfind/distance-field.ts +150 -0
- package/src/strategies/pathfind/escape-planner.test.ts +197 -0
- package/src/strategies/pathfind/escape-planner.ts +348 -0
- package/src/strategies/pathfind/walkable-grid.ts +117 -0
- package/src/strategies/patrol.ts +11 -0
- package/src/strategies/player-targets.ts +13 -0
- package/src/strategies/report-patrol.ts +11 -0
- package/src/strategies/shrimp-memory.knowledge.md +20 -0
- package/src/strategies/shrimp-memory.ts +25 -0
- package/src/strategies/social-task.test.ts +28 -0
- package/src/strategies/social-task.ts +49 -0
- package/src/strategies/spawn.ts +71 -0
- package/src/strategies/speech-module.ts +123 -0
- package/src/strategies/strategy-loop.ts +757 -0
- package/src/strategies/task-kill-report.ts +17 -0
- package/src/strategies/task-only.ts +11 -0
- package/src/strategies/task-report.ts +22 -0
- package/src/strategies/types.ts +96 -0
- package/src/strategies/warrior-memory.knowledge.md +20 -0
- package/src/strategies/warrior-memory.ts +16 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import type { GameState, TaskInfo } from '../../sdk/types.js';
|
|
2
|
+
import { Action } from '../../sdk/action.js';
|
|
3
|
+
import {
|
|
4
|
+
approachOrDoTaskStep,
|
|
5
|
+
firstAvailableTask,
|
|
6
|
+
killCooldownSecs,
|
|
7
|
+
nonTeammatesVisible,
|
|
8
|
+
PatrolState,
|
|
9
|
+
PROGRESS_INTERVAL_MS,
|
|
10
|
+
} from '../game-utils.js';
|
|
11
|
+
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
12
|
+
import { Goal } from './goal.js';
|
|
13
|
+
import { LoneKillCore } from './lone-kill-core.js';
|
|
14
|
+
import { LoneKillGoal } from './lone-kill-goal.js';
|
|
15
|
+
import { WanderTaskGoal } from './wander-task-goal.js';
|
|
16
|
+
import { EmergencyHuntGoal } from './emergency-hunt-goal.js';
|
|
17
|
+
import { emitLeaf, setBehavior, URGENT_GOAL_PRIORITY, WANDER_GOAL_PRIORITY } from './leaf-goal.js';
|
|
18
|
+
import { corpseReportWithNonTeammate, immediateLoneKillDecision } from './crab-octopus-reflexes.js';
|
|
19
|
+
|
|
20
|
+
const SABOTAGE_ALARM_DEDUPE_MS = 90_000;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 蟹的「破坏 + 落单猎杀 + 任务伪装」编排 top。本身不产决策,每 tick 自上而下判定该走哪条分支、
|
|
24
|
+
* 把对应**叶子子目标**挂为唯一子目标(产出动作的是叶子;URGENT/WANDER 优先级供 ConversationGoal 判断):
|
|
25
|
+
*
|
|
26
|
+
* 0a. 反射·立刻出刀(crab-octopus-reflexes,URGENT):落单非队友贴脸(≤KILL_RANGE)且刀好 → 立刻出刀,
|
|
27
|
+
* 无视暂停窗口、不管自己是否正在做破坏任务。
|
|
28
|
+
* 0b. 反射·报尸(crab-octopus-reflexes,URGENT):有尸体且附近有非队友 → 报警/靠近报警,不管尸体是谁造成的。
|
|
29
|
+
* 1a. 破坏完成 → 触发紧急警报(一次性动作,LeafGoal 承载,URGENT)。
|
|
30
|
+
* 1b. 去做真实破坏任务(到点遇阻返回 null → 回退去猎杀,故 LeafGoal 承载,WANDER);破坏点紧挨已知尸体时跳过。
|
|
31
|
+
* 1c. 正在做任务(doing_task)→ 本 tick 不产决策,不撇下任务去猎杀(0a 的贴脸反射刀仍生效)。
|
|
32
|
+
* 2. 落单猎杀(LoneKillGoal,URGENT):assess 判 hunt(刀好且有落单目标)→ 叶子 pursue;
|
|
33
|
+
* 刀在冷却时 assess 返回 idle,不追不等,直接下沉到破坏/任务/巡逻。
|
|
34
|
+
* 3. 紧急期搜猎(EmergencyHuntGoal,URGENT):往人少处找真正落单的,永远有动作(不在人前乱杀)。
|
|
35
|
+
* 4. 任务伪装 / 巡逻(WanderTaskGoal,WANDER):先真实任务再虾的伪装任务,跳过紧挨已知尸体的任务,没任务就巡逻。
|
|
36
|
+
*/
|
|
37
|
+
export class CrabSabotageTop extends Goal {
|
|
38
|
+
private readonly killCore = new LoneKillCore();
|
|
39
|
+
private readonly patrol = new PatrolState();
|
|
40
|
+
private readonly triggeredSabotageTasks = new Map<string, { at: number; tick: number | null }>();
|
|
41
|
+
private readonly loneKillGoal = new LoneKillGoal(this.killCore);
|
|
42
|
+
private readonly emergencyHuntGoal = new EmergencyHuntGoal(this.killCore, this.patrol);
|
|
43
|
+
private readonly wanderGoal = new WanderTaskGoal(
|
|
44
|
+
(state, ctx) =>
|
|
45
|
+
firstAvailableTask(ctx.taskData, t => this.isWorkableTask(t) && !t.is_fake_shrimp, undefined, ctx.blockedMoveTarget, ctx.knownCorpses)
|
|
46
|
+
?? firstAvailableTask(ctx.taskData, t => this.isWorkableTask(t) && !!t.is_fake_shrimp, undefined, ctx.blockedMoveTarget, ctx.knownCorpses),
|
|
47
|
+
() => this.killCore.patrolStopOnPlayer(),
|
|
48
|
+
this.patrol,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
onMeetingResume(): void {
|
|
52
|
+
this.killCore.reset();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
56
|
+
this.emitProgress(state, ctx);
|
|
57
|
+
|
|
58
|
+
const immediateKill = immediateLoneKillDecision(state, ctx);
|
|
59
|
+
if (immediateKill) return emitLeaf(this, immediateKill, URGENT_GOAL_PRIORITY);
|
|
60
|
+
|
|
61
|
+
const corpseReport = corpseReportWithNonTeammate(state, ctx);
|
|
62
|
+
if (corpseReport) return emitLeaf(this, corpseReport, URGENT_GOAL_PRIORITY);
|
|
63
|
+
|
|
64
|
+
// 1a. 破坏完成 → 触发紧急警报(紧急一次性动作)
|
|
65
|
+
const alarm = this.trySabotageAlarm(state, ctx);
|
|
66
|
+
if (alarm) return emitLeaf(this, alarm, URGENT_GOAL_PRIORITY);
|
|
67
|
+
|
|
68
|
+
// 1b. 去做真实破坏任务(游走类工作;到点遇阻返回 null → 下沉去猎杀,故可回退)
|
|
69
|
+
const crabTask = this.tryCrabTask(state, ctx);
|
|
70
|
+
if (crabTask) return emitLeaf(this, crabTask, WANDER_GOAL_PRIORITY);
|
|
71
|
+
|
|
72
|
+
if (state.you.doing_task) {
|
|
73
|
+
if (this.subGoal) this.removeSubGoal();
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 2. 落单猎杀(紧急;紧急期 bypass 暂停窗口持续盯防)
|
|
78
|
+
const target = this.killCore.assess(state, ctx, { bypassIgnoreWindow: !!ctx.emergency });
|
|
79
|
+
if (target.kind === 'hunt') {
|
|
80
|
+
this.loneKillGoal.setTarget(target.target);
|
|
81
|
+
return setBehavior(this, this.loneKillGoal, URGENT_GOAL_PRIORITY);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 3. 紧急期搜猎(紧急):虾群涌向维修点,蟹不做任务,往人少处找真正落单的(警报期照样会被举报,不在人前乱杀)
|
|
85
|
+
if (ctx.emergency) return setBehavior(this, this.emergencyHuntGoal, URGENT_GOAL_PRIORITY);
|
|
86
|
+
|
|
87
|
+
// 4. 任务伪装 / 巡逻(游走)
|
|
88
|
+
return setBehavior(this, this.wanderGoal, WANDER_GOAL_PRIORITY);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ── 1a. 破坏完成 → 触发紧急警报 ──
|
|
92
|
+
private trySabotageAlarm(state: GameState, ctx: StrategyContext): BehaviorDecision[] | null {
|
|
93
|
+
if (state.you.faction !== 'crab') return null;
|
|
94
|
+
if (ctx.emergency) return null;
|
|
95
|
+
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const completed = ctx.taskData.find(t =>
|
|
98
|
+
t.faction === 'crab'
|
|
99
|
+
&& !t.is_fake_shrimp
|
|
100
|
+
&& t.status === 'completed'
|
|
101
|
+
&& this.shouldTriggerSabotageAlarm(t, state, now),
|
|
102
|
+
);
|
|
103
|
+
if (completed && !state.you.doing_task) {
|
|
104
|
+
this.markSabotageTriggered(completed, state, now);
|
|
105
|
+
ctx.alarmDone = true;
|
|
106
|
+
ctx.notifications.push('破坏任务完成,开启紧急警报!警报期照样会被举报,只清落单目标。');
|
|
107
|
+
return [{ action: Action.triggerAlarm() }];
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── 1b. 去做真实破坏任务(破坏点紧挨已知尸体时跳过;到点遇阻返回 null,让上层回退去猎杀)──
|
|
113
|
+
private tryCrabTask(state: GameState, ctx: StrategyContext): BehaviorDecision[] | null {
|
|
114
|
+
if (state.you.faction !== 'crab') return null;
|
|
115
|
+
if (ctx.emergency) return null;
|
|
116
|
+
if (state.you.doing_task) return null;
|
|
117
|
+
|
|
118
|
+
const task = firstAvailableTask(
|
|
119
|
+
ctx.taskData,
|
|
120
|
+
t => t.faction === 'crab' && !t.is_fake_shrimp,
|
|
121
|
+
undefined,
|
|
122
|
+
ctx.blockedMoveTarget,
|
|
123
|
+
ctx.knownCorpses,
|
|
124
|
+
);
|
|
125
|
+
if (task) return approachOrDoTaskStep(state, ctx, task, false);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private isWorkableTask(task: TaskInfo): boolean {
|
|
130
|
+
return task.status !== 'emergency';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── 破坏警报去重(自旧实现原样保留)──
|
|
134
|
+
|
|
135
|
+
private sabotageTaskKey(task: TaskInfo): string {
|
|
136
|
+
const x = task.x == null ? '' : Math.round(task.x);
|
|
137
|
+
const y = task.y == null ? '' : Math.round(task.y);
|
|
138
|
+
return `${task.task_id || task.task_name}:${x}:${y}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private shouldTriggerSabotageAlarm(task: TaskInfo, state: GameState, now: number): boolean {
|
|
142
|
+
const record = this.triggeredSabotageTasks.get(this.sabotageTaskKey(task));
|
|
143
|
+
if (!record) return true;
|
|
144
|
+
if (now - record.at < SABOTAGE_ALARM_DEDUPE_MS) return false;
|
|
145
|
+
return this.hasFreshSabotageEvent(task, state, record.tick);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private markSabotageTriggered(task: TaskInfo, state: GameState, now: number): void {
|
|
149
|
+
this.triggeredSabotageTasks.set(this.sabotageTaskKey(task), {
|
|
150
|
+
at: now,
|
|
151
|
+
tick: this.numericTick(state.tick),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private hasFreshSabotageEvent(task: TaskInfo, state: GameState, lastTriggeredTick: number | null): boolean {
|
|
156
|
+
const taskNames = new Set([task.task_id, task.task_name].filter(Boolean));
|
|
157
|
+
return (state.new_events ?? []).some(evt => {
|
|
158
|
+
if (evt?.type !== 'task_sabotaged') return false;
|
|
159
|
+
const eventTick = this.numericTick(evt.tick);
|
|
160
|
+
if (lastTriggeredTick != null && eventTick != null && eventTick <= lastTriggeredTick) return false;
|
|
161
|
+
const eventTaskName = typeof evt.task_name === 'string' ? evt.task_name : '';
|
|
162
|
+
return taskNames.has(eventTaskName);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private numericTick(value: unknown): number | null {
|
|
167
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private emitProgress(state: GameState, ctx: StrategyContext): void {
|
|
171
|
+
const now = Date.now();
|
|
172
|
+
if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
|
|
173
|
+
ctx.lastProgressNotifyAt = now;
|
|
174
|
+
|
|
175
|
+
const room = state.you.room ?? '未知';
|
|
176
|
+
const cd = killCooldownSecs(state);
|
|
177
|
+
const killsRemaining = state.you.kills_remaining;
|
|
178
|
+
const nonTeammates = nonTeammatesVisible(state, ctx);
|
|
179
|
+
|
|
180
|
+
let msg = `[进度] 当前在${room}`;
|
|
181
|
+
if (nonTeammates.length === 0) {
|
|
182
|
+
msg += ',视野内没有落单目标,继续巡逻做任务。';
|
|
183
|
+
} else if (nonTeammates.length === 1) {
|
|
184
|
+
msg += `,视野内发现落单目标${nonTeammates[0].name}。`;
|
|
185
|
+
if (cd > 0) msg += `攻击冷却中(${cd}s),先做任务、冷却好再下手。`;
|
|
186
|
+
if (killsRemaining === 0) msg += '出刀次数已用完,继续巡逻做任务。';
|
|
187
|
+
} else {
|
|
188
|
+
msg += `,视野内有${nonTeammates.length}人,不适合动手。`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (state.emergency) {
|
|
192
|
+
msg += ' 紧急警报进行中,往人少处找真正落单的目标,别在人前动手。';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
ctx.notifications.push(msg);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { GameState } from '../../sdk/types.js';
|
|
2
|
+
import { Action } from '../../sdk/action.js';
|
|
3
|
+
import { nonTeammatesVisible, patrolStep, PatrolState, roomAwayFromPlayers } from '../game-utils.js';
|
|
4
|
+
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
5
|
+
import { Goal } from './goal.js';
|
|
6
|
+
import type { LoneKillCore } from './lone-kill-core.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 紧急(自己触发破坏警报)期间的搜猎子目标(叶子,由上层以 URGENT 优先级挂载):虾群涌向维修点,
|
|
10
|
+
* 蟹不做任务,朝人少方向走去抓真正掉队的——警报期杀人照样会被举报,所以不在人前乱杀、只清落单;
|
|
11
|
+
* 看不到非队友就按房间游走搜寻。永远有动作。
|
|
12
|
+
* stop_on_player 复用 core.patrolStopOnPlayer()(身边只有队友时别撞着队友停下)。
|
|
13
|
+
*/
|
|
14
|
+
export class EmergencyHuntGoal extends Goal {
|
|
15
|
+
constructor(
|
|
16
|
+
private readonly core: LoneKillCore,
|
|
17
|
+
private readonly patrol: PatrolState,
|
|
18
|
+
) {
|
|
19
|
+
super();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
23
|
+
const crowd = nonTeammatesVisible(state, ctx);
|
|
24
|
+
const room = roomAwayFromPlayers(state, ctx, crowd);
|
|
25
|
+
if (room) return [{ action: Action.move({ x: room.x, y: room.y }, false) }];
|
|
26
|
+
return patrolStep(state, ctx, this.patrol, this.core.patrolStopOnPlayer());
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { GameState } from '../../sdk/types.js';
|
|
2
|
+
import { Action } from '../../sdk/action.js';
|
|
3
|
+
import {
|
|
4
|
+
dist,
|
|
5
|
+
isTargetAlive,
|
|
6
|
+
nearestVisibleTarget,
|
|
7
|
+
PatrolState,
|
|
8
|
+
PATROL_REACHED_DISTANCE,
|
|
9
|
+
PROGRESS_INTERVAL_MS,
|
|
10
|
+
pursueVisibleTarget,
|
|
11
|
+
} from '../game-utils.js';
|
|
12
|
+
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
13
|
+
import { Goal } from './goal.js';
|
|
14
|
+
|
|
15
|
+
const DEFAULT_TRACK_DISTANCE = 40;
|
|
16
|
+
const LAST_SEEN_ARRIVE = 20;
|
|
17
|
+
|
|
18
|
+
export class FindPlayerTop extends Goal {
|
|
19
|
+
private readonly patrol = new PatrolState();
|
|
20
|
+
private lastSeenX: number | null = null;
|
|
21
|
+
private lastSeenY: number | null = null;
|
|
22
|
+
private foundNotified = false;
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
private readonly target: string,
|
|
26
|
+
private readonly trackDistance: number = DEFAULT_TRACK_DISTANCE,
|
|
27
|
+
) {
|
|
28
|
+
super();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
32
|
+
if (!isTargetAlive(state, this.target, ctx)) {
|
|
33
|
+
ctx.notifications.push('目标已死亡');
|
|
34
|
+
this.emitProgress(state, ctx);
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const target = nearestVisibleTarget(state, ctx, [this.target]);
|
|
39
|
+
if (target) {
|
|
40
|
+
this.lastSeenX = target.x;
|
|
41
|
+
this.lastSeenY = target.y;
|
|
42
|
+
if (!this.foundNotified) {
|
|
43
|
+
this.foundNotified = true;
|
|
44
|
+
ctx.notifications.push('我已经牢牢跟住他了!');
|
|
45
|
+
}
|
|
46
|
+
const decision = pursueVisibleTarget(state, target, { kill: false, followDistance: this.trackDistance });
|
|
47
|
+
this.emitProgress(state, ctx);
|
|
48
|
+
return decision ? [decision] : [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.foundNotified = false;
|
|
52
|
+
|
|
53
|
+
if (this.lastSeenX != null && this.lastSeenY != null) {
|
|
54
|
+
if (dist(state.you.x, state.you.y, this.lastSeenX, this.lastSeenY) > LAST_SEEN_ARRIVE) {
|
|
55
|
+
this.emitProgress(state, ctx);
|
|
56
|
+
return [{ action: Action.move({ x: this.lastSeenX, y: this.lastSeenY }) }];
|
|
57
|
+
}
|
|
58
|
+
this.lastSeenX = null;
|
|
59
|
+
this.lastSeenY = null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.emitProgress(state, ctx);
|
|
63
|
+
return this.patrolDecision(state, ctx);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private patrolDecision(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
67
|
+
let room = this.patrol.nextRoom(ctx);
|
|
68
|
+
if (!room) return [];
|
|
69
|
+
if (dist(state.you.x, state.you.y, room.x, room.y) <= PATROL_REACHED_DISTANCE) {
|
|
70
|
+
this.patrol.advance(ctx);
|
|
71
|
+
const next = this.patrol.nextRoom(ctx);
|
|
72
|
+
if (next) room = next;
|
|
73
|
+
}
|
|
74
|
+
return [{ action: Action.move({ x: room.x, y: room.y }) }];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private emitProgress(state: GameState, ctx: StrategyContext): void {
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
|
|
80
|
+
ctx.lastProgressNotifyAt = now;
|
|
81
|
+
|
|
82
|
+
const room = state.you.room ?? '未知';
|
|
83
|
+
if (this.foundNotified) {
|
|
84
|
+
const t = nearestVisibleTarget(state, ctx, [this.target]);
|
|
85
|
+
const d = t ? (t.distance ?? dist(state.you.x, state.you.y, t.x, t.y)) : 0;
|
|
86
|
+
ctx.notifications.push(`[进度] 当前在${room},正在跟踪目标${this.target},距离${Math.round(d)}px。`);
|
|
87
|
+
} else if (this.lastSeenX != null) {
|
|
88
|
+
ctx.notifications.push(`[进度] 当前在${room},跟丢了,正赶往${this.target}最后出现的位置。`);
|
|
89
|
+
} else {
|
|
90
|
+
ctx.notifications.push(`[进度] 当前在${room},正在巡逻寻找${this.target}。`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { GameState, PlayerInfo } from '../../sdk/types.js';
|
|
2
|
+
import { dist, hasReachedRoomTarget, resolveRoom, roomAwayFromPlayers } from '../game-utils.js';
|
|
3
|
+
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
4
|
+
import { Goal } from './goal.js';
|
|
5
|
+
import { MoveRoomGoal } from './move-room-goal.js';
|
|
6
|
+
|
|
7
|
+
export type FleePlayerResolver = (state: GameState, ctx: StrategyContext) => PlayerInfo[];
|
|
8
|
+
|
|
9
|
+
export interface FleePlayersGoalOptions {
|
|
10
|
+
emitProgress?: boolean;
|
|
11
|
+
finishWhenEmpty?: boolean;
|
|
12
|
+
stopOnPlayer?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class FleePlayersGoal extends Goal {
|
|
16
|
+
constructor(
|
|
17
|
+
public readonly key: string,
|
|
18
|
+
private readonly resolvePlayers: FleePlayerResolver,
|
|
19
|
+
private readonly options: FleePlayersGoalOptions = {},
|
|
20
|
+
) {
|
|
21
|
+
super();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
25
|
+
const players = this.resolvePlayers(state, ctx);
|
|
26
|
+
const worker = this.subGoal;
|
|
27
|
+
if (worker instanceof MoveRoomGoal && this.canKeepTarget(worker, state, ctx, players)) return [];
|
|
28
|
+
|
|
29
|
+
const room = roomAwayFromPlayers(state, ctx, players);
|
|
30
|
+
if (!room) return [];
|
|
31
|
+
|
|
32
|
+
if (!(worker instanceof MoveRoomGoal) || worker.room !== room.name) {
|
|
33
|
+
if (this.subGoal) this.removeSubGoal();
|
|
34
|
+
this.setSubGoal(new MoveRoomGoal(room.name, hasReachedRoomTarget, {
|
|
35
|
+
emitProgress: this.options.emitProgress ?? false,
|
|
36
|
+
stopOnPlayer: this.options.stopOnPlayer ?? false,
|
|
37
|
+
}), 0.2);
|
|
38
|
+
}
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
isFinish(state: GameState, ctx: StrategyContext): boolean {
|
|
43
|
+
return (this.options.finishWhenEmpty ?? true) && this.resolvePlayers(state, ctx).length === 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private canKeepTarget(worker: MoveRoomGoal, state: GameState, ctx: StrategyContext, players: PlayerInfo[]): boolean {
|
|
47
|
+
const target = resolveRoom(worker.room, ctx);
|
|
48
|
+
if (!target) return false;
|
|
49
|
+
if (hasReachedRoomTarget(state, target)) return true;
|
|
50
|
+
if (ctx.blockedMoveTarget && dist(target.x, target.y, ctx.blockedMoveTarget.x, ctx.blockedMoveTarget.y) <= 10) return false;
|
|
51
|
+
return !players.some(player => player.room?.toLowerCase() === target.name.toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { GameState } from '../../sdk/types.js';
|
|
2
|
+
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
3
|
+
import { Goal } from './goal.js';
|
|
4
|
+
|
|
5
|
+
export class GoalManager {
|
|
6
|
+
private top: Goal | null = null;
|
|
7
|
+
|
|
8
|
+
setTop(goal: Goal): void {
|
|
9
|
+
this.top = goal;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getTop(): Goal | null {
|
|
13
|
+
return this.top;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
clear(): void {
|
|
17
|
+
this.top = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
21
|
+
const decisions: BehaviorDecision[] = [];
|
|
22
|
+
let parent: Goal | null = null;
|
|
23
|
+
let node: Goal | null = this.top;
|
|
24
|
+
|
|
25
|
+
while (node !== null) {
|
|
26
|
+
if (node.isFinish(state, ctx)) {
|
|
27
|
+
if (parent === null) {
|
|
28
|
+
this.top = null;
|
|
29
|
+
} else {
|
|
30
|
+
parent.removeSubGoal();
|
|
31
|
+
}
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
decisions.push(...node.tick(state, ctx));
|
|
35
|
+
parent = node;
|
|
36
|
+
node = node.subGoal;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return decisions;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +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
|
+
}
|
|
@@ -0,0 +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
|
+
}
|