@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,33 @@
|
|
|
1
|
+
import type { GameState, TaskInfo } from '../../sdk/types.js';
|
|
2
|
+
import { approachOrDoTaskStep, patrolStep, PatrolState } from '../game-utils.js';
|
|
3
|
+
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
4
|
+
import { Goal } from './goal.js';
|
|
5
|
+
|
|
6
|
+
export type TaskPicker = (state: GameState, ctx: StrategyContext) => TaskInfo | null;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 任务伪装 + 巡逻子目标(叶子,由上层以 WANDER 优先级挂载):先按 pickTask 取一个任务靠近 / 提交,
|
|
10
|
+
* 没有可做任务、或到点暂不可提交,就按房间巡逻,绝不停在原地。供 lone-kill-task / crab-sabotage 复用,
|
|
11
|
+
* pickTask 决定做哪类任务,stopOnPlayer 通常接 LoneKillCore.patrolStopOnPlayer(),patrol 由上层注入以共享巡逻进度。
|
|
12
|
+
*/
|
|
13
|
+
export class WanderTaskGoal extends Goal {
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly pickTask: TaskPicker,
|
|
16
|
+
private readonly stopOnPlayer: () => boolean,
|
|
17
|
+
private readonly patrol: PatrolState,
|
|
18
|
+
) {
|
|
19
|
+
super();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
23
|
+
if (state.you.doing_task) return [];
|
|
24
|
+
|
|
25
|
+
const task = this.pickTask(state, ctx);
|
|
26
|
+
if (task) {
|
|
27
|
+
const decision = approachOrDoTaskStep(state, ctx, task, this.stopOnPlayer());
|
|
28
|
+
if (decision) return decision;
|
|
29
|
+
// 到点却暂时无法提交 → 转巡逻,避免卡在任务点
|
|
30
|
+
}
|
|
31
|
+
return patrolStep(state, ctx, this.patrol, this.stopOnPlayer());
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildKnowledgeView, type KnowledgeFile } from '../../lib/knowledge-store.js';
|
|
3
|
+
import type { GameState } from '../../sdk/types.js';
|
|
4
|
+
import type { StrategyContext } from '../types.js';
|
|
5
|
+
import { KeepAwayGoal } from './keep-away-goal.js';
|
|
6
|
+
import { KillVisibleTargetGoal } from './kill-target-goal.js';
|
|
7
|
+
import { WarriorShrimpTop } from './warrior-shrimp-top.js';
|
|
8
|
+
|
|
9
|
+
function state(): GameState {
|
|
10
|
+
return {
|
|
11
|
+
phase: 'wandering',
|
|
12
|
+
tick: 1,
|
|
13
|
+
you: {
|
|
14
|
+
name: 'warrior',
|
|
15
|
+
x: 0,
|
|
16
|
+
y: 0,
|
|
17
|
+
room: 'control',
|
|
18
|
+
role: 'shrimp_warrior',
|
|
19
|
+
faction: 'lobster',
|
|
20
|
+
is_alive: true,
|
|
21
|
+
kill_cooldown_secs: 0,
|
|
22
|
+
kills_remaining: 1,
|
|
23
|
+
},
|
|
24
|
+
your_tasks: [],
|
|
25
|
+
players: [{ name: 'target', seat: 2, x: 100, y: 0, room: 'control', distance: 100 }],
|
|
26
|
+
all_players: [{ name: 'target', seat: 2, role: 'unknown', is_alive: true }],
|
|
27
|
+
corpses: [],
|
|
28
|
+
stale: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function knowledge(mark: 'suspect' | 'hostile' | 'trusted') {
|
|
33
|
+
const ts = Date.now();
|
|
34
|
+
const file: KnowledgeFile = {
|
|
35
|
+
version: 1,
|
|
36
|
+
gameId: 'game-1',
|
|
37
|
+
subjects: {
|
|
38
|
+
'player:target': {
|
|
39
|
+
type: 'player',
|
|
40
|
+
selector: 'target',
|
|
41
|
+
tags: { [mark]: { ts } },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
return buildKnowledgeView(file);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function context(overrides: Partial<StrategyContext> = {}): StrategyContext {
|
|
49
|
+
return {
|
|
50
|
+
taskData: [],
|
|
51
|
+
emergency: null,
|
|
52
|
+
taskLocalBlockedUntil: 0,
|
|
53
|
+
reportCorpseTarget: null,
|
|
54
|
+
reportBlockedUntil: 0,
|
|
55
|
+
notifications: [],
|
|
56
|
+
lastProgressNotifyAt: Date.now(),
|
|
57
|
+
teammates: new Set(),
|
|
58
|
+
alarmDone: false,
|
|
59
|
+
rooms: [{ name: 'patrol', x: 500, y: 0 }],
|
|
60
|
+
playerNamesBySeat: { '2': 'target' },
|
|
61
|
+
forcePatrolAdvance: false,
|
|
62
|
+
blockedMoveTarget: null,
|
|
63
|
+
mySeat: 1,
|
|
64
|
+
speechNotifications: [],
|
|
65
|
+
agentAlerts: [],
|
|
66
|
+
...overrides,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
describe('WarriorShrimpTop knowledge tiers', () => {
|
|
71
|
+
it('avoids a suspect even when the weapon is ready', () => {
|
|
72
|
+
const top = new WarriorShrimpTop();
|
|
73
|
+
|
|
74
|
+
top.tick(state(), context({ knowledge: knowledge('suspect') }));
|
|
75
|
+
|
|
76
|
+
expect(top.subGoal).toBeInstanceOf(KeepAwayGoal);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('hunts hostile targets', () => {
|
|
80
|
+
const top = new WarriorShrimpTop();
|
|
81
|
+
|
|
82
|
+
top.tick(state(), context({ knowledge: knowledge('hostile') }));
|
|
83
|
+
|
|
84
|
+
expect(top.subGoal).toBeInstanceOf(KillVisibleTargetGoal);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import type { GameState, PlayerInfo } from '../../sdk/types.js';
|
|
2
|
+
import { Action } from '../../sdk/action.js';
|
|
3
|
+
import {
|
|
4
|
+
canUseKill,
|
|
5
|
+
corpseAtScene,
|
|
6
|
+
dist,
|
|
7
|
+
firstAvailableTask,
|
|
8
|
+
hasKnownCorpse,
|
|
9
|
+
isKnowledgeHostile,
|
|
10
|
+
isKnowledgeThreat,
|
|
11
|
+
isKnowledgeTrusted,
|
|
12
|
+
killCooldownSecs,
|
|
13
|
+
KILL_RANGE,
|
|
14
|
+
matchesAnyTarget,
|
|
15
|
+
nonTeammatesVisible,
|
|
16
|
+
PROGRESS_INTERVAL_MS,
|
|
17
|
+
reportCorpseDecision,
|
|
18
|
+
SHRIMP_VISION_RANGE,
|
|
19
|
+
SHRIMP_VISION_RELEASE_RANGE,
|
|
20
|
+
taskMoveDecision,
|
|
21
|
+
} from '../game-utils.js';
|
|
22
|
+
import type { BehaviorDecision, StrategyContext } from '../types.js';
|
|
23
|
+
import { Goal } from './goal.js';
|
|
24
|
+
import { KeepAwayGoal, type KeepAwayGoalOptions } from './keep-away-goal.js';
|
|
25
|
+
import { KillVisibleTargetGoal } from './kill-target-goal.js';
|
|
26
|
+
import { SafeTaskOrPatrolGoal } from './safe-task-goal.js';
|
|
27
|
+
|
|
28
|
+
const SUB_PRIORITY = 0.5;
|
|
29
|
+
const STRANGER_TRIGGER_RADIUS = SHRIMP_VISION_RANGE;
|
|
30
|
+
const STRANGER_RELEASE_RADIUS = SHRIMP_VISION_RELEASE_RANGE;
|
|
31
|
+
const WITNESS_MEMORY_MS = 0;
|
|
32
|
+
const WITNESS_EXEMPT_COUNT = 2;
|
|
33
|
+
const TASK_RETURN_INERTIA_MS = 2_000;
|
|
34
|
+
const FLEE_KEY = 'warrior-flee';
|
|
35
|
+
const STRANGER_KEY = 'warrior-stranger-distance';
|
|
36
|
+
|
|
37
|
+
type ProgressTier = 'flee-bad' | 'keep-distance' | 'task';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 带刀虾·记忆进阶版(武士虾/枪虾,带刀好人)。好人阵营但能出刀,按行为表的优先级逐条判断:
|
|
41
|
+
*
|
|
42
|
+
* - P0 发现尸体:旁边有非保护对象且刀可用 → 先出刀;否则报警(靠近至可报距离)。
|
|
43
|
+
* - P1 处理危险:明确猎杀目标(启动参数 / hostile)刀好就追杀;
|
|
44
|
+
* suspect 只表示可疑,始终回避,不会仅凭怀疑主动出刀;
|
|
45
|
+
* 无可信同伴且单个无标记陌生人贴近 → 按普通虾 B 档先拉开距离。
|
|
46
|
+
* - P2 紧急任务:无危险时优先处理。
|
|
47
|
+
* - P3 兜底:做任务 / 巡逻
|
|
48
|
+
* (SafeTaskOrPatrolGoal:测地最近、威胁旁/途经威胁的任务硬排除、带粘性)。
|
|
49
|
+
*
|
|
50
|
+
* 谁算「认定坏人」「可信同伴」来自 ctx.knowledge(见 warrior-memory.knowledge.md),
|
|
51
|
+
* 队友由游戏事实判定(nonTeammatesVisible),知识库只提供推断。
|
|
52
|
+
*/
|
|
53
|
+
export class WarriorShrimpTop extends Goal {
|
|
54
|
+
private readonly taskGoal = new SafeTaskOrPatrolGoal(
|
|
55
|
+
// 威胁点 = 当前会触发追杀/后撤的对象,或未被目击者豁免的无标记陌生人;保护对象不算。
|
|
56
|
+
(s, c) => nonTeammatesVisible(s, c)
|
|
57
|
+
.filter(p => !this.isTrusted(p, c))
|
|
58
|
+
.filter(p => this.isThreat(p, c) || !this.witnessExempt)
|
|
59
|
+
.map(p => ({ x: p.x, y: p.y })),
|
|
60
|
+
);
|
|
61
|
+
private readonly strangerSeenAt = new Map<string, number>();
|
|
62
|
+
private witnessExempt = false;
|
|
63
|
+
|
|
64
|
+
constructor(private readonly huntTargets: string[] = []) {
|
|
65
|
+
super();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
tick(state: GameState, ctx: StrategyContext): BehaviorDecision[] {
|
|
69
|
+
const visible = nonTeammatesVisible(state, ctx);
|
|
70
|
+
this.updateWitnessMemory(visible, ctx);
|
|
71
|
+
const killReady = canUseKill(state);
|
|
72
|
+
|
|
73
|
+
// ── P0 发现尸体 ──
|
|
74
|
+
// 灭口贴脸目击者只在「此刻就在命案现场」(当帧视野有尸体)时考虑——记忆里某处有尸体不算现场,
|
|
75
|
+
// 否则会变成随便撞见一个人就灭口。
|
|
76
|
+
if (corpseAtScene(state) && killReady) {
|
|
77
|
+
const victim = this.nearestKillable(state, ctx, visible);
|
|
78
|
+
if (victim) {
|
|
79
|
+
this.clearSub();
|
|
80
|
+
return [{ action: Action.kill(victim.name) }];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// 报尸 / 前往报尸:可以追着记忆里看见过、已离开视野的尸体走回去报告。
|
|
84
|
+
if (hasKnownCorpse(ctx)) {
|
|
85
|
+
const report = reportCorpseDecision(state, ctx, { respectBlock: true });
|
|
86
|
+
if (report) {
|
|
87
|
+
this.clearSub();
|
|
88
|
+
return [report];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── P1-A hostile 刀好就追;suspect 只回避 ──
|
|
93
|
+
const threats = visible.filter(p => this.isThreat(p, ctx));
|
|
94
|
+
if (threats.length > 0) {
|
|
95
|
+
const huntTargets = threats.filter(p => this.isHuntTarget(p, ctx));
|
|
96
|
+
if (killReady && huntTargets.length > 0) {
|
|
97
|
+
this.setHunt(this.nearestByDistance(state, huntTargets).name);
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
this.taskGoal.planTask(state, ctx, { holdUnsafeCurrentForMs: TASK_RETURN_INERTIA_MS });
|
|
101
|
+
this.setFlee();
|
|
102
|
+
this.emitProgress(state, ctx, visible, 'flee-bad');
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── P1-B 无可信同伴且无标记陌生人贴近 → 按普通虾 B 档拉开距离 ──
|
|
107
|
+
const trustedVisible = visible.some(p => this.isTrusted(p, ctx));
|
|
108
|
+
if (!trustedVisible) {
|
|
109
|
+
const worker = this.subGoal;
|
|
110
|
+
const strangerClose = visible.some(p =>
|
|
111
|
+
this.isUnmarkedStranger(p, ctx)
|
|
112
|
+
&& (p.distance ?? dist(p.x, p.y, state.you.x, state.you.y)) <= STRANGER_TRIGGER_RADIUS);
|
|
113
|
+
const stillBackingOff = worker instanceof KeepAwayGoal && worker.key === STRANGER_KEY && !worker.isFinish(state, ctx);
|
|
114
|
+
if (!this.witnessExempt && (strangerClose || stillBackingOff)) {
|
|
115
|
+
this.taskGoal.planTask(state, ctx, { holdUnsafeCurrentForMs: TASK_RETURN_INERTIA_MS });
|
|
116
|
+
this.setStrangerKeepAway();
|
|
117
|
+
this.emitProgress(state, ctx, visible, 'keep-distance');
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── P2 紧急任务 ──
|
|
123
|
+
const emergency = firstAvailableTask([], t => t.faction !== 'crab', ctx.emergency, ctx.blockedMoveTarget);
|
|
124
|
+
if (emergency) {
|
|
125
|
+
this.clearSub();
|
|
126
|
+
return [taskMoveDecision(state, ctx, emergency)];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── P3 普通任务 / 巡逻 ──
|
|
130
|
+
this.clearSub();
|
|
131
|
+
this.emitProgress(state, ctx, visible, 'task');
|
|
132
|
+
return this.taskGoal.tick(state, ctx);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** 目击者豁免:当前只按本轮可见玩家判断,见到 2 个以上陌生人时不对无标记者后撤。 */
|
|
136
|
+
private updateWitnessMemory(visible: PlayerInfo[], ctx: StrategyContext): void {
|
|
137
|
+
const now = Date.now();
|
|
138
|
+
for (const p of visible) {
|
|
139
|
+
if (!this.isUnmarkedStranger(p, ctx)) continue;
|
|
140
|
+
const key = p.name || String(p.seat ?? '');
|
|
141
|
+
if (key) this.strangerSeenAt.set(key, now);
|
|
142
|
+
}
|
|
143
|
+
for (const [key, at] of this.strangerSeenAt) {
|
|
144
|
+
if (now - at > WITNESS_MEMORY_MS) this.strangerSeenAt.delete(key);
|
|
145
|
+
}
|
|
146
|
+
this.witnessExempt = this.strangerSeenAt.size >= WITNESS_EXEMPT_COUNT;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private isUnmarkedStranger(p: PlayerInfo, ctx: StrategyContext): boolean {
|
|
150
|
+
return !this.isTrusted(p, ctx) && !this.isThreat(p, ctx);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** 知识里 trusted 的玩家:可信同伴,永不击杀。 */
|
|
154
|
+
private isTrusted(p: PlayerInfo, ctx: StrategyContext): boolean {
|
|
155
|
+
return isKnowledgeTrusted(p, ctx);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** 明确猎杀目标:启动参数或 hostile;suspect 不在这里。 */
|
|
159
|
+
private isHuntTarget(p: PlayerInfo, ctx: StrategyContext): boolean {
|
|
160
|
+
return !this.isTrusted(p, ctx)
|
|
161
|
+
&& (matchesAnyTarget(p, this.huntTargets, ctx) || isKnowledgeHostile(p, ctx));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** 威胁:明确猎杀目标,或知识库 suspect 标记;trusted 永远排除。 */
|
|
165
|
+
private isThreat(p: PlayerInfo, ctx: StrategyContext): boolean {
|
|
166
|
+
return !this.isTrusted(p, ctx)
|
|
167
|
+
&& (this.isHuntTarget(p, ctx) || isKnowledgeThreat(p, ctx));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** 尸体场景下「认定坏人/身份不明」= 视野内最近的非保护非队友,且在出刀范围内。 */
|
|
171
|
+
private nearestKillable(state: GameState, ctx: StrategyContext, visible: PlayerInfo[]): PlayerInfo | null {
|
|
172
|
+
return visible
|
|
173
|
+
.filter(p => !this.isTrusted(p, ctx))
|
|
174
|
+
.map(p => ({ p, d: p.distance ?? dist(state.you.x, state.you.y, p.x, p.y) }))
|
|
175
|
+
.filter(x => x.d <= KILL_RANGE)
|
|
176
|
+
.sort((a, b) => a.d - b.d)[0]?.p ?? null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private nearestByDistance(state: GameState, players: PlayerInfo[]): PlayerInfo {
|
|
180
|
+
return players
|
|
181
|
+
.map(p => ({ p, d: p.distance ?? dist(state.you.x, state.you.y, p.x, p.y) }))
|
|
182
|
+
.sort((a, b) => a.d - b.d)[0].p;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private setHunt(targetName: string): void {
|
|
186
|
+
const worker = this.subGoal;
|
|
187
|
+
if (worker instanceof KillVisibleTargetGoal && worker.targetName === targetName) return;
|
|
188
|
+
if (this.subGoal) this.removeSubGoal();
|
|
189
|
+
this.setSubGoal(new KillVisibleTargetGoal(targetName, [], true), SUB_PRIORITY);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private setFlee(): void {
|
|
193
|
+
const worker = this.subGoal;
|
|
194
|
+
if (worker instanceof KeepAwayGoal && worker.key === FLEE_KEY) return;
|
|
195
|
+
if (this.subGoal) this.removeSubGoal();
|
|
196
|
+
this.setSubGoal(new KeepAwayGoal(
|
|
197
|
+
FLEE_KEY,
|
|
198
|
+
(s, c) => nonTeammatesVisible(s, c).filter(p => this.isThreat(p, c)),
|
|
199
|
+
{ threatRadius: Infinity, noun: '认定坏人' } satisfies KeepAwayGoalOptions,
|
|
200
|
+
), SUB_PRIORITY);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private setStrangerKeepAway(): void {
|
|
204
|
+
const worker = this.subGoal;
|
|
205
|
+
if (worker instanceof KeepAwayGoal && worker.key === STRANGER_KEY) return;
|
|
206
|
+
if (this.subGoal) this.removeSubGoal();
|
|
207
|
+
this.setSubGoal(new KeepAwayGoal(
|
|
208
|
+
STRANGER_KEY,
|
|
209
|
+
(s, c) => nonTeammatesVisible(s, c).filter(p => this.isUnmarkedStranger(p, c)),
|
|
210
|
+
{
|
|
211
|
+
threatRadius: STRANGER_RELEASE_RADIUS,
|
|
212
|
+
noun: '陌生人',
|
|
213
|
+
planOpts: { steps: 4 },
|
|
214
|
+
} satisfies KeepAwayGoalOptions,
|
|
215
|
+
), SUB_PRIORITY);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private clearSub(): void {
|
|
219
|
+
if (this.subGoal) this.removeSubGoal();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private emitProgress(state: GameState, ctx: StrategyContext, visible: PlayerInfo[], tier: ProgressTier): void {
|
|
223
|
+
const now = Date.now();
|
|
224
|
+
if (now - ctx.lastProgressNotifyAt < PROGRESS_INTERVAL_MS) return;
|
|
225
|
+
ctx.lastProgressNotifyAt = now;
|
|
226
|
+
|
|
227
|
+
const room = state.you.room ?? '未知';
|
|
228
|
+
const cd = killCooldownSecs(state);
|
|
229
|
+
const killsRemaining = state.you.kills_remaining;
|
|
230
|
+
|
|
231
|
+
let msg = `[进度] 当前在${room}`;
|
|
232
|
+
if (visible.length === 0) {
|
|
233
|
+
msg += ',附近无人,做任务/巡逻。';
|
|
234
|
+
} else if (tier === 'keep-distance') {
|
|
235
|
+
msg += ',无标记陌生人靠得太近,先拉开距离再做任务。';
|
|
236
|
+
} else {
|
|
237
|
+
const bad = visible.filter(p => this.isThreat(p, ctx)).map(p => p.name);
|
|
238
|
+
if (bad.length > 0) msg += `,发现认定坏人${bad.join('、')}`;
|
|
239
|
+
else if (this.witnessExempt) msg += `,附近有${visible.length}个无标记陌生人互为目击者,正常做任务`;
|
|
240
|
+
else msg += `,附近有${visible.length}人,正常做任务`;
|
|
241
|
+
if (tier === 'flee-bad') msg += killCooldownSecs(state) > 0 ? ',刀在冷却,正在回避' : ',正在处理';
|
|
242
|
+
if (cd > 0) msg += `(攻击冷却${cd}s)`;
|
|
243
|
+
if (killsRemaining === 0) msg += '(出刀次数已用完)';
|
|
244
|
+
msg += '。';
|
|
245
|
+
}
|
|
246
|
+
ctx.notifications.push(msg);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Action } from '../sdk/action.js';
|
|
2
|
+
import type { GameState } from '../sdk/types.js';
|
|
3
|
+
import type { BehaviorDecision } from './types.js';
|
|
4
|
+
import { TTS_TEXT_MAX_LENGTH } from '../lib/tts-keys.js';
|
|
5
|
+
import { stripRoleIds } from './player-targets.js';
|
|
6
|
+
|
|
7
|
+
export const MAX_GREETING_PHRASES = 3;
|
|
8
|
+
export const GREETING_COOLDOWN_MS = 120_000;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 解析「打招呼话术」启动参数:剥掉 role-id 噪声与空串,校验条数(≤3)与单条长度(≤TTS 上限)。
|
|
12
|
+
* 供 task-report / corpse-patrol / shrimp-memory / paradise-fish 共用——它们的启动参数语义都是「打招呼话术」。
|
|
13
|
+
*
|
|
14
|
+
* @param strategyId 仅用于错误文案。
|
|
15
|
+
*/
|
|
16
|
+
export function parseGreetingArgs(args: string[] | undefined, strategyId: string): string[] {
|
|
17
|
+
if (!args || args.length === 0) return [];
|
|
18
|
+
|
|
19
|
+
const phrases = args.map(a => stripRoleIds(a)).filter(a => a.length > 0);
|
|
20
|
+
if (phrases.length === 0) return [];
|
|
21
|
+
|
|
22
|
+
if (phrases.length > MAX_GREETING_PHRASES) {
|
|
23
|
+
throw new Error(`${strategyId} strategy accepts 0-${MAX_GREETING_PHRASES} greeting phrases (got ${phrases.length}).`);
|
|
24
|
+
}
|
|
25
|
+
for (const phrase of phrases) {
|
|
26
|
+
if (phrase.length > TTS_TEXT_MAX_LENGTH) {
|
|
27
|
+
throw new Error(`Greeting phrase too long (${phrase.length} chars, max ${TTS_TEXT_MAX_LENGTH}).`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return phrases;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 打招呼节流器:视野内有人时随机发一条话术,之后 GREETING_COOLDOWN_MS 内不再发。
|
|
35
|
+
* 被多个编排型 Top Goal(TaskReportTop / NormalShrimpTop 等)持有复用。
|
|
36
|
+
*/
|
|
37
|
+
export class GreetingTracker {
|
|
38
|
+
private lastGreetingAt: number | null = null;
|
|
39
|
+
|
|
40
|
+
constructor(private readonly phrases: string[]) {}
|
|
41
|
+
|
|
42
|
+
tryGreeting(state: GameState): BehaviorDecision | null {
|
|
43
|
+
if (this.phrases.length === 0) return null;
|
|
44
|
+
if (state.players.length === 0) return null;
|
|
45
|
+
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
if (this.lastGreetingAt !== null && now - this.lastGreetingAt < GREETING_COOLDOWN_MS) return null;
|
|
48
|
+
|
|
49
|
+
const text = this.phrases[Math.floor(Math.random() * this.phrases.length)];
|
|
50
|
+
this.lastGreetingAt = now;
|
|
51
|
+
return { action: Action.speech(text) };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StrategyEntry } from './types.js';
|
|
2
|
+
import { GoalRootStrategy } from './goals/goal-root-strategy.js';
|
|
3
|
+
import { KillFrenzyTop } from './goals/kill-frenzy-top.js';
|
|
4
|
+
|
|
5
|
+
export const strategy: StrategyEntry = {
|
|
6
|
+
id: 'kill-frenzy',
|
|
7
|
+
description:
|
|
8
|
+
'疯狂砍人:见谁杀谁(除队友外所有可见玩家),多人在场也照杀。按房间顺序巡逻,视野里只要有非队友就冲向最近的那个,50内且冷却好就出刀,冷却中保持10距离贴身跟随等冷却。与 kill-lone 不同,不会因为周围人多而暂停攻击。永不停手。',
|
|
9
|
+
create() {
|
|
10
|
+
return new GoalRootStrategy('kill-frenzy', () => new KillFrenzyTop(), { resetOnMeetingResume: false });
|
|
11
|
+
},
|
|
12
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# kill-lone 知识契约
|
|
2
|
+
|
|
3
|
+
`kill-lone` 读取统一的玩家三档标记:
|
|
4
|
+
|
|
5
|
+
| 标记 | 行为 |
|
|
6
|
+
|------|------|
|
|
7
|
+
| `suspect` | 不升级为主动猎杀目标,仍按默认落单规则判断 |
|
|
8
|
+
| `hostile` | 视为明确猎杀目标,见到就追杀,可突破多人保护规则 |
|
|
9
|
+
| `trusted` | 永不击杀,优先级高于其他猎杀判断 |
|
|
10
|
+
|
|
11
|
+
未标记玩家维持默认行为:仅猎杀落单的非队友。
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
ccl knowledge mark 5 suspect --confidence 0.7 --note "行为可疑"
|
|
15
|
+
ccl knowledge mark 5 hostile --confidence 0.9 --note "确认敌对"
|
|
16
|
+
ccl knowledge mark 3 trusted --note "已澄清"
|
|
17
|
+
ccl knowledge del player 5
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`role` 是纯身份事实,不直接控制策略。
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { StrategyEntry } from './types.js';
|
|
2
|
+
import { GoalRootStrategy } from './goals/goal-root-strategy.js';
|
|
3
|
+
import { KillLoneTop } from './goals/kill-lone-top.js';
|
|
4
|
+
import { parseTargetArgs } from './player-targets.js';
|
|
5
|
+
|
|
6
|
+
export const strategy: StrategyEntry = {
|
|
7
|
+
id: 'kill-lone',
|
|
8
|
+
description: '按照房间顺序进行巡逻,巡逻时遇到人会判断。可传入避免击杀的座位号或名字列表。视野里只有一个非队友且不在避免名单内就靠近,进入50距离就出刀;冷却中贴在10距离附近等。视野里出现两人以上时,暂停攻击10秒并继续巡逻。',
|
|
9
|
+
create(args?: string[]) {
|
|
10
|
+
const protectedTargets = args ? parseTargetArgs(args) : [];
|
|
11
|
+
return new GoalRootStrategy('kill-lone', () => new KillLoneTop(protectedTargets), { resetOnMeetingResume: false });
|
|
12
|
+
},
|
|
13
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { StrategyEntry } from './types.js';
|
|
2
|
+
import { GoalRootStrategy } from './goals/goal-root-strategy.js';
|
|
3
|
+
import { KillTargetTop } from './goals/kill-target-top.js';
|
|
4
|
+
import { parseTargetArgs } from './player-targets.js';
|
|
5
|
+
|
|
6
|
+
export const strategy: StrategyEntry = {
|
|
7
|
+
id: 'kill-target',
|
|
8
|
+
description: '专门追杀指定目标(传入座位号或名字,可多人)。按照房间顺序进行巡逻,目标进入视野就冲上去,50内且冷却好就出刀;冷却中保持10距离贴身跟随等待。找目标时路过其他玩家不会停步。所有目标死亡后停止。',
|
|
9
|
+
create(args?: string[]) {
|
|
10
|
+
const targets = args ? parseTargetArgs(args) : [];
|
|
11
|
+
if (targets.length === 0) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
'kill-target strategy requires target seat numbers or player names (comma- or space-separated).',
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return new GoalRootStrategy('kill-target', () => new KillTargetTop(targets), { resetOnMeetingResume: false });
|
|
17
|
+
},
|
|
18
|
+
};
|