cortico 0.1.0
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/LICENSE +21 -0
- package/README.md +47 -0
- package/package.json +43 -0
- package/src/boot.ts +46 -0
- package/src/bot.ts +1342 -0
- package/src/config-file.ts +41 -0
- package/src/core/README.md +92 -0
- package/src/core/billing.ts +26 -0
- package/src/core/blobs.ts +106 -0
- package/src/core/bus.ts +311 -0
- package/src/core/config-schema.ts +201 -0
- package/src/core/config.ts +262 -0
- package/src/core/core.ts +699 -0
- package/src/core/cost.ts +244 -0
- package/src/core/event-store.ts +304 -0
- package/src/core/fork.ts +113 -0
- package/src/core/generation.ts +118 -0
- package/src/core/instance-lock.ts +215 -0
- package/src/core/ipc-logger.ts +105 -0
- package/src/core/language.ts +62 -0
- package/src/core/log-context.ts +38 -0
- package/src/core/loop.ts +1858 -0
- package/src/core/markers.ts +48 -0
- package/src/core/prefix.ts +121 -0
- package/src/core/run.ts +95 -0
- package/src/core/secrets.ts +18 -0
- package/src/core/session.ts +56 -0
- package/src/core/sessions.ts +261 -0
- package/src/core/state.ts +104 -0
- package/src/core/template.ts +72 -0
- package/src/core/timers.ts +129 -0
- package/src/core/tool-log.ts +135 -0
- package/src/core/transcript.ts +92 -0
- package/src/core/truncate.ts +121 -0
- package/src/core/types.ts +1140 -0
- package/src/core/usage-log.ts +79 -0
- package/src/core/util.ts +386 -0
- package/src/deploy-listing.ts +160 -0
- package/src/deploy.ts +162 -0
- package/src/extensions/README.md +72 -0
- package/src/extensions/dry-mount.ts +390 -0
- package/src/extensions/manifest.ts +153 -0
- package/src/extensions/runtime.ts +42 -0
- package/src/extensions.ts +519 -0
- package/src/launcher.ts +285 -0
- package/src/paths.ts +155 -0
- package/src/protocol/open-responses/LICENSE +201 -0
- package/src/protocol/open-responses/README.md +14 -0
- package/src/protocol/open-responses/context-helpers.ts +27 -0
- package/src/protocol/open-responses/context-log.ts +59 -0
- package/src/protocol/open-responses/context.ts +89 -0
- package/src/protocol/open-responses/generated.ts +115 -0
- package/src/protocol/open-responses/index.ts +33 -0
- package/src/protocol/open-responses/openapi.json +4230 -0
- package/src/protocol/open-responses/stream.ts +146 -0
- package/src/protocol/open-responses/tokens.ts +31 -0
- package/src/providers/README.md +86 -0
- package/src/providers/base.ts +111 -0
- package/src/providers/configuration.ts +97 -0
- package/src/providers/console/config.ts +21 -0
- package/src/providers/console/settings.ts +443 -0
- package/src/providers/console/strings.ts +74 -0
- package/src/providers/console/types.ts +11 -0
- package/src/providers/llamacpp/archive.ts +148 -0
- package/src/providers/llamacpp/catalog.ts +247 -0
- package/src/providers/llamacpp/config.ts +36 -0
- package/src/providers/llamacpp/console/client.ts +6 -0
- package/src/providers/llamacpp/console/models-panel.ts +120 -0
- package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
- package/src/providers/llamacpp/console/server.ts +153 -0
- package/src/providers/llamacpp/index.ts +95 -0
- package/src/providers/llamacpp/native.ts +51 -0
- package/src/providers/llamacpp/options.ts +117 -0
- package/src/providers/llamacpp/runtime-store.ts +139 -0
- package/src/providers/llamacpp/runtime.ts +214 -0
- package/src/providers/llamacpp/server.ts +286 -0
- package/src/providers/llamacpp/strings.ts +230 -0
- package/src/providers/openai-responses-compat/config.ts +20 -0
- package/src/providers/openai-responses-compat/index.ts +89 -0
- package/src/providers/openai-responses-compat/native.ts +127 -0
- package/src/providers/openai-responses-compat/strings.ts +19 -0
- package/src/providers/pricebook.ts +75 -0
- package/src/providers/registry.ts +137 -0
- package/src/providers/strings.ts +71 -0
- package/src/providers/transport/chat.ts +156 -0
- package/src/providers/transport/errors.ts +48 -0
- package/src/providers/transport/history.ts +68 -0
- package/src/providers/transport/native-input.ts +83 -0
- package/src/providers/transport/native-types.ts +13 -0
- package/src/providers/transport/response-assembly.ts +191 -0
- package/src/providers/transport/response-http.ts +196 -0
- package/src/providers/transport/response-meters.ts +27 -0
- package/src/providers/transport/responses-input.ts +56 -0
- package/src/web/README.md +84 -0
- package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
- package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
- package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
- package/src/web/client/console-pages/builtins.ts +10 -0
- package/src/web/client/console-pages/context.ts +135 -0
- package/src/web/client/console-pages/host.ts +633 -0
- package/src/web/client/console-pages/loader.ts +132 -0
- package/src/web/client/console-pages/strings.ts +90 -0
- package/src/web/client/console-pages/tools/strings.ts +49 -0
- package/src/web/client/console-pages/tools/view.ts +215 -0
- package/src/web/client/core/api.ts +200 -0
- package/src/web/client/core/language.ts +56 -0
- package/src/web/client/core/lifecycle.ts +132 -0
- package/src/web/client/core/router.ts +199 -0
- package/src/web/client/core/stream.ts +157 -0
- package/src/web/client/core/websocket.ts +82 -0
- package/src/web/client/features/appearance/index.ts +333 -0
- package/src/web/client/features/appearance/strings.ts +87 -0
- package/src/web/client/features/config/index.ts +11 -0
- package/src/web/client/features/config/strings.ts +41 -0
- package/src/web/client/features/config/view.ts +428 -0
- package/src/web/client/features/core/events.ts +197 -0
- package/src/web/client/features/core/index.ts +254 -0
- package/src/web/client/features/core/run.ts +56 -0
- package/src/web/client/features/core/runlog.ts +214 -0
- package/src/web/client/features/core/sessions.ts +53 -0
- package/src/web/client/features/core/strings.ts +157 -0
- package/src/web/client/features/extensions/index.ts +422 -0
- package/src/web/client/features/extensions/strings.ts +164 -0
- package/src/web/client/features/feature.ts +93 -0
- package/src/web/client/features/live/context.ts +280 -0
- package/src/web/client/features/live/fork.ts +156 -0
- package/src/web/client/features/live/index.ts +392 -0
- package/src/web/client/features/live/onboarding.ts +113 -0
- package/src/web/client/features/live/protocol.ts +122 -0
- package/src/web/client/features/live/sessions.ts +71 -0
- package/src/web/client/features/live/status.ts +68 -0
- package/src/web/client/features/live/strings.ts +252 -0
- package/src/web/client/features/live/timeline.ts +590 -0
- package/src/web/client/features/prompts/editor.ts +292 -0
- package/src/web/client/features/prompts/index.ts +346 -0
- package/src/web/client/features/prompts/strings.ts +136 -0
- package/src/web/client/features/prompts/view.ts +256 -0
- package/src/web/client/features/providers/index.ts +100 -0
- package/src/web/client/features/providers/strings.ts +23 -0
- package/src/web/client/features/settings/general.ts +28 -0
- package/src/web/client/features/settings/index.ts +82 -0
- package/src/web/client/features/settings/strings.ts +33 -0
- package/src/web/client/features/storage/index.ts +1 -0
- package/src/web/client/features/storage/strings.ts +45 -0
- package/src/web/client/features/storage/view.ts +154 -0
- package/src/web/client/features/usage/chart.ts +486 -0
- package/src/web/client/features/usage/index.ts +407 -0
- package/src/web/client/features/usage/labels.ts +46 -0
- package/src/web/client/features/usage/range.ts +36 -0
- package/src/web/client/features/usage/state.ts +37 -0
- package/src/web/client/features/usage/strings.ts +191 -0
- package/src/web/client/features/usage/tooltip.ts +86 -0
- package/src/web/client/features/usage/types.ts +96 -0
- package/src/web/client/features/worlds/index.ts +424 -0
- package/src/web/client/features/worlds/strings.ts +121 -0
- package/src/web/client/main.ts +273 -0
- package/src/web/client/shell/avatar.ts +233 -0
- package/src/web/client/shell/index.ts +549 -0
- package/src/web/client/shell/strings.ts +89 -0
- package/src/web/client/theme/handoff.ts +43 -0
- package/src/web/client/theme/palette.ts +109 -0
- package/src/web/client/theme/registry.ts +130 -0
- package/src/web/client/theme/storage.ts +72 -0
- package/src/web/client/theme/strings.ts +123 -0
- package/src/web/client/theme/studio.ts +366 -0
- package/src/web/client/ui/actions.ts +109 -0
- package/src/web/client/ui/data.ts +185 -0
- package/src/web/client/ui/dom.ts +23 -0
- package/src/web/client/ui/fields.ts +191 -0
- package/src/web/client/ui/format.ts +75 -0
- package/src/web/client/ui/icons.ts +183 -0
- package/src/web/client/ui/images.ts +91 -0
- package/src/web/client/ui/index.ts +89 -0
- package/src/web/client/ui/lamp.ts +125 -0
- package/src/web/client/ui/log.ts +98 -0
- package/src/web/client/ui/overlay.ts +184 -0
- package/src/web/client/ui/page.ts +8 -0
- package/src/web/client/ui/prompt-input.tsx +311 -0
- package/src/web/client/ui/sheet.ts +131 -0
- package/src/web/client/ui/strings.ts +71 -0
- package/src/web/console-pages.ts +367 -0
- package/src/web/files.ts +107 -0
- package/src/web/path-picker.ts +296 -0
- package/src/web/public/index.html +17 -0
- package/src/web/public/styles.css +1715 -0
- package/src/web/server.ts +1883 -0
- package/src/web/shared/client-panel.ts +603 -0
- package/src/web/shared/console-protocol.ts +560 -0
- package/src/web/shared/css.d.ts +1 -0
- package/src/web/shared/path-picker.ts +23 -0
- package/src/web/shared/theme.ts +256 -0
- package/src/web/theme-store.ts +37 -0
- package/src/world.ts +449 -0
- package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
- package/src/worlds/bilibili/README.md +176 -0
- package/src/worlds/bilibili/audience-admission.ts +995 -0
- package/src/worlds/bilibili/client.ts +528 -0
- package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
- package/src/worlds/bilibili/config.ts +185 -0
- package/src/worlds/bilibili/console/client.ts +202 -0
- package/src/worlds/bilibili/definition.ts +35 -0
- package/src/worlds/bilibili/gift-frame.ts +131 -0
- package/src/worlds/bilibili/normalize.ts +452 -0
- package/src/worlds/bilibili/overlay/announcement.ts +76 -0
- package/src/worlds/bilibili/overlay/assets.ts +82 -0
- package/src/worlds/bilibili/overlay/model.ts +494 -0
- package/src/worlds/bilibili/overlay/project.ts +202 -0
- package/src/worlds/bilibili/overlay/server.ts +286 -0
- package/src/worlds/bilibili/overlay/types.ts +217 -0
- package/src/worlds/bilibili/overlay/web/app.js +690 -0
- package/src/worlds/bilibili/overlay/web/editor.css +297 -0
- package/src/worlds/bilibili/overlay/web/editor.html +102 -0
- package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
- package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
- package/src/worlds/bilibili/overlay/web/styles.css +258 -0
- package/src/worlds/bilibili/protobuf.ts +123 -0
- package/src/worlds/bilibili/wire.ts +94 -0
- package/src/worlds/bilibili/world.ts +1389 -0
- package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
- package/src/worlds/console-fixture/console/client.ts +72 -0
- package/src/worlds/console-fixture/world.ts +73 -0
- package/src/worlds/index.ts +15 -0
- package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
- package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
- package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
- package/src/worlds/minecraft/README.md +917 -0
- package/src/worlds/minecraft/blueprint-plan.ts +915 -0
- package/src/worlds/minecraft/blueprint-registry.ts +566 -0
- package/src/worlds/minecraft/blueprint-repair.ts +236 -0
- package/src/worlds/minecraft/blueprint-resource.ts +272 -0
- package/src/worlds/minecraft/blueprint.ts +492 -0
- package/src/worlds/minecraft/body-lease.ts +340 -0
- package/src/worlds/minecraft/bridge.ts +749 -0
- package/src/worlds/minecraft/check.ts +651 -0
- package/src/worlds/minecraft/chests.ts +292 -0
- package/src/worlds/minecraft/client-launch.ts +272 -0
- package/src/worlds/minecraft/client-options.ts +85 -0
- package/src/worlds/minecraft/client-skins.ts +164 -0
- package/src/worlds/minecraft/client-window.ps1 +91 -0
- package/src/worlds/minecraft/client.ts +423 -0
- package/src/worlds/minecraft/combat-context.ts +109 -0
- package/src/worlds/minecraft/combat.ts +1552 -0
- package/src/worlds/minecraft/config.ts +499 -0
- package/src/worlds/minecraft/console/access.ts +218 -0
- package/src/worlds/minecraft/console/client.ts +244 -0
- package/src/worlds/minecraft/console/log.ts +110 -0
- package/src/worlds/minecraft/console/mount.ts +420 -0
- package/src/worlds/minecraft/console/skin.ts +257 -0
- package/src/worlds/minecraft/console/style.css +154 -0
- package/src/worlds/minecraft/console/world.ts +449 -0
- package/src/worlds/minecraft/deaths.ts +135 -0
- package/src/worlds/minecraft/definition.ts +18 -0
- package/src/worlds/minecraft/engine-child.ts +274 -0
- package/src/worlds/minecraft/engine-ipc.ts +98 -0
- package/src/worlds/minecraft/entity-facts.ts +132 -0
- package/src/worlds/minecraft/escape.ts +407 -0
- package/src/worlds/minecraft/executor.ts +14494 -0
- package/src/worlds/minecraft/explored.ts +156 -0
- package/src/worlds/minecraft/geometry.ts +205 -0
- package/src/worlds/minecraft/goal-plan.ts +647 -0
- package/src/worlds/minecraft/item-break.ts +126 -0
- package/src/worlds/minecraft/item-facts.ts +146 -0
- package/src/worlds/minecraft/item-pick.ts +67 -0
- package/src/worlds/minecraft/level-dat.ts +176 -0
- package/src/worlds/minecraft/log.ts +132 -0
- package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
- package/src/worlds/minecraft/names.ts +377 -0
- package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
- package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
- package/src/worlds/minecraft/piglin.ts +49 -0
- package/src/worlds/minecraft/policy.ts +547 -0
- package/src/worlds/minecraft/precheck.ts +655 -0
- package/src/worlds/minecraft/proxy.ts +445 -0
- package/src/worlds/minecraft/ranged.ts +635 -0
- package/src/worlds/minecraft/readouts.ts +102 -0
- package/src/worlds/minecraft/round.ts +26 -0
- package/src/worlds/minecraft/search-observation.ts +92 -0
- package/src/worlds/minecraft/server-config.ts +467 -0
- package/src/worlds/minecraft/server.ts +658 -0
- package/src/worlds/minecraft/show.ts +84 -0
- package/src/worlds/minecraft/skills.ts +1763 -0
- package/src/worlds/minecraft/terrain.ts +1735 -0
- package/src/worlds/minecraft/window.ts +96 -0
- package/src/worlds/minecraft/works.ts +179 -0
- package/src/worlds/minecraft/world.ts +6438 -0
- package/src/worlds/qq/ENV_PROMPT.md +5 -0
- package/src/worlds/qq/config.ts +108 -0
- package/src/worlds/qq/console/client.ts +126 -0
- package/src/worlds/qq/console/events.ts +155 -0
- package/src/worlds/qq/console/gate.ts +232 -0
- package/src/worlds/qq/console/roster.ts +203 -0
- package/src/worlds/qq/console/style.css +65 -0
- package/src/worlds/qq/conversation.ts +28 -0
- package/src/worlds/qq/definition.ts +62 -0
- package/src/worlds/qq/driver.ts +375 -0
- package/src/worlds/qq/history-tools.ts +209 -0
- package/src/worlds/qq/normalize.ts +323 -0
- package/src/worlds/qq/qface-map.ts +282 -0
- package/src/worlds/qq/vision-prompt.ts +27 -0
- package/src/worlds/qq/vision.ts +642 -0
- package/src/worlds/qq/vlm.ts +142 -0
- package/src/worlds/qq/world.ts +1503 -0
- package/src/worlds/terminal/ENV_PROMPT.md +11 -0
- package/src/worlds/terminal/config.ts +9 -0
- package/src/worlds/terminal/definition.ts +14 -0
- package/src/worlds/terminal/world.ts +700 -0
- package/src/worlds/websearch/ENV_PROMPT.md +1 -0
- package/src/worlds/websearch/brave-client.ts +261 -0
- package/src/worlds/websearch/config.ts +75 -0
- package/src/worlds/websearch/definition.ts +20 -0
- package/src/worlds/websearch/world.ts +171 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** 普通猪灵的条件敌对事实。猪灵蛮兵不走这里,始终按敌对处理。 */
|
|
2
|
+
|
|
3
|
+
interface NamedStack { name?: string }
|
|
4
|
+
|
|
5
|
+
interface PiglinLike {
|
|
6
|
+
id?: number;
|
|
7
|
+
name?: string;
|
|
8
|
+
metadata?: unknown[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface PiglinBotLike {
|
|
12
|
+
inventory?: { slots?: Array<NamedStack | null | undefined> };
|
|
13
|
+
registry?: {
|
|
14
|
+
entitiesByName?: Record<string, { metadataKeys?: string[] } | undefined>;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const GOLD_ARMOR = new Set([
|
|
19
|
+
'golden_helmet', 'golden_chestplate', 'golden_leggings', 'golden_boots',
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
/** 背包里的金甲不算;只读玩家实际穿戴的四个装备槽。 */
|
|
23
|
+
export function wearsGoldArmor(bot: PiglinBotLike): boolean {
|
|
24
|
+
const slots = bot.inventory?.slots ?? [];
|
|
25
|
+
return [5, 6, 7, 8].some((slot) => GOLD_ARMOR.has(slots[slot]?.name ?? ''));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** metadata 下标随协议版本走 registry,不把 1.20.6 的数值写死。 */
|
|
29
|
+
export function isBabyPiglin(bot: PiglinBotLike, entity: PiglinLike): boolean {
|
|
30
|
+
if (entity.name !== 'piglin') return false;
|
|
31
|
+
const keys = bot.registry?.entitiesByName?.piglin?.metadataKeys ?? [];
|
|
32
|
+
const at = keys.indexOf('baby');
|
|
33
|
+
return at >= 0 && entity.metadata?.[at] === true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 是否应把普通猪灵当作当前战斗目标。实际打过玩家是硬事实,优先于金甲;幼体
|
|
38
|
+
* 不会主动攻击,未被实际来源指认时不进入目标池。
|
|
39
|
+
*/
|
|
40
|
+
export function piglinIsHostile(
|
|
41
|
+
bot: PiglinBotLike,
|
|
42
|
+
entity: PiglinLike,
|
|
43
|
+
provoked = false,
|
|
44
|
+
): boolean {
|
|
45
|
+
if (entity.name !== 'piglin') return false;
|
|
46
|
+
if (provoked) return true;
|
|
47
|
+
if (isBabyPiglin(bot, entity)) return false;
|
|
48
|
+
return !wearsGoldArmor(bot);
|
|
49
|
+
}
|
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mc_policy 的契约面:六格常驻规矩一处声明。
|
|
3
|
+
*
|
|
4
|
+
* `POLICY_FIELDS` 声明一次字段(schema 片段 / 工具说明的一行 / 入参校验),出口全部由
|
|
5
|
+
* 这一份生成:`POLICY_SCHEMA`、`POLICY_DOC`、`parsePolicy`。回读渲染另在 `renderPolicy`,
|
|
6
|
+
* 因为它按语义分句(照明名单与照明场合合成一句),不是逐格拼。
|
|
7
|
+
*
|
|
8
|
+
* 规矩是设置不是任务:不进队列、不占任务号、同步回执。五格落盘跨重启活着,所以
|
|
9
|
+
* 「上一场设过 reserve」必须有出口说出来 —— 回执六格全念、非默认项进环境提示词、
|
|
10
|
+
* 连入播报点明是上一场留下的。`fight` 是例外:重启回 `DEFAULT_FIGHT`(回弹本身也要
|
|
11
|
+
* 播报),理由见 `loadPolicyReport`。
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { dirname } from 'node:path';
|
|
15
|
+
import { zhName } from './names.ts';
|
|
16
|
+
|
|
17
|
+
/** 赶路遇坎挖还是垫;接寻路器的挖/垫代价系数 */
|
|
18
|
+
export type TravelPrefer = 'auto' | 'dig' | 'place';
|
|
19
|
+
|
|
20
|
+
/** 什么场合周身黑下来才补一根 */
|
|
21
|
+
type LightWhen = 'dig' | 'anywhere';
|
|
22
|
+
|
|
23
|
+
/** 怪贴到跟前主动动手的条件。只管主动进场,挨打还手不受它管 */
|
|
24
|
+
export type FightMode = 'auto' | 'armed' | 'off';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `fight` 的默认档:手上有趁手的家伙才主动进场。
|
|
28
|
+
*
|
|
29
|
+
* 空手主动扑怪是必输局而她开局与死后重来都是空背包,`auto` 让这一档成为常态。
|
|
30
|
+
* 不取 `off`:那一档连带把"有剑也不清怪"一起关掉,而挡住的只是空手那一种。
|
|
31
|
+
* 这一格不落盘(见 `loadPolicyReport`),所以默认值就是每次重启后的实际生效值。
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_FIGHT: FightMode = 'armed';
|
|
34
|
+
|
|
35
|
+
const TRAVEL_MODES: readonly TravelPrefer[] = ['auto', 'dig', 'place'];
|
|
36
|
+
const LIGHT_WHENS: readonly LightWhen[] = ['dig', 'anywhere'];
|
|
37
|
+
const FIGHT_MODES: readonly FightMode[] = ['auto', 'armed', 'off'];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 生效中的六格。`scaffold`/`light` 的 `null` = 用默认名单(`PolicyDefaults`),
|
|
41
|
+
* `[]` = 这条关掉;`reserve` 没有默认名单这一层,空数组就是「都不收着」。
|
|
42
|
+
*/
|
|
43
|
+
export interface PolicySettings {
|
|
44
|
+
/** 垫一格用哪些方块,顺序即优先;寻路器搭路用的也是这一份 */
|
|
45
|
+
scaffold: string[] | null;
|
|
46
|
+
/** 周身黑下来插一根用哪些 */
|
|
47
|
+
light: string[] | null;
|
|
48
|
+
lightWhen: LightWhen;
|
|
49
|
+
travel: TravelPrefer;
|
|
50
|
+
/** 收着不主动拿去挖的家伙什;剔完没有别的挖得出掉落时仍然会拿(回执点名) */
|
|
51
|
+
reserve: string[];
|
|
52
|
+
fight: FightMode;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 她没设过名单时用哪一份。**名单只有一份真相**:寻路器读 `cfg.scaffoldBlocks`,
|
|
57
|
+
* 维持条件层从这里取同一份,同时保住「默认 vs 她设的」这个区别(回执要说得出是哪一种)。
|
|
58
|
+
*/
|
|
59
|
+
export interface PolicyDefaults {
|
|
60
|
+
scaffold: string[];
|
|
61
|
+
light: string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function defaultPolicy(): PolicySettings {
|
|
65
|
+
return { scaffold: null, light: null, lightWhen: 'dig', travel: 'auto', reserve: [], fight: DEFAULT_FIGHT };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 没接 World 时(测试台架)的兜底名单;真 World 一律从 `cfg.scaffoldBlocks` 来 */
|
|
69
|
+
export const FALLBACK_DEFAULTS: PolicyDefaults = { scaffold: ['dirt', 'cobblestone'], light: ['torch'] };
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 重生点的物理载体:床(16 色)与重生锚。
|
|
73
|
+
*
|
|
74
|
+
* 它是**资产**不是耗材——寻路器挖路、几何形状顺带覆盖、顺手捡走这三条间接破坏
|
|
75
|
+
* 一律不许碰它;要搬要拆走她显式指名那一格的路(见执行器的重生锚闸)。
|
|
76
|
+
*/
|
|
77
|
+
export function isSpawnAnchorBlock(name: string): boolean {
|
|
78
|
+
return name === 'respawn_anchor' || /(^|_)bed$/.test(name);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 失去支撑会下落的 FallingBlock 方块族,含三种砧形态。
|
|
83
|
+
* 垫脚名单排除这些方块;build 禁止放在自身碰撞箱正上方。
|
|
84
|
+
*/
|
|
85
|
+
export function isGravityBlock(name: string): boolean {
|
|
86
|
+
return name === 'sand' || name === 'red_sand' || name === 'gravel'
|
|
87
|
+
|| name === 'suspicious_sand' || name === 'suspicious_gravel'
|
|
88
|
+
|| name === 'anvil' || name === 'chipped_anvil' || name === 'damaged_anvil'
|
|
89
|
+
|| name === 'dragon_egg' || name === 'pointed_dripstone'
|
|
90
|
+
|| name.endsWith('_concrete_powder');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
type PolicyValue = PolicySettings[keyof PolicySettings];
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 一格的声明。`parse` 认下就给值,不认就给一句 `why` —— 那一格不动,别的照改,
|
|
97
|
+
* 理由进回执的 note(静默吃掉参数是这条链上最贵的一类失败)。
|
|
98
|
+
*/
|
|
99
|
+
interface PolicyField {
|
|
100
|
+
key: keyof PolicySettings;
|
|
101
|
+
/** 参数 schema 里的那一条(含 description) */
|
|
102
|
+
schema: Record<string, unknown>;
|
|
103
|
+
/** 工具说明里的那一行 */
|
|
104
|
+
doc: string;
|
|
105
|
+
parse(v: unknown): { value: PolicyValue } | { why: string };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 一份料单:字符串数组,元素 trim;空数组 = 这条关掉。
|
|
110
|
+
* 名字不校验存在性,空串也照收——包里找不找得到由世界说了算,而把 `[""]` 悄悄
|
|
111
|
+
* 收成 `[]` 就是把「一个没用的名字」翻译成了「关掉这一条」。
|
|
112
|
+
*/
|
|
113
|
+
function listOf(v: unknown): string[] | null {
|
|
114
|
+
if (!Array.isArray(v) || v.some((s) => typeof s !== 'string')) return null;
|
|
115
|
+
return (v as string[]).map((s) => s.trim());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function listField(
|
|
119
|
+
key: 'scaffold' | 'light' | 'reserve',
|
|
120
|
+
description: string,
|
|
121
|
+
doc: string,
|
|
122
|
+
): PolicyField {
|
|
123
|
+
return {
|
|
124
|
+
key,
|
|
125
|
+
schema: { type: 'array', items: { type: 'string' }, description },
|
|
126
|
+
doc,
|
|
127
|
+
parse: (v) => {
|
|
128
|
+
const list = listOf(v);
|
|
129
|
+
return list ? { value: list } : { why: '要一串英文 id(数组),空数组是关掉这一条' };
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function enumField<T extends string>(
|
|
135
|
+
key: 'lightWhen' | 'travel' | 'fight',
|
|
136
|
+
values: readonly T[],
|
|
137
|
+
description: string,
|
|
138
|
+
doc: string,
|
|
139
|
+
): PolicyField {
|
|
140
|
+
return {
|
|
141
|
+
key,
|
|
142
|
+
schema: { type: 'string', enum: [...values], description },
|
|
143
|
+
doc,
|
|
144
|
+
parse: (v) =>
|
|
145
|
+
(typeof v === 'string' && (values as readonly string[]).includes(v.trim())
|
|
146
|
+
? { value: v.trim() as PolicyValue }
|
|
147
|
+
: { why: `只认 ${values.join('/')}` }),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** 注册表本体。顺序即 schema 与说明的出场顺序 */
|
|
152
|
+
const POLICY_FIELDS: readonly PolicyField[] = [
|
|
153
|
+
listField(
|
|
154
|
+
'scaffold',
|
|
155
|
+
'垫一格用哪些方块,顺序即优先;[] = 不垫',
|
|
156
|
+
`scaffold ["dirt","cobblestone"] 脚下没底时垫一格用哪些方块,顺序即优先。
|
|
157
|
+
赶路、逃跑、tunnel 垫桥、塔的每一格、上露天的
|
|
158
|
+
每一格都用这份。[] = 不垫`,
|
|
159
|
+
),
|
|
160
|
+
listField(
|
|
161
|
+
'light',
|
|
162
|
+
'插一根用哪些;[] = 不插',
|
|
163
|
+
'light ["torch"] 周身黑下来插一根用哪些。[] = 不插',
|
|
164
|
+
),
|
|
165
|
+
enumField(
|
|
166
|
+
'lightWhen', LIGHT_WHENS,
|
|
167
|
+
'什么场合黑了才插:dig 只在挖通道/挖空间的时候,anywhere 赶路和采集途中也插',
|
|
168
|
+
`lightWhen "dig" | "anywhere" 什么场合黑了才插:dig 只在挖通道/挖空间的时候
|
|
169
|
+
(默认),anywhere 赶路和采集途中也插`,
|
|
170
|
+
),
|
|
171
|
+
enumField(
|
|
172
|
+
'travel', TRAVEL_MODES,
|
|
173
|
+
'赶路遇到坎是挖开还是垫过去',
|
|
174
|
+
`travel "auto" | "dig" | "place" 赶路遇到坎:auto 按代价自选,dig 能挖就不垫,
|
|
175
|
+
place 能垫就不挖`,
|
|
176
|
+
),
|
|
177
|
+
listField(
|
|
178
|
+
'reserve',
|
|
179
|
+
'收着不主动拿去挖的家伙什;[] = 都不收着',
|
|
180
|
+
`reserve ["iron_pickaxe"] 这几件家伙什收着,挖东西不主动拿。
|
|
181
|
+
收着的那把是唯一挖得出掉落的时候仍然会拿。[] = 都不收着`,
|
|
182
|
+
),
|
|
183
|
+
enumField(
|
|
184
|
+
'fight', FIGHT_MODES,
|
|
185
|
+
'怪贴到跟前主动动手的条件;挨打一律还手,这一条管不了',
|
|
186
|
+
`fight "auto" | "armed" | "off" 怪贴到跟前主动动手的条件:armed 手上有趁手的
|
|
187
|
+
家伙才动手(默认),auto 空手也一律动手,
|
|
188
|
+
off 不主动动手。挨打一律还手,这一条改不了。
|
|
189
|
+
这一格重连之后回 armed,别的五格留着`,
|
|
190
|
+
),
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
/** mc_policy 的参数 schema。六格全可选:空调用 = 只回读 */
|
|
194
|
+
export const POLICY_SCHEMA: Record<string, unknown> = {
|
|
195
|
+
type: 'object',
|
|
196
|
+
properties: Object.fromEntries(POLICY_FIELDS.map((f) => [f.key, f.schema])),
|
|
197
|
+
required: [],
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/** mc_policy 的对外说明(独立于 mc_do 的技能表) */
|
|
201
|
+
export const POLICY_DOC = [
|
|
202
|
+
`干活的常驻规矩,给哪个字段就改哪个,立即生效直到下次改。不排队,不占任务号。
|
|
203
|
+
一个字段都不给 = 只念一遍现在生效的规矩。`,
|
|
204
|
+
...POLICY_FIELDS.map((f) => f.doc),
|
|
205
|
+
`方块/物品一律用英文 id(dirt、cobblestone、torch、iron_pickaxe)。
|
|
206
|
+
料不在包里的那一条就不做,不算受阻;做过的进收工回执报数。
|
|
207
|
+
回执一律念出六条当下生效的规矩,没改过的念默认值。`,
|
|
208
|
+
].join('\n');
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 解析对这次调用做过的改动:某一格没收下(不认的值),或某个键根本不存在。
|
|
212
|
+
* 收下的那些不进这里 —— 回执念的是 `set` 之后读回来的六格,本来就说得出结果。
|
|
213
|
+
*/
|
|
214
|
+
interface PolicyNote {
|
|
215
|
+
field: string;
|
|
216
|
+
given: unknown;
|
|
217
|
+
why: string;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function policyNoteText(n: PolicyNote): string {
|
|
221
|
+
return `${n.field} 写的 ${JSON.stringify(n.given)},${n.why},没收下`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 入参 → 补丁。**给哪个字段就改哪个,没给的一个字都不动**;`{}` 合法,只回读。
|
|
226
|
+
*
|
|
227
|
+
* 名字不校验存在性:她写 `stone_pickaxe` 还是 `stone_pick` 由包里找不找得到说了算
|
|
228
|
+
* (与 `smelt.input` 同一条规矩)。
|
|
229
|
+
*/
|
|
230
|
+
export function parsePolicy(
|
|
231
|
+
args: Record<string, unknown>,
|
|
232
|
+
): { patch: Partial<PolicySettings>; notes: PolicyNote[] } {
|
|
233
|
+
const patch: Partial<PolicySettings> = {};
|
|
234
|
+
const notes: PolicyNote[] = [];
|
|
235
|
+
for (const f of POLICY_FIELDS) {
|
|
236
|
+
const v = args[f.key];
|
|
237
|
+
// null 不算「她想说什么」:模型照 schema 把用不上的字段填成 null 是常态
|
|
238
|
+
if (v === undefined || v === null) continue;
|
|
239
|
+
const r = f.parse(v);
|
|
240
|
+
if ('why' in r) notes.push({ field: f.key, given: v, why: r.why });
|
|
241
|
+
else (patch as Record<string, unknown>)[f.key] = r.value;
|
|
242
|
+
}
|
|
243
|
+
const known = new Set<string>(POLICY_FIELDS.map((f) => f.key));
|
|
244
|
+
for (const [field, given] of Object.entries(args)) {
|
|
245
|
+
if (!known.has(field) && given !== undefined && given !== null) {
|
|
246
|
+
notes.push({ field, given, why: 'mc_policy 没有这一格' });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return { patch, notes };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** 一格的值渲染成她写得出的那个 token:名单给数组字面量,枚举给字面量 */
|
|
253
|
+
function valueToken(v: PolicyValue): string {
|
|
254
|
+
if (v === null) return '默认';
|
|
255
|
+
if (Array.isArray(v)) return v.length === 0 ? '[](关着)' : `[${v.join(',')}]`;
|
|
256
|
+
return String(v);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** 这一次调用真正改动的那几格,渲染成 `fight:auto→armed`;一格没动返回空数组 */
|
|
260
|
+
function policyDiff(before: PolicySettings, after: PolicySettings): string[] {
|
|
261
|
+
const out: string[] = [];
|
|
262
|
+
for (const f of POLICY_FIELDS) {
|
|
263
|
+
const a = before[f.key];
|
|
264
|
+
const b = after[f.key];
|
|
265
|
+
if (JSON.stringify(a) === JSON.stringify(b)) continue;
|
|
266
|
+
out.push(`${f.key}:${valueToken(a)}→${valueToken(b)}`);
|
|
267
|
+
}
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** `fight` 那一格不跨重启:改了它就得当场说一句,别让她带着一条已经弹回去的规矩干活 */
|
|
272
|
+
const FIGHT_VOLATILE_NOTE =
|
|
273
|
+
`fight 这一格不跨重启,重启会弹回 ${DEFAULT_FIGHT}(其余五格落盘,跨重启还在)。`;
|
|
274
|
+
|
|
275
|
+
/** 查询与修改使用不同回执开头;修改报告 diff,并注明未变项和不跨重启项。 */
|
|
276
|
+
export function renderPolicyChange(
|
|
277
|
+
before: PolicySettings,
|
|
278
|
+
after: PolicySettings,
|
|
279
|
+
patch: Partial<PolicySettings>,
|
|
280
|
+
): string {
|
|
281
|
+
const asked = POLICY_FIELDS.map((f) => f.key).filter((k) => patch[k] !== undefined);
|
|
282
|
+
if (asked.length === 0) return '念一遍(这次一格都没给,什么都没改)。';
|
|
283
|
+
const diff = policyDiff(before, after);
|
|
284
|
+
const volatile = asked.includes('fight') ? FIGHT_VOLATILE_NOTE : '';
|
|
285
|
+
if (diff.length === 0) {
|
|
286
|
+
return `一格都没变:${asked.join('、')} 给的值与现在生效的一模一样` +
|
|
287
|
+
`(${asked.map((k) => `${k}:${valueToken(after[k])}`).join(';')})。${volatile}`;
|
|
288
|
+
}
|
|
289
|
+
return `这次改了 ${diff.join(';')};其余 ${POLICY_FIELDS.length - diff.length} 条没动。${volatile}`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function namesText(names: readonly string[]): string {
|
|
293
|
+
return names.map((n) => zhName(n)).join('、');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** 一份料单当下是什么:默认名单 / 关着 / 她指定的那几样 */
|
|
297
|
+
function listText(given: string[] | null, fallback: readonly string[]): string {
|
|
298
|
+
if (given === null) return `用默认名单(${namesText(fallback)})`;
|
|
299
|
+
if (given.length === 0) return '关着';
|
|
300
|
+
return `只用${namesText(given)}`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** 垫脚名单与 renderPolicyEnv 使用相同过滤;被剔除的重力方块须明确报告。 */
|
|
304
|
+
function scaffoldText(given: string[] | null, fallback: readonly string[]): string {
|
|
305
|
+
if (given !== null && given.length === 0) return '关着';
|
|
306
|
+
const list = given ?? fallback;
|
|
307
|
+
const usable = list.filter((n) => !isGravityBlock(n));
|
|
308
|
+
const heavy = list.filter((n) => isGravityBlock(n));
|
|
309
|
+
// 名单里的全被剔了:bridge 会回退到寻路器自己的默认名单,不是"不垫"
|
|
310
|
+
if (usable.length === 0) {
|
|
311
|
+
return `料单里${namesText(heavy)}垫下去会自己掉,一样都没收下,仍走寻路器默认名单`;
|
|
312
|
+
}
|
|
313
|
+
const head = given === null ? `用默认名单(${namesText(usable)})` : `只用${namesText(usable)}`;
|
|
314
|
+
return heavy.length > 0 ? `${head}(${namesText(heavy)}垫下去会自己掉,不收)` : head;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 垫脚名单里被开工蓝图收口的那几样,只述事实。
|
|
319
|
+
*
|
|
320
|
+
* 判定与哪几样收口全在 World 侧算好传进来(渲染保持纯函数)。措辞一律叫「蓝图预留」:
|
|
321
|
+
* 六格里另有一格也叫 `reserve`(收着不主动拿的家伙什),两者不是一回事。
|
|
322
|
+
*/
|
|
323
|
+
function blueprintHeldText(held: readonly string[]): string {
|
|
324
|
+
return `垫脚名单里${namesText(held)}正在蓝图预留中(开工的蓝图还要用),`
|
|
325
|
+
+ '垫一格排到最后,要动它先用 mc_blueprint 的 reserve_override。';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const TRAVEL_ZH: Record<TravelPrefer, string> = {
|
|
329
|
+
auto: '按代价自选挖还是垫',
|
|
330
|
+
dig: '能挖就不垫',
|
|
331
|
+
place: '能垫就不挖',
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const LIGHT_WHEN_ZH: Record<LightWhen, string> = {
|
|
335
|
+
dig: '只在挖通道和挖空间的时候插',
|
|
336
|
+
anywhere: '赶路和采集途中也插',
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const FIGHT_ZH: Record<FightMode, string> = {
|
|
340
|
+
auto: '怪贴到跟前一律动手',
|
|
341
|
+
armed: '手上有趁手的家伙才主动动手,挨打照旧还手',
|
|
342
|
+
off: '不主动动手,挨打照旧还手',
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* 全量回读六项设置,未修改项显示默认值,措辞不使用第二人称。
|
|
347
|
+
* combatOff 时注明 fight 不生效;blueprintHeld 列出被开工蓝图预留收口的物品。
|
|
348
|
+
*/
|
|
349
|
+
export function renderPolicy(
|
|
350
|
+
s: PolicySettings,
|
|
351
|
+
def: PolicyDefaults,
|
|
352
|
+
combatOff = false,
|
|
353
|
+
blueprintHeld: readonly string[] = [],
|
|
354
|
+
): string {
|
|
355
|
+
const parts = [
|
|
356
|
+
`垫一格${scaffoldText(s.scaffold, def.scaffold)}`,
|
|
357
|
+
s.light !== null && s.light.length === 0
|
|
358
|
+
? '插一根关着(照明场合那一格用不上)'
|
|
359
|
+
: `插一根${listText(s.light, def.light)},${LIGHT_WHEN_ZH[s.lightWhen]}`,
|
|
360
|
+
`赶路遇坎${TRAVEL_ZH[s.travel]}`,
|
|
361
|
+
s.reserve.length === 0 ? '没有收着不用的家伙什' : `${namesText(s.reserve)}收着不主动拿`,
|
|
362
|
+
FIGHT_ZH[s.fight],
|
|
363
|
+
];
|
|
364
|
+
if (combatOff) parts.push('战斗总开关在控制台关着,主动动手这一格现在不起作用');
|
|
365
|
+
// 蓝图预留是当下的现场状况,不是第七格规矩:另起一句,不混进六格那一串
|
|
366
|
+
const held = blueprintHeld.length > 0 ? blueprintHeldText(blueprintHeld) : '';
|
|
367
|
+
return `现在生效的规矩:${parts.join(';')}。${held}`;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* 环境提示词那一行:只念与默认不同的几条,全默认返回空串(一个字都不加)。
|
|
372
|
+
*
|
|
373
|
+
* 不需要 `PolicyDefaults`:默认名单那一档正是"与默认相同",本来就不进这一行。
|
|
374
|
+
* 代理侧现读落盘文件走的也是这个函数,那边拿不到 `cfg.scaffoldBlocks`。
|
|
375
|
+
*/
|
|
376
|
+
export function renderPolicyEnv(
|
|
377
|
+
s: PolicySettings,
|
|
378
|
+
restored = false,
|
|
379
|
+
blueprintHeld: readonly string[] = [],
|
|
380
|
+
): string {
|
|
381
|
+
const parts: string[] = [];
|
|
382
|
+
if (s.scaffold !== null) {
|
|
383
|
+
// 与 bridge 一致地剔除重力方块,并报告剔除项。
|
|
384
|
+
// 空名单表示禁垫;非空名单被全部剔除时,bridge 回退到寻路器默认名单。
|
|
385
|
+
const usable = s.scaffold.filter((n) => !isGravityBlock(n));
|
|
386
|
+
const heavy = s.scaffold.filter((n) => isGravityBlock(n));
|
|
387
|
+
if (s.scaffold.length === 0) parts.push('不垫脚');
|
|
388
|
+
else if (usable.length === 0) {
|
|
389
|
+
parts.push(`垫脚名单里${namesText(heavy)}垫下去会自己掉,一样都没收下,垫一格仍走默认名单`);
|
|
390
|
+
} else {
|
|
391
|
+
parts.push(`垫一格只用${namesText(usable)}`);
|
|
392
|
+
if (heavy.length > 0) parts.push(`${namesText(heavy)}垫下去会自己掉,不收`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (s.light !== null) {
|
|
396
|
+
parts.push(s.light.length === 0 ? '不插火把' : `插一根只用${namesText(s.light)}`);
|
|
397
|
+
}
|
|
398
|
+
if (s.lightWhen !== 'dig') parts.push(LIGHT_WHEN_ZH[s.lightWhen]);
|
|
399
|
+
if (s.travel !== 'auto') parts.push(`赶路遇坎${TRAVEL_ZH[s.travel]}`);
|
|
400
|
+
if (s.reserve.length > 0) parts.push(`${namesText(s.reserve)}收着不主动拿`);
|
|
401
|
+
if (s.fight !== DEFAULT_FIGHT) parts.push(FIGHT_ZH[s.fight]);
|
|
402
|
+
// 蓝图预留是当下现场状况,不是「与默认不同的规矩」:单独一句,六格全默认时也照说
|
|
403
|
+
// (`PolicyBook` 拿这个函数判「有没有非默认项」,所以它不许把这一句算进去 —— 那一路
|
|
404
|
+
// 一律不传 blueprintHeld)
|
|
405
|
+
const held = blueprintHeld.length > 0 ? blueprintHeldText(blueprintHeld) : '';
|
|
406
|
+
if (parts.length === 0) return held;
|
|
407
|
+
// restored 表示上次运行落盘且本次尚未修改的设置;连接回执注明来源。
|
|
408
|
+
return `${restored ? '上一场设下的常驻规矩还在' : '常驻规矩'}(与默认不同的几条):${parts.join(';')}。${held}`;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** 读盘的结果:六格,外加这一次读盘**丢掉**的 `fight` 档(没丢就是 null) */
|
|
412
|
+
interface PolicyLoad {
|
|
413
|
+
settings: PolicySettings;
|
|
414
|
+
/** 盘上存着、而本进程按规矩不认的那一档;它是「回弹」这件事的唯一凭据 */
|
|
415
|
+
droppedFight: FightMode | null;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* 从落盘文件加载六项设置,缺失或无效字段回默认;fight 始终回 DEFAULT_FIGHT。
|
|
420
|
+
* 被丢弃的 fight 值经 droppedFight 传出,由连接回执报告回滚。
|
|
421
|
+
*/
|
|
422
|
+
export function loadPolicyReport(file: string | null): PolicyLoad {
|
|
423
|
+
const out = defaultPolicy();
|
|
424
|
+
if (!file || !existsSync(file)) return { settings: out, droppedFight: null };
|
|
425
|
+
let data: Record<string, unknown>;
|
|
426
|
+
try {
|
|
427
|
+
const raw = readFileSync(file, 'utf8').trim();
|
|
428
|
+
if (!raw) return { settings: out, droppedFight: null };
|
|
429
|
+
data = JSON.parse(raw) as Record<string, unknown>;
|
|
430
|
+
} catch {
|
|
431
|
+
return { settings: out, droppedFight: null };
|
|
432
|
+
}
|
|
433
|
+
let droppedFight: FightMode | null = null;
|
|
434
|
+
for (const f of POLICY_FIELDS) {
|
|
435
|
+
// fight 重启后回配置默认值;其余五项工作方式跨重启保留。
|
|
436
|
+
if (f.key === 'fight') {
|
|
437
|
+
const disk = data[f.key];
|
|
438
|
+
if (disk !== undefined && disk !== null) {
|
|
439
|
+
const r = f.parse(disk);
|
|
440
|
+
// 盘上是默认档 = 什么都没丢,不必惊动她
|
|
441
|
+
if (!('why' in r) && r.value !== out.fight) droppedFight = r.value as FightMode;
|
|
442
|
+
}
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
const v = data[f.key];
|
|
446
|
+
if (v === undefined || v === null) continue;
|
|
447
|
+
const r = f.parse(v);
|
|
448
|
+
if (!('why' in r)) (out as unknown as Record<string, unknown>)[f.key] = r.value;
|
|
449
|
+
}
|
|
450
|
+
return { settings: out, droppedFight };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** 只要六格的读法;丢档信息见 `loadPolicyReport` */
|
|
454
|
+
export function loadPolicy(file: string | null): PolicySettings {
|
|
455
|
+
return loadPolicyReport(file).settings;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* 回弹播报:上一进程设下的 `fight` 档没跨过重启,这一刻据实说出来。
|
|
460
|
+
*
|
|
461
|
+
* 措辞只陈述两件事实(原来那档是什么、现在按什么规矩转)加一句怎么改回去。
|
|
462
|
+
* 不解释重启的原因 —— 那一层归闭包原则管,而且我们也未必知道是崩了还是人为关的。
|
|
463
|
+
*/
|
|
464
|
+
export function renderFightRollback(prev: FightMode): string {
|
|
465
|
+
return `战斗那一格不跨重启:上次设的「${FIGHT_ZH[prev]}」已经回到默认「${FIGHT_ZH[DEFAULT_FIGHT]}」`
|
|
466
|
+
+ `;还要按原样打就再设一次 fight。`;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* 常驻规矩的持有者:进程内一份 + 落盘一份。
|
|
471
|
+
*
|
|
472
|
+
* 落盘的理由与容器账本同一条:进程内状态被一次崩溃重启静默清回默认,正是「隐形」
|
|
473
|
+
* 那类最贵的失败。风险(重启后带着一条她不记得的规矩)由环境提示词与连入播报兜住,
|
|
474
|
+
* 而代价最大的那一格(`fight`)干脆不跨重启,回弹那一刻由连入播报点名 —— 见
|
|
475
|
+
* `loadPolicyReport` 与 `takeFightRollback`。
|
|
476
|
+
*/
|
|
477
|
+
export class PolicyBook {
|
|
478
|
+
private s: PolicySettings;
|
|
479
|
+
/** 载入时磁盘带来的非默认项至今没被动过 —— 连入播报据此点明来路 */
|
|
480
|
+
private restoredFromDisk: boolean;
|
|
481
|
+
/** 载入时被丢掉的 `fight` 档,等着连入播报取走(取过就清) */
|
|
482
|
+
private droppedFight: FightMode | null;
|
|
483
|
+
|
|
484
|
+
constructor(private readonly file: string | null) {
|
|
485
|
+
const load = loadPolicyReport(file);
|
|
486
|
+
this.s = load.settings;
|
|
487
|
+
this.restoredFromDisk = renderPolicyEnv(this.s) !== '';
|
|
488
|
+
this.droppedFight = load.droppedFight;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** 现在这份非默认规矩是不是上一场留下、本场一次没改过的 */
|
|
492
|
+
restored(): boolean {
|
|
493
|
+
return this.restoredFromDisk;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* 一次性取走 fight 回滚通知;重连复用本对象,不重播。
|
|
498
|
+
* 取走通知时才将磁盘值写回当前值,避免连入前退出导致通知丢失。
|
|
499
|
+
*/
|
|
500
|
+
takeFightRollback(): FightMode | null {
|
|
501
|
+
const prev = this.droppedFight;
|
|
502
|
+
if (prev === null) return null;
|
|
503
|
+
this.droppedFight = null;
|
|
504
|
+
this.save();
|
|
505
|
+
return prev;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
get(): PolicySettings {
|
|
509
|
+
return {
|
|
510
|
+
...this.s,
|
|
511
|
+
scaffold: this.s.scaffold ? [...this.s.scaffold] : null,
|
|
512
|
+
light: this.s.light ? [...this.s.light] : null,
|
|
513
|
+
reserve: [...this.s.reserve],
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** 给哪一格就改哪一格;没给的一个字都不动 */
|
|
518
|
+
set(patch: Partial<PolicySettings>): void {
|
|
519
|
+
this.s = { ...this.s, ...patch };
|
|
520
|
+
this.restoredFromDisk = false;
|
|
521
|
+
// 她自己重新拧了这一格,回弹就没什么可报的了 —— 再报只会把人绕晕
|
|
522
|
+
if (patch.fight !== undefined) this.droppedFight = null;
|
|
523
|
+
this.save();
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
stat(): string {
|
|
527
|
+
const env = renderPolicyEnv(this.s);
|
|
528
|
+
if (!this.file || !existsSync(this.file)) return env === '' ? '(全默认)' : '非默认(未落盘)';
|
|
529
|
+
const kb = (statSync(this.file).size / 1024).toFixed(1);
|
|
530
|
+
return `${env === '' ? '全默认' : '有非默认项'} / ${kb}KB`;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
clear(): string {
|
|
534
|
+
const wasDefault = renderPolicyEnv(this.s) === '';
|
|
535
|
+
this.s = defaultPolicy();
|
|
536
|
+
this.restoredFromDisk = false;
|
|
537
|
+
this.droppedFight = null;
|
|
538
|
+
this.save();
|
|
539
|
+
return wasDefault ? '常驻规矩本来就都是默认的' : '常驻规矩已清回默认';
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
private save(): void {
|
|
543
|
+
if (!this.file) return;
|
|
544
|
+
mkdirSync(dirname(this.file), { recursive: true });
|
|
545
|
+
writeFileSync(this.file, `${JSON.stringify(this.s)}\n`, 'utf8');
|
|
546
|
+
}
|
|
547
|
+
}
|