@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,53 +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
|
-
}
|
|
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
|
+
}
|
|
@@ -1,123 +1,123 @@
|
|
|
1
|
-
import type { Position } from '../sdk/types.js';
|
|
2
|
-
import { dist } from './game-utils.js';
|
|
3
|
-
import { assessRoutes } from './pathfind/escape-planner.js';
|
|
4
|
-
|
|
5
|
-
export interface HideSpot {
|
|
6
|
-
/** 世界像素坐标,与 GameState / 可行走网格同坐标系。 */
|
|
7
|
-
x: number;
|
|
8
|
-
y: number;
|
|
9
|
-
/** 所在房间,仅供日志/调试。 */
|
|
10
|
-
room: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* 躲藏点:离线从服务端烘焙地图(config/clawclaw/clawclaw.tmj.baked.npz)算出的「藏身角落」。筛选条件:
|
|
15
|
-
* - 所在房间有 ≥2 个出口(非死胡同,被追能逃);**走廊**(做任务的必经路线)一律排除;
|
|
16
|
-
* - 出口 ≥4 的枢纽房间排除(穿行人流太大,藏不住);
|
|
17
|
-
* - 取房间内「离各出口最深、且远离任务点/出生点」的可走格(缩在内侧角落,避开穿行线与刷新点)。
|
|
18
|
-
*
|
|
19
|
-
* 重新生成:scripts/find-hide-spots.py(用后端 venv 解释器跑,读 baked.npz,打印可粘贴的本数组)。
|
|
20
|
-
* 这是离线烘焙数据,和 pathfind 的可行走网格一样:运行时按需 snap 到最近可走格即可。
|
|
21
|
-
*/
|
|
22
|
-
export const HIDE_SPOTS: readonly HideSpot[] = [
|
|
23
|
-
{ x: 2882, y: 1362, room: '导航仓' },
|
|
24
|
-
{ x: 2386, y: 1138, room: '健身房' },
|
|
25
|
-
{ x: 1922, y: 1234, room: '酒吧' },
|
|
26
|
-
{ x: 2418, y: 2130, room: '休闲会所' },
|
|
27
|
-
{ x: 1538, y: 1826, room: '监控室' },
|
|
28
|
-
{ x: 514, y: 2402, room: '动力监控室' },
|
|
29
|
-
{ x: 1538, y: 2514, room: '自助餐厅' },
|
|
30
|
-
{ x: 1314, y: 2674, room: '制氧舱' },
|
|
31
|
-
{ x: 2514, y: 2882, room: '中央厨房' },
|
|
32
|
-
{ x: 2882, y: 2994, room: '冷库' },
|
|
33
|
-
{ x: 3858, y: 2434, room: '情报室' },
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
/** 躲藏点离任一威胁多近就硬排除(与 nearestSafeTask 的端点排除同量级)。 */
|
|
37
|
-
export const HIDE_THREAT_EXCLUDE_RANGE = 500;
|
|
38
|
-
/** 去躲藏点的测地路径经过威胁这个距离内也排除(避免走向把守者)。 */
|
|
39
|
-
export const HIDE_PATH_THREAT_RADIUS = 350;
|
|
40
|
-
|
|
41
|
-
export interface NearestSafeHideOptions {
|
|
42
|
-
threatExcludeRadius?: number;
|
|
43
|
-
pathThreatRadius?: number;
|
|
44
|
-
/** 当前已选躲藏点:仍是合法候选就保持不变,杜绝等距摇摆。 */
|
|
45
|
-
stickyTo?: HideSpot | null;
|
|
46
|
-
/** 被标记为不可达的移动目标:在其附近的点跳过。 */
|
|
47
|
-
blockedTarget?: Position | null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const BLOCKED_RADIUS = 12;
|
|
51
|
-
|
|
52
|
-
function sameSpot(a: HideSpot, b: HideSpot): boolean {
|
|
53
|
-
return a.x === b.x && a.y === b.y;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** 所有点都被威胁封死时的兜底:离威胁最远的点(仍好过原地等死)。 */
|
|
57
|
-
function farthestFromThreats(threatPoints: Position[], blocked?: Position | null): HideSpot | null {
|
|
58
|
-
let best: HideSpot | null = null;
|
|
59
|
-
let bestMin = -Infinity;
|
|
60
|
-
for (const s of HIDE_SPOTS) {
|
|
61
|
-
if (blocked && dist(s.x, s.y, blocked.x, blocked.y) <= BLOCKED_RADIUS) continue;
|
|
62
|
-
const minD = threatPoints.length === 0 ? 0 : Math.min(...threatPoints.map(p => dist(s.x, s.y, p.x, p.y)));
|
|
63
|
-
if (minD > bestMin) {
|
|
64
|
-
bestMin = minD;
|
|
65
|
-
best = s;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return best;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* 选「最近的安全躲藏点」:与 nearestSafeTask 同构——威胁旁的点硬排除、去路经过威胁附近的点排除,
|
|
73
|
-
* 余者取测地最近(distance-field 一次扫描;测地不可达按欧氏垫底),带粘性(当前点仍合法就不换)。
|
|
74
|
-
* 全部被威胁封死时退回「离威胁最远」的点。
|
|
75
|
-
*/
|
|
76
|
-
export function nearestSafeHideSpot(
|
|
77
|
-
from: Position,
|
|
78
|
-
threatPoints: Position[] = [],
|
|
79
|
-
opts: NearestSafeHideOptions = {},
|
|
80
|
-
): HideSpot | null {
|
|
81
|
-
const excludeR = opts.threatExcludeRadius ?? HIDE_THREAT_EXCLUDE_RANGE;
|
|
82
|
-
const blocked = opts.blockedTarget ?? null;
|
|
83
|
-
|
|
84
|
-
const candidates = HIDE_SPOTS.filter(s =>
|
|
85
|
-
(!blocked || dist(s.x, s.y, blocked.x, blocked.y) > BLOCKED_RADIUS)
|
|
86
|
-
&& !threatPoints.some(p => dist(s.x, s.y, p.x, p.y) <= excludeR));
|
|
87
|
-
|
|
88
|
-
if (candidates.length === 0) return farthestFromThreats(threatPoints, blocked);
|
|
89
|
-
|
|
90
|
-
// 无威胁时粘性可零成本短路(不必为路径检查扫距离场)。
|
|
91
|
-
if (opts.stickyTo && threatPoints.length === 0) {
|
|
92
|
-
const sticky = candidates.find(c => sameSpot(c, opts.stickyTo!));
|
|
93
|
-
if (sticky) return sticky;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const routes = assessRoutes(
|
|
97
|
-
from,
|
|
98
|
-
candidates.map(c => ({ x: c.x, y: c.y })),
|
|
99
|
-
threatPoints,
|
|
100
|
-
opts.pathThreatRadius ?? HIDE_PATH_THREAT_RADIUS,
|
|
101
|
-
);
|
|
102
|
-
const indexed = candidates.map((c, i) => ({ c, i }));
|
|
103
|
-
const viable = routes == null ? indexed : indexed.filter(({ i }) => !routes[i].nearThreat);
|
|
104
|
-
const pool = viable.length > 0 ? viable : indexed;
|
|
105
|
-
|
|
106
|
-
if (opts.stickyTo) {
|
|
107
|
-
const sticky = pool.find(({ c }) => sameSpot(c, opts.stickyTo!));
|
|
108
|
-
if (sticky) return sticky.c;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
let best = pool[0].c;
|
|
112
|
-
let bestScore = Infinity;
|
|
113
|
-
for (const { c, i } of pool) {
|
|
114
|
-
const euclid = dist(from.x, from.y, c.x, c.y);
|
|
115
|
-
const geo = routes?.[i].distancePx;
|
|
116
|
-
const score = geo == null ? euclid : geo !== Infinity ? geo : euclid + 1e6;
|
|
117
|
-
if (score < bestScore) {
|
|
118
|
-
bestScore = score;
|
|
119
|
-
best = c;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return best;
|
|
123
|
-
}
|
|
1
|
+
import type { Position } from '../sdk/types.js';
|
|
2
|
+
import { dist } from './game-utils.js';
|
|
3
|
+
import { assessRoutes } from './pathfind/escape-planner.js';
|
|
4
|
+
|
|
5
|
+
export interface HideSpot {
|
|
6
|
+
/** 世界像素坐标,与 GameState / 可行走网格同坐标系。 */
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
/** 所在房间,仅供日志/调试。 */
|
|
10
|
+
room: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 躲藏点:离线从服务端烘焙地图(config/clawclaw/clawclaw.tmj.baked.npz)算出的「藏身角落」。筛选条件:
|
|
15
|
+
* - 所在房间有 ≥2 个出口(非死胡同,被追能逃);**走廊**(做任务的必经路线)一律排除;
|
|
16
|
+
* - 出口 ≥4 的枢纽房间排除(穿行人流太大,藏不住);
|
|
17
|
+
* - 取房间内「离各出口最深、且远离任务点/出生点」的可走格(缩在内侧角落,避开穿行线与刷新点)。
|
|
18
|
+
*
|
|
19
|
+
* 重新生成:scripts/find-hide-spots.py(用后端 venv 解释器跑,读 baked.npz,打印可粘贴的本数组)。
|
|
20
|
+
* 这是离线烘焙数据,和 pathfind 的可行走网格一样:运行时按需 snap 到最近可走格即可。
|
|
21
|
+
*/
|
|
22
|
+
export const HIDE_SPOTS: readonly HideSpot[] = [
|
|
23
|
+
{ x: 2882, y: 1362, room: '导航仓' },
|
|
24
|
+
{ x: 2386, y: 1138, room: '健身房' },
|
|
25
|
+
{ x: 1922, y: 1234, room: '酒吧' },
|
|
26
|
+
{ x: 2418, y: 2130, room: '休闲会所' },
|
|
27
|
+
{ x: 1538, y: 1826, room: '监控室' },
|
|
28
|
+
{ x: 514, y: 2402, room: '动力监控室' },
|
|
29
|
+
{ x: 1538, y: 2514, room: '自助餐厅' },
|
|
30
|
+
{ x: 1314, y: 2674, room: '制氧舱' },
|
|
31
|
+
{ x: 2514, y: 2882, room: '中央厨房' },
|
|
32
|
+
{ x: 2882, y: 2994, room: '冷库' },
|
|
33
|
+
{ x: 3858, y: 2434, room: '情报室' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
/** 躲藏点离任一威胁多近就硬排除(与 nearestSafeTask 的端点排除同量级)。 */
|
|
37
|
+
export const HIDE_THREAT_EXCLUDE_RANGE = 500;
|
|
38
|
+
/** 去躲藏点的测地路径经过威胁这个距离内也排除(避免走向把守者)。 */
|
|
39
|
+
export const HIDE_PATH_THREAT_RADIUS = 350;
|
|
40
|
+
|
|
41
|
+
export interface NearestSafeHideOptions {
|
|
42
|
+
threatExcludeRadius?: number;
|
|
43
|
+
pathThreatRadius?: number;
|
|
44
|
+
/** 当前已选躲藏点:仍是合法候选就保持不变,杜绝等距摇摆。 */
|
|
45
|
+
stickyTo?: HideSpot | null;
|
|
46
|
+
/** 被标记为不可达的移动目标:在其附近的点跳过。 */
|
|
47
|
+
blockedTarget?: Position | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const BLOCKED_RADIUS = 12;
|
|
51
|
+
|
|
52
|
+
function sameSpot(a: HideSpot, b: HideSpot): boolean {
|
|
53
|
+
return a.x === b.x && a.y === b.y;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** 所有点都被威胁封死时的兜底:离威胁最远的点(仍好过原地等死)。 */
|
|
57
|
+
function farthestFromThreats(threatPoints: Position[], blocked?: Position | null): HideSpot | null {
|
|
58
|
+
let best: HideSpot | null = null;
|
|
59
|
+
let bestMin = -Infinity;
|
|
60
|
+
for (const s of HIDE_SPOTS) {
|
|
61
|
+
if (blocked && dist(s.x, s.y, blocked.x, blocked.y) <= BLOCKED_RADIUS) continue;
|
|
62
|
+
const minD = threatPoints.length === 0 ? 0 : Math.min(...threatPoints.map(p => dist(s.x, s.y, p.x, p.y)));
|
|
63
|
+
if (minD > bestMin) {
|
|
64
|
+
bestMin = minD;
|
|
65
|
+
best = s;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return best;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 选「最近的安全躲藏点」:与 nearestSafeTask 同构——威胁旁的点硬排除、去路经过威胁附近的点排除,
|
|
73
|
+
* 余者取测地最近(distance-field 一次扫描;测地不可达按欧氏垫底),带粘性(当前点仍合法就不换)。
|
|
74
|
+
* 全部被威胁封死时退回「离威胁最远」的点。
|
|
75
|
+
*/
|
|
76
|
+
export function nearestSafeHideSpot(
|
|
77
|
+
from: Position,
|
|
78
|
+
threatPoints: Position[] = [],
|
|
79
|
+
opts: NearestSafeHideOptions = {},
|
|
80
|
+
): HideSpot | null {
|
|
81
|
+
const excludeR = opts.threatExcludeRadius ?? HIDE_THREAT_EXCLUDE_RANGE;
|
|
82
|
+
const blocked = opts.blockedTarget ?? null;
|
|
83
|
+
|
|
84
|
+
const candidates = HIDE_SPOTS.filter(s =>
|
|
85
|
+
(!blocked || dist(s.x, s.y, blocked.x, blocked.y) > BLOCKED_RADIUS)
|
|
86
|
+
&& !threatPoints.some(p => dist(s.x, s.y, p.x, p.y) <= excludeR));
|
|
87
|
+
|
|
88
|
+
if (candidates.length === 0) return farthestFromThreats(threatPoints, blocked);
|
|
89
|
+
|
|
90
|
+
// 无威胁时粘性可零成本短路(不必为路径检查扫距离场)。
|
|
91
|
+
if (opts.stickyTo && threatPoints.length === 0) {
|
|
92
|
+
const sticky = candidates.find(c => sameSpot(c, opts.stickyTo!));
|
|
93
|
+
if (sticky) return sticky;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const routes = assessRoutes(
|
|
97
|
+
from,
|
|
98
|
+
candidates.map(c => ({ x: c.x, y: c.y })),
|
|
99
|
+
threatPoints,
|
|
100
|
+
opts.pathThreatRadius ?? HIDE_PATH_THREAT_RADIUS,
|
|
101
|
+
);
|
|
102
|
+
const indexed = candidates.map((c, i) => ({ c, i }));
|
|
103
|
+
const viable = routes == null ? indexed : indexed.filter(({ i }) => !routes[i].nearThreat);
|
|
104
|
+
const pool = viable.length > 0 ? viable : indexed;
|
|
105
|
+
|
|
106
|
+
if (opts.stickyTo) {
|
|
107
|
+
const sticky = pool.find(({ c }) => sameSpot(c, opts.stickyTo!));
|
|
108
|
+
if (sticky) return sticky.c;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let best = pool[0].c;
|
|
112
|
+
let bestScore = Infinity;
|
|
113
|
+
for (const { c, i } of pool) {
|
|
114
|
+
const euclid = dist(from.x, from.y, c.x, c.y);
|
|
115
|
+
const geo = routes?.[i].distancePx;
|
|
116
|
+
const score = geo == null ? euclid : geo !== Infinity ? geo : euclid + 1e6;
|
|
117
|
+
if (score < bestScore) {
|
|
118
|
+
bestScore = score;
|
|
119
|
+
best = c;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return best;
|
|
123
|
+
}
|
package/src/strategies/hide.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import type { StrategyEntry } from './types.js';
|
|
2
|
-
import { GoalRootStrategy } from './goals/goal-root-strategy.js';
|
|
3
|
-
import { HideTop } from './goals/hide-top.js';
|
|
4
|
-
|
|
5
|
-
/** 解析参数:kill/fight → 死角自保开;nokill/flee/pacifist → 关;缺省(null)= 按角色默认。 */
|
|
6
|
-
export function parseHideKillArg(args?: string[]): boolean | null {
|
|
7
|
-
for (const raw of args ?? []) {
|
|
8
|
-
const a = raw.trim().toLowerCase();
|
|
9
|
-
if (a === 'kill' || a === 'fight' || a === 'kill=on') return true;
|
|
10
|
-
if (a === 'nokill' || a === 'flee' || a === 'pacifist' || a === 'kill=off') return false;
|
|
11
|
-
}
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const strategy: StrategyEntry = {
|
|
16
|
-
id: 'hide',
|
|
17
|
-
description:
|
|
18
|
-
'躲藏策略:在离线从地图算好的若干「非走廊、≥2 出口可逃、远离任务点/出生点」的藏身角落里,选测地最近且不挨威胁的一个潜伏不动;视野里一出现非队友,就像 shrimp-memory 那样保持距离甩开(KeepAway),并边逃边重算排除该威胁的新藏点,逃完落到新点继续潜伏。蟹不躲队友。可选参数 kill / nokill 控制「被单个对手逼进出刀距离且无路可逃时是否出刀自保」——缺省按角色:蟹/章鱼默认开,武士虾/枪虾(及无刀角色)默认关。',
|
|
19
|
-
create(args?: string[]) {
|
|
20
|
-
const killWhenCornered = parseHideKillArg(args);
|
|
21
|
-
return new GoalRootStrategy('hide', () => new HideTop(killWhenCornered), { resetOnMeetingResume: false });
|
|
22
|
-
},
|
|
23
|
-
};
|
|
1
|
+
import type { StrategyEntry } from './types.js';
|
|
2
|
+
import { GoalRootStrategy } from './goals/goal-root-strategy.js';
|
|
3
|
+
import { HideTop } from './goals/hide-top.js';
|
|
4
|
+
|
|
5
|
+
/** 解析参数:kill/fight → 死角自保开;nokill/flee/pacifist → 关;缺省(null)= 按角色默认。 */
|
|
6
|
+
export function parseHideKillArg(args?: string[]): boolean | null {
|
|
7
|
+
for (const raw of args ?? []) {
|
|
8
|
+
const a = raw.trim().toLowerCase();
|
|
9
|
+
if (a === 'kill' || a === 'fight' || a === 'kill=on') return true;
|
|
10
|
+
if (a === 'nokill' || a === 'flee' || a === 'pacifist' || a === 'kill=off') return false;
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const strategy: StrategyEntry = {
|
|
16
|
+
id: 'hide',
|
|
17
|
+
description:
|
|
18
|
+
'躲藏策略:在离线从地图算好的若干「非走廊、≥2 出口可逃、远离任务点/出生点」的藏身角落里,选测地最近且不挨威胁的一个潜伏不动;视野里一出现非队友,就像 shrimp-memory 那样保持距离甩开(KeepAway),并边逃边重算排除该威胁的新藏点,逃完落到新点继续潜伏。蟹不躲队友。可选参数 kill / nokill 控制「被单个对手逼进出刀距离且无路可逃时是否出刀自保」——缺省按角色:蟹/章鱼默认开,武士虾/枪虾(及无刀角色)默认关。',
|
|
19
|
+
create(args?: string[]) {
|
|
20
|
+
const killWhenCornered = parseHideKillArg(args);
|
|
21
|
+
return new GoalRootStrategy('hide', () => new HideTop(killWhenCornered), { resetOnMeetingResume: false });
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -1,12 +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
|
-
};
|
|
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
|
+
};
|
|
@@ -1,20 +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` 是纯身份事实,不直接控制策略。
|
|
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` 是纯身份事实,不直接控制策略。
|
|
@@ -1,13 +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
|
-
};
|
|
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
|
+
};
|
|
@@ -1,18 +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
|
-
};
|
|
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
|
+
};
|