@myclaw163/clawclaw-cli 0.6.58 → 0.6.60
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 -440
- package/package.json +48 -48
- package/personas//347/220/206/346/231/272/346/270/251/345/222/214.md +23 -23
- package/personas//350/200/201/350/260/213/346/267/261/347/256/227.md +22 -22
- package/personas//350/257/232/346/201/263/347/233/264/347/216/207.md +22 -22
- package/personas//350/275/273/346/235/276/346/264/273/346/263/274.md +22 -22
- package/personas//351/207/216/346/200/247/345/217/233/351/200/206.md +23 -23
- package/scripts/postinstall.mjs +20 -20
- package/scripts/sync-bundled-skill.mjs +244 -244
- package/scripts/sync-bundled-skill.test.mjs +152 -152
- package/skills/clawclaw/SKILL.md +244 -244
- package/skills/clawclaw/references/CHATTERBOX.md +142 -142
- package/skills/clawclaw/references/COMMANDS.md +129 -132
- package/skills/clawclaw/references/GAME-MECHANICS.md +186 -186
- package/skills/clawclaw/references/HUB.md +48 -48
- package/skills/clawclaw/references/KNOWLEDGE.md +43 -43
- package/skills/clawclaw/references/STRATEGIES.md +57 -57
- package/skills/clawclaw/references/STREAM.md +59 -59
- package/skills/clawclaw/references/TACTICS.md +65 -65
- package/src/assets/clawclaw-ascii-map.txt +40 -40
- package/src/cli.ts +110 -110
- package/src/commands/_schema.ts +109 -109
- package/src/commands/account.ts +209 -209
- package/src/commands/config.ts +30 -30
- package/src/commands/do.test.ts +73 -37
- package/src/commands/do.ts +126 -95
- package/src/commands/events.ts +22 -22
- package/src/commands/game-map.test.ts +28 -28
- package/src/commands/game-start-plan.test.ts +84 -84
- package/src/commands/game.ts +1027 -1027
- package/src/commands/history-player.test.ts +102 -102
- package/src/commands/history.ts +573 -573
- package/src/commands/hub.test.ts +96 -96
- package/src/commands/hub.ts +234 -234
- package/src/commands/knowledge.test.ts +19 -19
- package/src/commands/knowledge.ts +168 -168
- package/src/commands/load.test.ts +51 -51
- package/src/commands/load.ts +13 -13
- package/src/commands/meeting-history.test.ts +106 -106
- package/src/commands/memory.ts +40 -40
- package/src/commands/peek.ts +45 -45
- package/src/commands/persona.ts +57 -57
- package/src/commands/setup/codex.ts +248 -248
- package/src/commands/setup/hermes.test.ts +96 -96
- package/src/commands/setup/hermes.ts +76 -76
- package/src/commands/setup/index.ts +13 -13
- package/src/commands/setup/openclaw.test.ts +114 -114
- package/src/commands/setup/openclaw.ts +147 -147
- package/src/commands/skill.ts +128 -128
- package/src/commands/state.ts +46 -46
- package/src/commands/strategy.test.ts +135 -135
- package/src/commands/strategy.ts +180 -180
- package/src/commands/tts.ts +128 -128
- package/src/commands/upgrade.test.ts +82 -82
- package/src/commands/upgrade.ts +148 -148
- package/src/commands/watch.test.ts +969 -969
- package/src/commands/watch.ts +720 -720
- package/src/lib/auth.test.ts +59 -59
- package/src/lib/auth.ts +186 -186
- package/src/lib/command-meta.ts +37 -37
- package/src/lib/game-client.ts +391 -391
- package/src/lib/host-config-patcher.test.ts +130 -130
- package/src/lib/host-config-patcher.ts +151 -151
- package/src/lib/http-keepalive.ts +15 -15
- package/src/lib/http-transport.test.ts +42 -42
- package/src/lib/http-transport.ts +113 -113
- package/src/lib/hub-client.test.ts +56 -56
- package/src/lib/hub-client.ts +88 -88
- package/src/lib/hub-install.test.ts +98 -98
- package/src/lib/hub-install.ts +121 -121
- package/src/lib/hub-reminder.ts +56 -56
- package/src/lib/hub-unzip.test.ts +69 -69
- package/src/lib/hub-unzip.ts +62 -62
- package/src/lib/init-command.test.ts +75 -75
- package/src/lib/init-command.ts +120 -120
- package/src/lib/knowledge-store.test.ts +180 -180
- package/src/lib/knowledge-store.ts +374 -374
- package/src/lib/load-context.test.ts +52 -52
- package/src/lib/load-context.ts +52 -52
- package/src/lib/match-state.test.ts +134 -134
- package/src/lib/match-state.ts +94 -94
- package/src/lib/netease-tts.ts +83 -83
- package/src/lib/normalize.ts +42 -42
- package/src/lib/persona.test.ts +41 -41
- package/src/lib/persona.ts +72 -72
- package/src/lib/server-registry.ts +152 -152
- package/src/lib/skill-version.test.ts +48 -48
- package/src/lib/skill-version.ts +19 -19
- package/src/lib/strategy-export.test.ts +232 -232
- package/src/lib/strategy-export.ts +242 -242
- package/src/lib/tts-keys.ts +7 -7
- package/src/lib/tts-speech.test.ts +63 -63
- package/src/lib/tts-speech.ts +76 -76
- package/src/lib/workspace-argv.test.ts +49 -49
- package/src/lib/workspace-argv.ts +44 -44
- package/src/perception/player-history-store.test.ts +87 -87
- package/src/perception/player-history-store.ts +194 -194
- package/src/pipeline/event-store.ts +124 -124
- package/src/pipeline/pipeline.ts +35 -35
- package/src/runtime/auto-upgrade.test.ts +66 -66
- package/src/runtime/auto-upgrade.ts +31 -31
- package/src/runtime/event-daemon.test.ts +107 -107
- package/src/runtime/event-daemon.ts +409 -409
- package/src/runtime/owner-control.ts +150 -150
- package/src/runtime/raw-ws-log.test.ts +33 -33
- package/src/runtime/raw-ws-log.ts +32 -32
- package/src/runtime/runtime-logger.ts +107 -107
- package/src/runtime/ws-client.test.ts +104 -104
- package/src/runtime/ws-client.ts +272 -272
- package/src/sdk/action.ts +166 -166
- package/src/sdk/index.ts +110 -110
- package/src/sdk/types.ts +146 -146
- package/src/strategies/avoid-lone.ts +11 -11
- package/src/strategies/avoid-players.knowledge.md +20 -20
- package/src/strategies/avoid-players.ts +15 -15
- package/src/strategies/corpse-patrol.ts +22 -22
- package/src/strategies/crab-sabotage.ts +21 -21
- package/src/strategies/custom-module.test.ts +269 -269
- package/src/strategies/find-player.ts +16 -16
- package/src/strategies/game-utils.test.ts +190 -190
- package/src/strategies/game-utils.ts +744 -744
- package/src/strategies/goals/avoid-lone-top.ts +168 -168
- package/src/strategies/goals/avoid-players-top.test.ts +83 -83
- package/src/strategies/goals/avoid-players-top.ts +121 -121
- package/src/strategies/goals/conversation-goal.ts +51 -51
- package/src/strategies/goals/corpse-patrol-top.ts +91 -91
- package/src/strategies/goals/crab-octopus-reflexes.ts +93 -93
- package/src/strategies/goals/crab-sabotage-top.ts +197 -197
- package/src/strategies/goals/emergency-hunt-goal.ts +28 -28
- package/src/strategies/goals/find-player-top.ts +93 -93
- package/src/strategies/goals/flee-players-goal.ts +53 -53
- package/src/strategies/goals/goal-manager.ts +41 -41
- package/src/strategies/goals/goal-root-strategy.ts +49 -49
- package/src/strategies/goals/goal.ts +28 -28
- package/src/strategies/goals/keep-away-goal.ts +209 -209
- package/src/strategies/goals/kill-frenzy-top.ts +80 -80
- package/src/strategies/goals/kill-lone-top.ts +160 -160
- package/src/strategies/goals/kill-target-goal.ts +59 -59
- package/src/strategies/goals/kill-target-top.ts +109 -109
- package/src/strategies/goals/leaf-goal.ts +25 -25
- package/src/strategies/goals/linger-corpse-goal.ts +79 -79
- package/src/strategies/goals/lone-kill-core.ts +82 -82
- package/src/strategies/goals/lone-kill-goal.ts +24 -24
- package/src/strategies/goals/lone-kill-task-top.test.ts +85 -85
- package/src/strategies/goals/lone-kill-task-top.ts +86 -86
- package/src/strategies/goals/move-room-goal.ts +60 -60
- package/src/strategies/goals/normal-shrimp-top.test.ts +80 -80
- package/src/strategies/goals/normal-shrimp-top.ts +242 -242
- package/src/strategies/goals/paradise-fish-top.test.ts +126 -126
- package/src/strategies/goals/paradise-fish-top.ts +219 -219
- package/src/strategies/goals/patrol-top.ts +57 -57
- package/src/strategies/goals/report-patrol-top.ts +80 -80
- package/src/strategies/goals/safe-task-goal.ts +102 -102
- package/src/strategies/goals/social-task-top.ts +161 -161
- package/src/strategies/goals/task-kill-report-top.ts +163 -163
- package/src/strategies/goals/task-only-top.ts +57 -57
- package/src/strategies/goals/task-or-patrol-goal.ts +41 -41
- package/src/strategies/goals/task-report-top.ts +57 -57
- package/src/strategies/goals/wander-task-goal.ts +33 -33
- package/src/strategies/goals/warrior-shrimp-top.test.ts +86 -86
- package/src/strategies/goals/warrior-shrimp-top.ts +248 -248
- package/src/strategies/greeting.ts +53 -53
- package/src/strategies/kill-frenzy.ts +12 -12
- package/src/strategies/kill-lone.knowledge.md +20 -20
- package/src/strategies/kill-lone.ts +13 -13
- package/src/strategies/kill-target.ts +18 -18
- package/src/strategies/loader.test.ts +678 -678
- package/src/strategies/loader.ts +172 -172
- package/src/strategies/lone-kill-task.ts +21 -21
- package/src/strategies/meeting-gate.test.ts +59 -59
- package/src/strategies/meeting-gate.ts +23 -23
- package/src/strategies/move-room.ts +15 -15
- package/src/strategies/new-events-backfill.ts +98 -98
- package/src/strategies/paradise-fish.knowledge.md +20 -20
- package/src/strategies/paradise-fish.ts +25 -25
- package/src/strategies/pathfind/distance-field.ts +150 -150
- package/src/strategies/pathfind/escape-planner.test.ts +197 -197
- package/src/strategies/pathfind/escape-planner.ts +355 -355
- package/src/strategies/pathfind/walkable-grid.ts +117 -117
- package/src/strategies/patrol.ts +11 -11
- package/src/strategies/player-targets.ts +13 -13
- package/src/strategies/report-patrol.ts +11 -11
- package/src/strategies/shrimp-memory.knowledge.md +20 -20
- package/src/strategies/shrimp-memory.ts +25 -25
- package/src/strategies/social-task.test.ts +28 -28
- package/src/strategies/social-task.ts +49 -49
- package/src/strategies/spawn.ts +82 -82
- package/src/strategies/speech-module.ts +123 -123
- package/src/strategies/strategy-loop.ts +763 -763
- package/src/strategies/task-kill-report.ts +17 -17
- package/src/strategies/task-only.ts +11 -11
- package/src/strategies/task-report.ts +22 -22
- package/src/strategies/types.ts +96 -96
- package/src/strategies/warrior-memory.knowledge.md +20 -20
- package/src/strategies/warrior-memory.ts +16 -16
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
import type { GameState } from '../../sdk/types.js';
|
|
2
|
-
import { Action } from '../../sdk/action.js';
|
|
3
|
-
import {
|
|
4
|
-
canUseKill,
|
|
5
|
-
corpseAtScene,
|
|
6
|
-
dist,
|
|
7
|
-
killCommitRange,
|
|
8
|
-
nearestReportableCorpse,
|
|
9
|
-
nonTeammatesVisible,
|
|
10
|
-
reportCorpseDecision,
|
|
11
|
-
} from '../game-utils.js';
|
|
12
|
-
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* 蟹(坏人)/ 章鱼(中立带刀)默认策略共享的「反射动作」——由 CrabSabotageTop 与 LoneKillTaskTop
|
|
16
|
-
* 在各自 tick() 的**最前面**调用,抢在破坏/任务/落单猎杀编排之前先处理两类即时机会:
|
|
17
|
-
*
|
|
18
|
-
* - immediateLoneKillDecision:唯一一个落单非队友就贴在脸上(出刀范围内)且刀好 → 立刻出刀。它无视
|
|
19
|
-
* LoneKillCore 的「暂停窗口」,也不管自己是否正在做破坏任务——只要能原地一刀就先杀。
|
|
20
|
-
* - corpseReportWithNonTeammate:地上有尸体且附近有非队友 → 报警(够不着先靠近)。不管尸体是谁
|
|
21
|
-
* 造成的(含队友的杀),靠主动报警伪装好人 / 把嫌疑栽到在场者身上。
|
|
22
|
-
*
|
|
23
|
-
* 调用方按「能贴脸杀就杀,否则报警」的次序:先 immediateLoneKillDecision 再 corpseReportWithNonTeammate。
|
|
24
|
-
* 两者都**无记忆**(不读 ctx.knowledge),与默认蟹/章鱼对任意落单非队友照杀的设定一致;蟹/章鱼目前
|
|
25
|
-
* 没有记忆版策略,故没有「点名/保护」入口。
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
/** 有尸体且附近有非队友 → 报警/靠近报警;否则 null。respectBlock 防止 5s 内重复报。 */
|
|
29
|
-
export function corpseReportWithNonTeammate(state: GameState, ctx: StrategyContext): BehaviorDecision[] | null {
|
|
30
|
-
// 报尸自证/嫁祸是「此刻就在尸体旁、有人撞见」的现场反射,必须用当帧视野的尸体;用持久记忆会让
|
|
31
|
-
// 蟹/章鱼在地图别处随便撞见一个人就生成错误的栽赃简报。
|
|
32
|
-
if (!corpseAtScene(state)) return null;
|
|
33
|
-
const witnesses = nonTeammatesVisible(state, ctx);
|
|
34
|
-
if (witnesses.length === 0) return null;
|
|
35
|
-
|
|
36
|
-
const report = reportCorpseDecision(state, ctx, { respectBlock: true });
|
|
37
|
-
if (!report) return null;
|
|
38
|
-
|
|
39
|
-
const witnessNames = witnesses.map(p => p.name).join('、');
|
|
40
|
-
if (report.action.type === 'report') {
|
|
41
|
-
// 实时给 Agent 一条「为什么是我报的尸」简报:报尸=召集会议,是高风险自证动作。
|
|
42
|
-
// 若尸体很可能是自己刚才下的那一刀,必须显式提醒,免得 Agent 之后发言露馅。
|
|
43
|
-
const corpseName = nearestReportableCorpse(state)?.name ?? '';
|
|
44
|
-
ctx.agentAlerts.push(
|
|
45
|
-
isRecentOwnKill(ctx, corpseName)
|
|
46
|
-
? `我刚下手的目标${corpseName ? `(${corpseName})` : ''}尸体被${witnessNames}撞见,我抢先报尸召集会议、把自己装成发现者并把嫌疑引向在场者。接下来发言/投票要顺着「我是主动报告的人」走,绝不能承认是自己动的手。`
|
|
47
|
-
: `尸体旁有${witnessNames}在场,我抢先报尸召集会议、伪装成主动报告的好人。发言时按「主动报告的发现者」立场走。`,
|
|
48
|
-
);
|
|
49
|
-
ctx.notifications.push(`尸体旁有${witnessNames}在场,立刻报告自证。`);
|
|
50
|
-
const base = report.action.thinkingContent ?? '发现尸体,报告召开会议';
|
|
51
|
-
return [{
|
|
52
|
-
action: report.action.withThinking(`${base};尸体旁有${witnessNames}在场,立刻报告自证。`),
|
|
53
|
-
}];
|
|
54
|
-
}
|
|
55
|
-
ctx.notifications.push(`尸体旁有${witnessNames}在场,靠近尸体准备报告自证。`);
|
|
56
|
-
return [{
|
|
57
|
-
action: report.action.withThinking(`尸体旁有${witnessNames}在场,靠近尸体准备报告自证。`),
|
|
58
|
-
}];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** 唯一一个落单非队友贴脸(出刀范围内)且刀好 → 立刻出刀;否则 null。无视暂停窗口、不管在做什么任务。 */
|
|
62
|
-
export function immediateLoneKillDecision(state: GameState, ctx: StrategyContext): BehaviorDecision[] | null {
|
|
63
|
-
if (!canUseKill(state)) return null;
|
|
64
|
-
|
|
65
|
-
const targets = nonTeammatesVisible(state, ctx);
|
|
66
|
-
if (targets.length !== 1) return null;
|
|
67
|
-
|
|
68
|
-
const target = targets[0];
|
|
69
|
-
const distance = target.distance ?? dist(state.you.x, state.you.y, target.x, target.y);
|
|
70
|
-
if (distance > killCommitRange(state.you.role)) return null;
|
|
71
|
-
|
|
72
|
-
ctx.notifications.push(`发现落单目标${target.name}就在身边,立刻出刀!`);
|
|
73
|
-
return [{ action: Action.kill(target.name) }];
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* 这具尸体是不是「自己刚才下的那一刀」。先按尸体名精确匹配 recentlyKilledTargets(刚杀过且未过期),
|
|
78
|
-
* 名字对不上但近期确实杀过人时,冒出的尸体大概率也是自己造成的,按 true 处理(宁可多提醒一次)。
|
|
79
|
-
*/
|
|
80
|
-
function isRecentOwnKill(ctx: StrategyContext, corpseName: string): boolean {
|
|
81
|
-
const map = ctx.recentlyKilledTargets;
|
|
82
|
-
if (!map || map.size === 0) return false;
|
|
83
|
-
const now = Date.now();
|
|
84
|
-
const key = corpseName.trim().toLowerCase();
|
|
85
|
-
if (key) {
|
|
86
|
-
const until = map.get(key);
|
|
87
|
-
if (until != null && until > now) return true;
|
|
88
|
-
}
|
|
89
|
-
for (const until of map.values()) {
|
|
90
|
-
if (until > now) return true;
|
|
91
|
-
}
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
1
|
+
import type { GameState } from '../../sdk/types.js';
|
|
2
|
+
import { Action } from '../../sdk/action.js';
|
|
3
|
+
import {
|
|
4
|
+
canUseKill,
|
|
5
|
+
corpseAtScene,
|
|
6
|
+
dist,
|
|
7
|
+
killCommitRange,
|
|
8
|
+
nearestReportableCorpse,
|
|
9
|
+
nonTeammatesVisible,
|
|
10
|
+
reportCorpseDecision,
|
|
11
|
+
} from '../game-utils.js';
|
|
12
|
+
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 蟹(坏人)/ 章鱼(中立带刀)默认策略共享的「反射动作」——由 CrabSabotageTop 与 LoneKillTaskTop
|
|
16
|
+
* 在各自 tick() 的**最前面**调用,抢在破坏/任务/落单猎杀编排之前先处理两类即时机会:
|
|
17
|
+
*
|
|
18
|
+
* - immediateLoneKillDecision:唯一一个落单非队友就贴在脸上(出刀范围内)且刀好 → 立刻出刀。它无视
|
|
19
|
+
* LoneKillCore 的「暂停窗口」,也不管自己是否正在做破坏任务——只要能原地一刀就先杀。
|
|
20
|
+
* - corpseReportWithNonTeammate:地上有尸体且附近有非队友 → 报警(够不着先靠近)。不管尸体是谁
|
|
21
|
+
* 造成的(含队友的杀),靠主动报警伪装好人 / 把嫌疑栽到在场者身上。
|
|
22
|
+
*
|
|
23
|
+
* 调用方按「能贴脸杀就杀,否则报警」的次序:先 immediateLoneKillDecision 再 corpseReportWithNonTeammate。
|
|
24
|
+
* 两者都**无记忆**(不读 ctx.knowledge),与默认蟹/章鱼对任意落单非队友照杀的设定一致;蟹/章鱼目前
|
|
25
|
+
* 没有记忆版策略,故没有「点名/保护」入口。
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** 有尸体且附近有非队友 → 报警/靠近报警;否则 null。respectBlock 防止 5s 内重复报。 */
|
|
29
|
+
export function corpseReportWithNonTeammate(state: GameState, ctx: StrategyContext): BehaviorDecision[] | null {
|
|
30
|
+
// 报尸自证/嫁祸是「此刻就在尸体旁、有人撞见」的现场反射,必须用当帧视野的尸体;用持久记忆会让
|
|
31
|
+
// 蟹/章鱼在地图别处随便撞见一个人就生成错误的栽赃简报。
|
|
32
|
+
if (!corpseAtScene(state)) return null;
|
|
33
|
+
const witnesses = nonTeammatesVisible(state, ctx);
|
|
34
|
+
if (witnesses.length === 0) return null;
|
|
35
|
+
|
|
36
|
+
const report = reportCorpseDecision(state, ctx, { respectBlock: true });
|
|
37
|
+
if (!report) return null;
|
|
38
|
+
|
|
39
|
+
const witnessNames = witnesses.map(p => p.name).join('、');
|
|
40
|
+
if (report.action.type === 'report') {
|
|
41
|
+
// 实时给 Agent 一条「为什么是我报的尸」简报:报尸=召集会议,是高风险自证动作。
|
|
42
|
+
// 若尸体很可能是自己刚才下的那一刀,必须显式提醒,免得 Agent 之后发言露馅。
|
|
43
|
+
const corpseName = nearestReportableCorpse(state)?.name ?? '';
|
|
44
|
+
ctx.agentAlerts.push(
|
|
45
|
+
isRecentOwnKill(ctx, corpseName)
|
|
46
|
+
? `我刚下手的目标${corpseName ? `(${corpseName})` : ''}尸体被${witnessNames}撞见,我抢先报尸召集会议、把自己装成发现者并把嫌疑引向在场者。接下来发言/投票要顺着「我是主动报告的人」走,绝不能承认是自己动的手。`
|
|
47
|
+
: `尸体旁有${witnessNames}在场,我抢先报尸召集会议、伪装成主动报告的好人。发言时按「主动报告的发现者」立场走。`,
|
|
48
|
+
);
|
|
49
|
+
ctx.notifications.push(`尸体旁有${witnessNames}在场,立刻报告自证。`);
|
|
50
|
+
const base = report.action.thinkingContent ?? '发现尸体,报告召开会议';
|
|
51
|
+
return [{
|
|
52
|
+
action: report.action.withThinking(`${base};尸体旁有${witnessNames}在场,立刻报告自证。`),
|
|
53
|
+
}];
|
|
54
|
+
}
|
|
55
|
+
ctx.notifications.push(`尸体旁有${witnessNames}在场,靠近尸体准备报告自证。`);
|
|
56
|
+
return [{
|
|
57
|
+
action: report.action.withThinking(`尸体旁有${witnessNames}在场,靠近尸体准备报告自证。`),
|
|
58
|
+
}];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 唯一一个落单非队友贴脸(出刀范围内)且刀好 → 立刻出刀;否则 null。无视暂停窗口、不管在做什么任务。 */
|
|
62
|
+
export function immediateLoneKillDecision(state: GameState, ctx: StrategyContext): BehaviorDecision[] | null {
|
|
63
|
+
if (!canUseKill(state)) return null;
|
|
64
|
+
|
|
65
|
+
const targets = nonTeammatesVisible(state, ctx);
|
|
66
|
+
if (targets.length !== 1) return null;
|
|
67
|
+
|
|
68
|
+
const target = targets[0];
|
|
69
|
+
const distance = target.distance ?? dist(state.you.x, state.you.y, target.x, target.y);
|
|
70
|
+
if (distance > killCommitRange(state.you.role)) return null;
|
|
71
|
+
|
|
72
|
+
ctx.notifications.push(`发现落单目标${target.name}就在身边,立刻出刀!`);
|
|
73
|
+
return [{ action: Action.kill(target.name) }];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 这具尸体是不是「自己刚才下的那一刀」。先按尸体名精确匹配 recentlyKilledTargets(刚杀过且未过期),
|
|
78
|
+
* 名字对不上但近期确实杀过人时,冒出的尸体大概率也是自己造成的,按 true 处理(宁可多提醒一次)。
|
|
79
|
+
*/
|
|
80
|
+
function isRecentOwnKill(ctx: StrategyContext, corpseName: string): boolean {
|
|
81
|
+
const map = ctx.recentlyKilledTargets;
|
|
82
|
+
if (!map || map.size === 0) return false;
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
const key = corpseName.trim().toLowerCase();
|
|
85
|
+
if (key) {
|
|
86
|
+
const until = map.get(key);
|
|
87
|
+
if (until != null && until > now) return true;
|
|
88
|
+
}
|
|
89
|
+
for (const until of map.values()) {
|
|
90
|
+
if (until > now) return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
@@ -1,197 +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):落单非队友贴脸(出刀范围内)且刀好 → 立刻出刀,
|
|
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
|
-
}
|
|
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):落单非队友贴脸(出刀范围内)且刀好 → 立刻出刀,
|
|
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
|
+
}
|
|
@@ -1,28 +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
|
-
}
|
|
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
|
+
}
|