@myclaw163/clawclaw-cli 0.6.66 → 0.6.67
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 +427 -427
- package/bin/clawclaw-cli.mjs +3 -3
- 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/find-hide-spots.py +157 -157
- package/scripts/postinstall.mjs +20 -20
- package/scripts/sync-bundled-skill.mjs +245 -245
- package/scripts/sync-bundled-skill.test.mjs +152 -152
- package/skills/clawclaw/SKILL.md +244 -245
- package/skills/clawclaw/references/CHATTERBOX.md +142 -142
- package/skills/clawclaw/references/COMMANDS.md +148 -148
- package/skills/clawclaw/references/GAME-MECHANICS.md +188 -188
- 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 +91 -92
- 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/do.test.ts +73 -73
- package/src/commands/do.ts +126 -126
- package/src/commands/events.test.ts +71 -71
- package/src/commands/events.ts +155 -155
- 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 +266 -248
- 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 +966 -966
- package/src/commands/watch.ts +659 -659
- 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/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 +75 -75
- 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-format.test.ts +135 -135
- package/src/pipeline/event-format.ts +376 -376
- package/src/pipeline/event-hints.ts +173 -173
- package/src/pipeline/event-store.test.ts +28 -28
- package/src/pipeline/event-store.ts +193 -193
- 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 +111 -111
- package/src/sdk/types.ts +159 -159
- 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 +782 -782
- package/src/strategies/goals/anchor-linger.ts +77 -77
- 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/follow-companion-goal.ts +106 -106
- 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/hide-top.ts +197 -197
- package/src/strategies/goals/keep-away-goal.ts +217 -217
- 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 +35 -35
- 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 +207 -207
- 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 +500 -500
- package/src/strategies/greeting.ts +53 -53
- package/src/strategies/hide-spots.ts +123 -123
- package/src/strategies/hide.ts +23 -23
- 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 +771 -771
- 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 +102 -102
- package/src/strategies/warrior-memory.knowledge.md +22 -22
- package/src/strategies/warrior-memory.ts +16 -16
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import type { GameState } from '../../sdk/types.js';
|
|
2
|
-
import type { BehaviorDecision, CustomModule, Strategy, StrategyContext } from '../types.js';
|
|
3
|
-
import type { Goal } from './goal.js';
|
|
4
|
-
import { GoalManager } from './goal-manager.js';
|
|
5
|
-
|
|
6
|
-
export interface GoalRootStrategyOptions {
|
|
7
|
-
resetOnMeetingResume?: boolean;
|
|
8
|
-
customModules?: CustomModule[];
|
|
9
|
-
onUpdateRole?: (role: string) => void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class GoalRootStrategy implements Strategy {
|
|
13
|
-
readonly name: string;
|
|
14
|
-
private readonly mgr = new GoalManager();
|
|
15
|
-
private readonly makeTop: () => Goal;
|
|
16
|
-
private readonly resetOnMeetingResume: boolean;
|
|
17
|
-
private readonly modules: CustomModule[];
|
|
18
|
-
private readonly onUpdateRole?: (role: string) => void;
|
|
19
|
-
|
|
20
|
-
constructor(name: string, makeTop: () => Goal, options?: GoalRootStrategyOptions) {
|
|
21
|
-
this.name = name;
|
|
22
|
-
this.makeTop = makeTop;
|
|
23
|
-
this.resetOnMeetingResume = options?.resetOnMeetingResume ?? true;
|
|
24
|
-
this.modules = options?.customModules ?? [];
|
|
25
|
-
this.onUpdateRole = options?.onUpdateRole;
|
|
26
|
-
this.mgr.setTop(makeTop());
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
decide(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
30
|
-
if (this.mgr.getTop() === null) this.mgr.setTop(this.makeTop());
|
|
31
|
-
return this.mgr.tick(state, ctx);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
onMeetingResume(): void {
|
|
35
|
-
if (this.resetOnMeetingResume) {
|
|
36
|
-
this.mgr.setTop(this.makeTop());
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
this.mgr.getTop()?.onMeetingResume();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
customModules(): CustomModule[] {
|
|
43
|
-
return this.modules;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
updateRole(role: string): void {
|
|
47
|
-
this.onUpdateRole?.(role);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
1
|
+
import type { GameState } from '../../sdk/types.js';
|
|
2
|
+
import type { BehaviorDecision, CustomModule, Strategy, StrategyContext } from '../types.js';
|
|
3
|
+
import type { Goal } from './goal.js';
|
|
4
|
+
import { GoalManager } from './goal-manager.js';
|
|
5
|
+
|
|
6
|
+
export interface GoalRootStrategyOptions {
|
|
7
|
+
resetOnMeetingResume?: boolean;
|
|
8
|
+
customModules?: CustomModule[];
|
|
9
|
+
onUpdateRole?: (role: string) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class GoalRootStrategy implements Strategy {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
private readonly mgr = new GoalManager();
|
|
15
|
+
private readonly makeTop: () => Goal;
|
|
16
|
+
private readonly resetOnMeetingResume: boolean;
|
|
17
|
+
private readonly modules: CustomModule[];
|
|
18
|
+
private readonly onUpdateRole?: (role: string) => void;
|
|
19
|
+
|
|
20
|
+
constructor(name: string, makeTop: () => Goal, options?: GoalRootStrategyOptions) {
|
|
21
|
+
this.name = name;
|
|
22
|
+
this.makeTop = makeTop;
|
|
23
|
+
this.resetOnMeetingResume = options?.resetOnMeetingResume ?? true;
|
|
24
|
+
this.modules = options?.customModules ?? [];
|
|
25
|
+
this.onUpdateRole = options?.onUpdateRole;
|
|
26
|
+
this.mgr.setTop(makeTop());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
decide(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
30
|
+
if (this.mgr.getTop() === null) this.mgr.setTop(this.makeTop());
|
|
31
|
+
return this.mgr.tick(state, ctx);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
onMeetingResume(): void {
|
|
35
|
+
if (this.resetOnMeetingResume) {
|
|
36
|
+
this.mgr.setTop(this.makeTop());
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.mgr.getTop()?.onMeetingResume();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
customModules(): CustomModule[] {
|
|
43
|
+
return this.modules;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
updateRole(role: string): void {
|
|
47
|
+
this.onUpdateRole?.(role);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import type { GameState } from '../../sdk/types.js';
|
|
2
|
-
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
3
|
-
|
|
4
|
-
export abstract class Goal {
|
|
5
|
-
subGoal: Goal | null = null;
|
|
6
|
-
priority = 0;
|
|
7
|
-
|
|
8
|
-
abstract tick(state: GameState, ctx: StrategyContext): BehaviorDecision[];
|
|
9
|
-
|
|
10
|
-
isFinish(_state: GameState, _ctx: StrategyContext): boolean {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
onMeetingResume(): void {}
|
|
15
|
-
|
|
16
|
-
setSubGoal(child: Goal, priority: number): boolean {
|
|
17
|
-
if (this.subGoal !== null && this.subGoal.priority >= priority) {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
child.priority = priority;
|
|
21
|
-
this.subGoal = child;
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
removeSubGoal(): void {
|
|
26
|
-
this.subGoal = null;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
1
|
+
import type { GameState } from '../../sdk/types.js';
|
|
2
|
+
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
3
|
+
|
|
4
|
+
export abstract class Goal {
|
|
5
|
+
subGoal: Goal | null = null;
|
|
6
|
+
priority = 0;
|
|
7
|
+
|
|
8
|
+
abstract tick(state: GameState, ctx: StrategyContext): BehaviorDecision[];
|
|
9
|
+
|
|
10
|
+
isFinish(_state: GameState, _ctx: StrategyContext): boolean {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
onMeetingResume(): void {}
|
|
15
|
+
|
|
16
|
+
setSubGoal(child: Goal, priority: number): boolean {
|
|
17
|
+
if (this.subGoal !== null && this.subGoal.priority >= priority) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
child.priority = priority;
|
|
21
|
+
this.subGoal = child;
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
removeSubGoal(): void {
|
|
26
|
+
this.subGoal = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,197 +1,197 @@
|
|
|
1
|
-
import type { GameState, PlayerInfo, Position } from '../../sdk/types.js';
|
|
2
|
-
import { Action } from '../../sdk/action.js';
|
|
3
|
-
import {
|
|
4
|
-
canUseKill,
|
|
5
|
-
dist,
|
|
6
|
-
killCommitRange,
|
|
7
|
-
nonTeammatesVisible,
|
|
8
|
-
PROGRESS_INTERVAL_MS,
|
|
9
|
-
SHRIMP_KILL_RANGE,
|
|
10
|
-
} from '../game-utils.js';
|
|
11
|
-
import { nearestSafeHideSpot, type HideSpot } from '../hide-spots.js';
|
|
12
|
-
import { planEscape, type EscapeOptions } from '../pathfind/escape-planner.js';
|
|
13
|
-
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
14
|
-
import { Goal } from './goal.js';
|
|
15
|
-
import { KeepAwayGoal } from './keep-away-goal.js';
|
|
16
|
-
|
|
17
|
-
const SUB_PRIORITY = 0.5;
|
|
18
|
-
const FLEE_KEY = 'hide-flee';
|
|
19
|
-
/** 走到躲藏点这个距离内就算到位,原地潜伏不动。 */
|
|
20
|
-
const REACHED_DISTANCE = 40;
|
|
21
|
-
/** 威胁离开视野后,其位置仍在选点时被回避这么久——逃完不会立刻又选回威胁刚待过的点。 */
|
|
22
|
-
const THREAT_MEMORY_MS = 5_000;
|
|
23
|
-
/**
|
|
24
|
-
* 「无处可逃」用的逃跑推演参数:与 KeepAwayGoal 同款保守模型(killRange=160,按枪虾/武士虾兜底)。
|
|
25
|
-
* 理由是「连我自己用来逃跑的同一套推演都判定必被追上,逃就没意义,才出刀自保」,判据自洽。
|
|
26
|
-
*/
|
|
27
|
-
const ESCAPE_OPTS: EscapeOptions = { killRange: SHRIMP_KILL_RANGE, steps: 7, beamWidth: 8, directions: 16, fieldRadius: 900 };
|
|
28
|
-
|
|
29
|
-
type ProgressTier = 'flee' | 'relocate' | 'hidden';
|
|
30
|
-
type ThreatPointResolver = () => Position[];
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 躲藏点劳作循环:与 SafeTaskOrPatrolGoal 同构——planSpot 边逃边预重算(粘性、威胁硬排除),
|
|
34
|
-
* tick 执行(走向躲藏点;到位则原地不动)。没有可用点时不动。
|
|
35
|
-
*/
|
|
36
|
-
class HideSpotGoal extends Goal {
|
|
37
|
-
private current: HideSpot | null = null;
|
|
38
|
-
|
|
39
|
-
constructor(private readonly threatPoints: ThreatPointResolver) {
|
|
40
|
-
super();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
get currentSpot(): HideSpot | null {
|
|
44
|
-
return this.current;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
reset(): void {
|
|
48
|
-
this.current = null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** 重算并记住安全躲藏点,不发移动指令(逃跑期间可安全调用)。 */
|
|
52
|
-
planSpot(state: GameState, ctx: StrategyContext): HideSpot | null {
|
|
53
|
-
const spot = nearestSafeHideSpot(
|
|
54
|
-
{ x: state.you.x, y: state.you.y },
|
|
55
|
-
this.threatPoints(),
|
|
56
|
-
{ stickyTo: this.current, blockedTarget: ctx.blockedMoveTarget },
|
|
57
|
-
);
|
|
58
|
-
this.current = spot;
|
|
59
|
-
return spot;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
63
|
-
const spot = this.planSpot(state, ctx);
|
|
64
|
-
if (!spot) return [];
|
|
65
|
-
if (dist(state.you.x, state.you.y, spot.x, spot.y) <= REACHED_DISTANCE) return []; // 到位,原地潜伏
|
|
66
|
-
return [{ action: Action.move({ x: spot.x, y: spot.y }).withThinking(`前往躲藏点${spot.room}潜伏`) }];
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* 躲藏策略顶层:在离线算好的藏身角落里选测地最近且不挨威胁的一个潜伏不动,直到有人进入视野。
|
|
72
|
-
* 有人来就 ① 用 KeepAwayGoal 保持距离甩开,② 边逃边重算排除该威胁的新躲藏点——与 shrimp-memory 的
|
|
73
|
-
* 「躲人 + 重选任务」同构,只是把任务换成躲藏点。蟹不躲队友(威胁统一用 nonTeammatesVisible 过滤队友)。
|
|
74
|
-
*
|
|
75
|
-
* killWhenCornered:被**单个**对手逼进出刀距离、且 planEscape 判定无路可逃时才出刀自保。
|
|
76
|
-
* null = 按角色默认:蟹 / 章鱼开,武士虾 / 枪虾(及无刀角色)关。
|
|
77
|
-
*/
|
|
78
|
-
export class HideTop extends Goal {
|
|
79
|
-
private readonly spotGoal = new HideSpotGoal(() => this.threatPointsFor());
|
|
80
|
-
private readonly recentThreats = new Map<string, { x: number; y: number; until: number }>();
|
|
81
|
-
|
|
82
|
-
constructor(private readonly killWhenCornered: boolean | null = null) {
|
|
83
|
-
super();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
onMeetingResume(): void {
|
|
87
|
-
this.spotGoal.reset();
|
|
88
|
-
this.recentThreats.clear();
|
|
89
|
-
if (this.subGoal) this.removeSubGoal();
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
93
|
-
const visible = nonTeammatesVisible(state, ctx);
|
|
94
|
-
this.rememberThreats(visible);
|
|
95
|
-
|
|
96
|
-
// ── 被单个对手逼到死角且刀好 → 出刀自保(按角色 / 参数开关) ──
|
|
97
|
-
if (this.killEnabled(state) && canUseKill(state)) {
|
|
98
|
-
const victim = this.corneredVictim(state, visible);
|
|
99
|
-
if (victim) {
|
|
100
|
-
if (this.subGoal) this.removeSubGoal();
|
|
101
|
-
ctx.notifications.push(`被${victim.name}逼到无处可逃,出刀自保!`);
|
|
102
|
-
return [{ action: Action.kill(victim.name).withThinking(`被${victim.name}逼进出刀距离又逃不掉,先下手自保。`) }];
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// ── 视野里有人 → 保持距离甩开 + 边逃边重算安全躲藏点 ──
|
|
107
|
-
if (visible.length > 0) {
|
|
108
|
-
this.spotGoal.planSpot(state, ctx); // 重算(排除威胁),逃完落到新点
|
|
109
|
-
this.setFlee();
|
|
110
|
-
this.emitProgress(state, ctx, visible, 'flee');
|
|
111
|
-
return [];
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ── 无人 → 走向最近安全躲藏点并潜伏 ──
|
|
115
|
-
if (this.subGoal) this.removeSubGoal();
|
|
116
|
-
const decisions = this.spotGoal.tick(state, ctx);
|
|
117
|
-
this.emitProgress(state, ctx, visible, decisions.length === 0 ? 'hidden' : 'relocate');
|
|
118
|
-
return decisions;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private killEnabled(state: GameState): boolean {
|
|
122
|
-
if (!roleHasKnife(state)) return false; // 无刀角色(普通虾)永不出刀,传 kill 参数也不行
|
|
123
|
-
return this.killWhenCornered ?? defaultKillWhenCornered(state);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** 单个对手、进入出刀距离、且 planEscape 判定必被追上 → 返回该对手,否则 null。 */
|
|
127
|
-
private corneredVictim(state: GameState, visible: PlayerInfo[]): PlayerInfo | null {
|
|
128
|
-
// 只在单个追兵时自保:2 人以上当众动手仍会被另一人举报,逃跑(推演偏悲观,真人有视野盲区)反而更优。
|
|
129
|
-
if (visible.length !== 1) return null;
|
|
130
|
-
const target = visible[0];
|
|
131
|
-
const d = target.distance ?? dist(state.you.x, state.you.y, target.x, target.y);
|
|
132
|
-
if (d > killCommitRange(state.you.role)) return null;
|
|
133
|
-
const plan = planEscape({ x: state.you.x, y: state.you.y }, [{ x: target.x, y: target.y }], ESCAPE_OPTS);
|
|
134
|
-
return plan.caught ? target : null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
private setFlee(): void {
|
|
138
|
-
const worker = this.subGoal;
|
|
139
|
-
if (worker instanceof KeepAwayGoal && worker.key === FLEE_KEY) return;
|
|
140
|
-
if (this.subGoal) this.removeSubGoal();
|
|
141
|
-
this.setSubGoal(new KeepAwayGoal(FLEE_KEY, (s, c) => nonTeammatesVisible(s, c), {
|
|
142
|
-
threatRadius: Infinity,
|
|
143
|
-
noun: '靠近的人',
|
|
144
|
-
}), SUB_PRIORITY);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/** 记住近期见过的非队友位置(按名字去重 + TTL),供威胁离开视野后选点继续回避。 */
|
|
148
|
-
private rememberThreats(visible: PlayerInfo[]): void {
|
|
149
|
-
const now = Date.now();
|
|
150
|
-
for (const p of visible) {
|
|
151
|
-
const key = p.name || String(p.seat ?? '');
|
|
152
|
-
if (key) this.recentThreats.set(key, { x: p.x, y: p.y, until: now + THREAT_MEMORY_MS });
|
|
153
|
-
}
|
|
154
|
-
for (const [key, v] of this.recentThreats) if (v.until <= now) this.recentThreats.delete(key);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
private threatPointsFor(): Position[] {
|
|
158
|
-
const now = Date.now();
|
|
159
|
-
const out: Position[] = [];
|
|
160
|
-
for (const v of this.recentThreats.values()) if (v.until > now) out.push({ x: v.x, y: v.y });
|
|
161
|
-
return out;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
private emitProgress(state: GameState, ctx: StrategyContext, visible: PlayerInfo[], tier: ProgressTier): void {
|
|
165
|
-
const now = Date.now();
|
|
166
|
-
if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
|
|
167
|
-
ctx.lastProgressNotifyAt = now;
|
|
168
|
-
|
|
169
|
-
const room = state.you.room ?? '未知';
|
|
170
|
-
const spot = this.spotGoal.currentSpot;
|
|
171
|
-
let msg = `[进度] 当前在${room}`;
|
|
172
|
-
if (tier === 'flee') {
|
|
173
|
-
const names = visible.map(p => p.name).join('、');
|
|
174
|
-
msg += `,${names}靠近,保持距离甩开${spot ? `,改藏到${spot.room}` : ''}。`;
|
|
175
|
-
} else if (tier === 'relocate') {
|
|
176
|
-
msg += spot ? `,前往躲藏点${spot.room}潜伏。` : ',寻找躲藏点。';
|
|
177
|
-
} else {
|
|
178
|
-
msg += spot ? `,已在${spot.room}潜伏,按兵不动。` : ',附近无可用躲藏点,原地待命。';
|
|
179
|
-
}
|
|
180
|
-
ctx.notifications.push(msg);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/** 是否带刀(有击杀能力):蟹 / 章鱼 / 武士虾 / 枪虾。普通虾无刀。 */
|
|
185
|
-
function roleHasKnife(state: GameState): boolean {
|
|
186
|
-
const role = state.you.role ?? '';
|
|
187
|
-
const faction = state.you.faction ?? '';
|
|
188
|
-
return faction === 'crab' || role.includes('crab') || role.includes('octopus')
|
|
189
|
-
|| role === 'shrimp_warrior' || role === 'shrimp_pistol';
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/** 角色默认是否「死角自保出刀」:蟹 / 章鱼开;武士虾 / 枪虾及其余关。 */
|
|
193
|
-
function defaultKillWhenCornered(state: GameState): boolean {
|
|
194
|
-
const role = state.you.role ?? '';
|
|
195
|
-
const faction = state.you.faction ?? '';
|
|
196
|
-
return faction === 'crab' || role.includes('crab') || role.includes('octopus');
|
|
197
|
-
}
|
|
1
|
+
import type { GameState, PlayerInfo, Position } from '../../sdk/types.js';
|
|
2
|
+
import { Action } from '../../sdk/action.js';
|
|
3
|
+
import {
|
|
4
|
+
canUseKill,
|
|
5
|
+
dist,
|
|
6
|
+
killCommitRange,
|
|
7
|
+
nonTeammatesVisible,
|
|
8
|
+
PROGRESS_INTERVAL_MS,
|
|
9
|
+
SHRIMP_KILL_RANGE,
|
|
10
|
+
} from '../game-utils.js';
|
|
11
|
+
import { nearestSafeHideSpot, type HideSpot } from '../hide-spots.js';
|
|
12
|
+
import { planEscape, type EscapeOptions } from '../pathfind/escape-planner.js';
|
|
13
|
+
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
14
|
+
import { Goal } from './goal.js';
|
|
15
|
+
import { KeepAwayGoal } from './keep-away-goal.js';
|
|
16
|
+
|
|
17
|
+
const SUB_PRIORITY = 0.5;
|
|
18
|
+
const FLEE_KEY = 'hide-flee';
|
|
19
|
+
/** 走到躲藏点这个距离内就算到位,原地潜伏不动。 */
|
|
20
|
+
const REACHED_DISTANCE = 40;
|
|
21
|
+
/** 威胁离开视野后,其位置仍在选点时被回避这么久——逃完不会立刻又选回威胁刚待过的点。 */
|
|
22
|
+
const THREAT_MEMORY_MS = 5_000;
|
|
23
|
+
/**
|
|
24
|
+
* 「无处可逃」用的逃跑推演参数:与 KeepAwayGoal 同款保守模型(killRange=160,按枪虾/武士虾兜底)。
|
|
25
|
+
* 理由是「连我自己用来逃跑的同一套推演都判定必被追上,逃就没意义,才出刀自保」,判据自洽。
|
|
26
|
+
*/
|
|
27
|
+
const ESCAPE_OPTS: EscapeOptions = { killRange: SHRIMP_KILL_RANGE, steps: 7, beamWidth: 8, directions: 16, fieldRadius: 900 };
|
|
28
|
+
|
|
29
|
+
type ProgressTier = 'flee' | 'relocate' | 'hidden';
|
|
30
|
+
type ThreatPointResolver = () => Position[];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 躲藏点劳作循环:与 SafeTaskOrPatrolGoal 同构——planSpot 边逃边预重算(粘性、威胁硬排除),
|
|
34
|
+
* tick 执行(走向躲藏点;到位则原地不动)。没有可用点时不动。
|
|
35
|
+
*/
|
|
36
|
+
class HideSpotGoal extends Goal {
|
|
37
|
+
private current: HideSpot | null = null;
|
|
38
|
+
|
|
39
|
+
constructor(private readonly threatPoints: ThreatPointResolver) {
|
|
40
|
+
super();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get currentSpot(): HideSpot | null {
|
|
44
|
+
return this.current;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
reset(): void {
|
|
48
|
+
this.current = null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** 重算并记住安全躲藏点,不发移动指令(逃跑期间可安全调用)。 */
|
|
52
|
+
planSpot(state: GameState, ctx: StrategyContext): HideSpot | null {
|
|
53
|
+
const spot = nearestSafeHideSpot(
|
|
54
|
+
{ x: state.you.x, y: state.you.y },
|
|
55
|
+
this.threatPoints(),
|
|
56
|
+
{ stickyTo: this.current, blockedTarget: ctx.blockedMoveTarget },
|
|
57
|
+
);
|
|
58
|
+
this.current = spot;
|
|
59
|
+
return spot;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
63
|
+
const spot = this.planSpot(state, ctx);
|
|
64
|
+
if (!spot) return [];
|
|
65
|
+
if (dist(state.you.x, state.you.y, spot.x, spot.y) <= REACHED_DISTANCE) return []; // 到位,原地潜伏
|
|
66
|
+
return [{ action: Action.move({ x: spot.x, y: spot.y }).withThinking(`前往躲藏点${spot.room}潜伏`) }];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 躲藏策略顶层:在离线算好的藏身角落里选测地最近且不挨威胁的一个潜伏不动,直到有人进入视野。
|
|
72
|
+
* 有人来就 ① 用 KeepAwayGoal 保持距离甩开,② 边逃边重算排除该威胁的新躲藏点——与 shrimp-memory 的
|
|
73
|
+
* 「躲人 + 重选任务」同构,只是把任务换成躲藏点。蟹不躲队友(威胁统一用 nonTeammatesVisible 过滤队友)。
|
|
74
|
+
*
|
|
75
|
+
* killWhenCornered:被**单个**对手逼进出刀距离、且 planEscape 判定无路可逃时才出刀自保。
|
|
76
|
+
* null = 按角色默认:蟹 / 章鱼开,武士虾 / 枪虾(及无刀角色)关。
|
|
77
|
+
*/
|
|
78
|
+
export class HideTop extends Goal {
|
|
79
|
+
private readonly spotGoal = new HideSpotGoal(() => this.threatPointsFor());
|
|
80
|
+
private readonly recentThreats = new Map<string, { x: number; y: number; until: number }>();
|
|
81
|
+
|
|
82
|
+
constructor(private readonly killWhenCornered: boolean | null = null) {
|
|
83
|
+
super();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
onMeetingResume(): void {
|
|
87
|
+
this.spotGoal.reset();
|
|
88
|
+
this.recentThreats.clear();
|
|
89
|
+
if (this.subGoal) this.removeSubGoal();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
93
|
+
const visible = nonTeammatesVisible(state, ctx);
|
|
94
|
+
this.rememberThreats(visible);
|
|
95
|
+
|
|
96
|
+
// ── 被单个对手逼到死角且刀好 → 出刀自保(按角色 / 参数开关) ──
|
|
97
|
+
if (this.killEnabled(state) && canUseKill(state)) {
|
|
98
|
+
const victim = this.corneredVictim(state, visible);
|
|
99
|
+
if (victim) {
|
|
100
|
+
if (this.subGoal) this.removeSubGoal();
|
|
101
|
+
ctx.notifications.push(`被${victim.name}逼到无处可逃,出刀自保!`);
|
|
102
|
+
return [{ action: Action.kill(victim.name).withThinking(`被${victim.name}逼进出刀距离又逃不掉,先下手自保。`) }];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── 视野里有人 → 保持距离甩开 + 边逃边重算安全躲藏点 ──
|
|
107
|
+
if (visible.length > 0) {
|
|
108
|
+
this.spotGoal.planSpot(state, ctx); // 重算(排除威胁),逃完落到新点
|
|
109
|
+
this.setFlee();
|
|
110
|
+
this.emitProgress(state, ctx, visible, 'flee');
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── 无人 → 走向最近安全躲藏点并潜伏 ──
|
|
115
|
+
if (this.subGoal) this.removeSubGoal();
|
|
116
|
+
const decisions = this.spotGoal.tick(state, ctx);
|
|
117
|
+
this.emitProgress(state, ctx, visible, decisions.length === 0 ? 'hidden' : 'relocate');
|
|
118
|
+
return decisions;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private killEnabled(state: GameState): boolean {
|
|
122
|
+
if (!roleHasKnife(state)) return false; // 无刀角色(普通虾)永不出刀,传 kill 参数也不行
|
|
123
|
+
return this.killWhenCornered ?? defaultKillWhenCornered(state);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** 单个对手、进入出刀距离、且 planEscape 判定必被追上 → 返回该对手,否则 null。 */
|
|
127
|
+
private corneredVictim(state: GameState, visible: PlayerInfo[]): PlayerInfo | null {
|
|
128
|
+
// 只在单个追兵时自保:2 人以上当众动手仍会被另一人举报,逃跑(推演偏悲观,真人有视野盲区)反而更优。
|
|
129
|
+
if (visible.length !== 1) return null;
|
|
130
|
+
const target = visible[0];
|
|
131
|
+
const d = target.distance ?? dist(state.you.x, state.you.y, target.x, target.y);
|
|
132
|
+
if (d > killCommitRange(state.you.role)) return null;
|
|
133
|
+
const plan = planEscape({ x: state.you.x, y: state.you.y }, [{ x: target.x, y: target.y }], ESCAPE_OPTS);
|
|
134
|
+
return plan.caught ? target : null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private setFlee(): void {
|
|
138
|
+
const worker = this.subGoal;
|
|
139
|
+
if (worker instanceof KeepAwayGoal && worker.key === FLEE_KEY) return;
|
|
140
|
+
if (this.subGoal) this.removeSubGoal();
|
|
141
|
+
this.setSubGoal(new KeepAwayGoal(FLEE_KEY, (s, c) => nonTeammatesVisible(s, c), {
|
|
142
|
+
threatRadius: Infinity,
|
|
143
|
+
noun: '靠近的人',
|
|
144
|
+
}), SUB_PRIORITY);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 记住近期见过的非队友位置(按名字去重 + TTL),供威胁离开视野后选点继续回避。 */
|
|
148
|
+
private rememberThreats(visible: PlayerInfo[]): void {
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
for (const p of visible) {
|
|
151
|
+
const key = p.name || String(p.seat ?? '');
|
|
152
|
+
if (key) this.recentThreats.set(key, { x: p.x, y: p.y, until: now + THREAT_MEMORY_MS });
|
|
153
|
+
}
|
|
154
|
+
for (const [key, v] of this.recentThreats) if (v.until <= now) this.recentThreats.delete(key);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private threatPointsFor(): Position[] {
|
|
158
|
+
const now = Date.now();
|
|
159
|
+
const out: Position[] = [];
|
|
160
|
+
for (const v of this.recentThreats.values()) if (v.until > now) out.push({ x: v.x, y: v.y });
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private emitProgress(state: GameState, ctx: StrategyContext, visible: PlayerInfo[], tier: ProgressTier): void {
|
|
165
|
+
const now = Date.now();
|
|
166
|
+
if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
|
|
167
|
+
ctx.lastProgressNotifyAt = now;
|
|
168
|
+
|
|
169
|
+
const room = state.you.room ?? '未知';
|
|
170
|
+
const spot = this.spotGoal.currentSpot;
|
|
171
|
+
let msg = `[进度] 当前在${room}`;
|
|
172
|
+
if (tier === 'flee') {
|
|
173
|
+
const names = visible.map(p => p.name).join('、');
|
|
174
|
+
msg += `,${names}靠近,保持距离甩开${spot ? `,改藏到${spot.room}` : ''}。`;
|
|
175
|
+
} else if (tier === 'relocate') {
|
|
176
|
+
msg += spot ? `,前往躲藏点${spot.room}潜伏。` : ',寻找躲藏点。';
|
|
177
|
+
} else {
|
|
178
|
+
msg += spot ? `,已在${spot.room}潜伏,按兵不动。` : ',附近无可用躲藏点,原地待命。';
|
|
179
|
+
}
|
|
180
|
+
ctx.notifications.push(msg);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** 是否带刀(有击杀能力):蟹 / 章鱼 / 武士虾 / 枪虾。普通虾无刀。 */
|
|
185
|
+
function roleHasKnife(state: GameState): boolean {
|
|
186
|
+
const role = state.you.role ?? '';
|
|
187
|
+
const faction = state.you.faction ?? '';
|
|
188
|
+
return faction === 'crab' || role.includes('crab') || role.includes('octopus')
|
|
189
|
+
|| role === 'shrimp_warrior' || role === 'shrimp_pistol';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** 角色默认是否「死角自保出刀」:蟹 / 章鱼开;武士虾 / 枪虾及其余关。 */
|
|
193
|
+
function defaultKillWhenCornered(state: GameState): boolean {
|
|
194
|
+
const role = state.you.role ?? '';
|
|
195
|
+
const faction = state.you.faction ?? '';
|
|
196
|
+
return faction === 'crab' || role.includes('crab') || role.includes('octopus');
|
|
197
|
+
}
|