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,566 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 用 Minecraft 1.20.6 注册表校验蓝图状态,并映射施工物品、功能动作与支撑规则。
|
|
3
|
+
* 数据通过 Mineflayer 依赖的 minecraft-data 和 prismarine-block 加载并缓存。
|
|
4
|
+
* wall_ 变体映射到去掉 wall_ 后的物品名;位置相关支撑由 compileBlueprint 核验。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import {
|
|
9
|
+
CARDINAL_VECTORS,
|
|
10
|
+
blockIdOf,
|
|
11
|
+
formatBlockState,
|
|
12
|
+
isAirState,
|
|
13
|
+
parseBlockState,
|
|
14
|
+
type Cardinal,
|
|
15
|
+
type NormalizedBlueprint,
|
|
16
|
+
type ParsedBlockState,
|
|
17
|
+
} from './blueprint.ts';
|
|
18
|
+
|
|
19
|
+
export const BLUEPRINT_MC_VERSION = '1.20.6' as const;
|
|
20
|
+
|
|
21
|
+
interface RegistryStateDefinition {
|
|
22
|
+
name: string;
|
|
23
|
+
type: 'enum' | 'bool' | 'int' | 'direction';
|
|
24
|
+
values?: unknown[];
|
|
25
|
+
num_values: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface RegistryBlock {
|
|
29
|
+
name: string;
|
|
30
|
+
defaultState: number;
|
|
31
|
+
states?: RegistryStateDefinition[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface MinecraftRegistry {
|
|
35
|
+
blocksByName: Record<string, RegistryBlock | undefined>;
|
|
36
|
+
itemsByName: Record<string, { id: number; name: string } | undefined>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface DecodedBlockState {
|
|
40
|
+
getProperties(): Record<string, boolean | number | string>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface PrismarineBlock {
|
|
44
|
+
fromStateId(stateId: number, biomeId: number): DecodedBlockState;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let cachedRegistry: MinecraftRegistry | null = null;
|
|
48
|
+
let cachedBlockFactory: PrismarineBlock | null = null;
|
|
49
|
+
|
|
50
|
+
/** 从 Mineflayer 的依赖目录解析 minecraft-data 和 prismarine-block。 */
|
|
51
|
+
function fromMineflayer<T>(moduleName: string): T {
|
|
52
|
+
const localRequire = createRequire(import.meta.url);
|
|
53
|
+
const mineflayerRequire = createRequire(localRequire.resolve('mineflayer'));
|
|
54
|
+
return mineflayerRequire(moduleName) as T;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function registry(): MinecraftRegistry {
|
|
58
|
+
if (cachedRegistry === null) {
|
|
59
|
+
const factory = fromMineflayer<(version: string) => MinecraftRegistry>('minecraft-data');
|
|
60
|
+
cachedRegistry = factory(BLUEPRINT_MC_VERSION);
|
|
61
|
+
}
|
|
62
|
+
return cachedRegistry;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function blockFactory(): PrismarineBlock {
|
|
66
|
+
if (cachedBlockFactory === null) {
|
|
67
|
+
const factory = fromMineflayer<(version: string) => PrismarineBlock>('prismarine-block');
|
|
68
|
+
cachedBlockFactory = factory(BLUEPRINT_MC_VERSION);
|
|
69
|
+
}
|
|
70
|
+
return cachedBlockFactory;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** `minecraft:oak_planks` → `oak_planks`;别的命名空间返回 null */
|
|
74
|
+
function vanillaName(id: string): string | null {
|
|
75
|
+
const [namespace, name] = id.split(':', 2);
|
|
76
|
+
return namespace === 'minecraft' && name ? name : null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── 默认状态补齐 ──────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
interface DefaultStateCompletion {
|
|
82
|
+
state: string;
|
|
83
|
+
/** 这次补上的属性;一条都没补时是空对象 */
|
|
84
|
+
added: Record<string, string>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 以注册表的默认状态补齐未指定属性。 */
|
|
88
|
+
export function completeBlockStateDefaults(
|
|
89
|
+
state: string,
|
|
90
|
+
path = 'block state',
|
|
91
|
+
): DefaultStateCompletion {
|
|
92
|
+
const parsed = parseBlockState(state, path);
|
|
93
|
+
const name = vanillaName(parsed.id);
|
|
94
|
+
if (name === null) return { state, added: {} };
|
|
95
|
+
const block = registry().blocksByName[name];
|
|
96
|
+
if (block === undefined) return { state, added: {} };
|
|
97
|
+
|
|
98
|
+
const defaults = blockFactory().fromStateId(block.defaultState, 0).getProperties();
|
|
99
|
+
const added: Record<string, string> = {};
|
|
100
|
+
for (const definition of block.states ?? []) {
|
|
101
|
+
if (parsed.properties[definition.name] !== undefined) continue;
|
|
102
|
+
const value = defaults[definition.name];
|
|
103
|
+
if (value !== undefined) added[definition.name] = String(value);
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
state: formatBlockState({ id: parsed.id, properties: { ...added, ...parsed.properties } }),
|
|
107
|
+
added,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── registry 校验 ─────────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
export interface BlueprintStateFailure {
|
|
114
|
+
/** 该状态在矩阵里第一次出现的位置 */
|
|
115
|
+
path: string;
|
|
116
|
+
state: string;
|
|
117
|
+
reason: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface RegistryValidation {
|
|
121
|
+
valid: boolean;
|
|
122
|
+
minecraft_version: typeof BLUEPRINT_MC_VERSION;
|
|
123
|
+
/** 去重后的状态条数;每种只校验一次 */
|
|
124
|
+
unique_states: number;
|
|
125
|
+
failures: BlueprintStateFailure[];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** 状态 → 它在矩阵里第一次出现的路径;同一状态只校验一次,报错报第一处 */
|
|
129
|
+
function firstStatePaths(blueprint: NormalizedBlueprint): Map<string, string> {
|
|
130
|
+
const states = new Map<string, string>();
|
|
131
|
+
for (let y = 0; y < blueprint.layers.length; y++) {
|
|
132
|
+
for (let z = 0; z < blueprint.layers[y].length; z++) {
|
|
133
|
+
for (let x = 0; x < blueprint.layers[y][z].length; x++) {
|
|
134
|
+
const state = blueprint.layers[y][z][x];
|
|
135
|
+
if (!states.has(state)) states.set(state, `layers[${y}][${z}][${x}]`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return states;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function invalidValueReason(
|
|
143
|
+
definition: RegistryStateDefinition,
|
|
144
|
+
value: string,
|
|
145
|
+
): string | null {
|
|
146
|
+
if (definition.type === 'bool') {
|
|
147
|
+
return value === 'true' || value === 'false'
|
|
148
|
+
? null
|
|
149
|
+
: `${definition.name}=${value} 不合法,只能是 true 或 false`;
|
|
150
|
+
}
|
|
151
|
+
const allowed = definition.values?.map(String);
|
|
152
|
+
if (allowed !== undefined && !allowed.includes(value)) {
|
|
153
|
+
return `${definition.name}=${value} 不合法,只能是 ${allowed.join('、')}`;
|
|
154
|
+
}
|
|
155
|
+
if (definition.type === 'int' && !/^-?\d+$/.test(value)) {
|
|
156
|
+
return `${definition.name}=${value} 不合法,这里要整数`;
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function propertyProblems(parsed: ParsedBlockState, block: RegistryBlock): string[] {
|
|
162
|
+
const definitions = block.states ?? [];
|
|
163
|
+
const byName = new Map(definitions.map((definition) => [definition.name, definition]));
|
|
164
|
+
const problems: string[] = [];
|
|
165
|
+
|
|
166
|
+
const missing = definitions
|
|
167
|
+
.map((definition) => definition.name)
|
|
168
|
+
.filter((name) => !(name in parsed.properties));
|
|
169
|
+
if (missing.length > 0) problems.push(`缺属性:${missing.join('、')}`);
|
|
170
|
+
|
|
171
|
+
const unknown = Object.keys(parsed.properties).filter((name) => !byName.has(name)).sort();
|
|
172
|
+
if (unknown.length > 0) problems.push(`多了不存在的属性:${unknown.join('、')}`);
|
|
173
|
+
|
|
174
|
+
for (const definition of definitions) {
|
|
175
|
+
const value = parsed.properties[definition.name];
|
|
176
|
+
if (value === undefined) continue;
|
|
177
|
+
const problem = invalidValueReason(definition, value);
|
|
178
|
+
if (problem !== null) problems.push(problem);
|
|
179
|
+
}
|
|
180
|
+
return problems;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function validateState(state: string, path: string): BlueprintStateFailure | null {
|
|
184
|
+
let parsed: ParsedBlockState;
|
|
185
|
+
try {
|
|
186
|
+
parsed = parseBlockState(state, path);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
return { path, state, reason: error instanceof Error ? error.message : '状态串解析失败' };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const name = vanillaName(parsed.id);
|
|
192
|
+
if (name === null) {
|
|
193
|
+
return { path, state, reason: `命名空间只收 minecraft,给的是 ${parsed.id}` };
|
|
194
|
+
}
|
|
195
|
+
const block = registry().blocksByName[name];
|
|
196
|
+
if (block === undefined) {
|
|
197
|
+
return { path, state, reason: `Java ${BLUEPRINT_MC_VERSION} 里没有 ${parsed.id} 这个方块` };
|
|
198
|
+
}
|
|
199
|
+
const problems = propertyProblems(parsed, block);
|
|
200
|
+
return problems.length === 0 ? null : { path, state, reason: problems.join(';') };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** 逐个去重状态过 registry:方块存不存在、属性齐不齐、值合不合法 */
|
|
204
|
+
export function validateBlueprintRegistry(blueprint: NormalizedBlueprint): RegistryValidation {
|
|
205
|
+
const paths = firstStatePaths(blueprint);
|
|
206
|
+
const failures: BlueprintStateFailure[] = [];
|
|
207
|
+
for (const [state, path] of paths) {
|
|
208
|
+
const failure = validateState(state, path);
|
|
209
|
+
if (failure !== null) failures.push(failure);
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
valid: failures.length === 0,
|
|
213
|
+
minecraft_version: BLUEPRINT_MC_VERSION,
|
|
214
|
+
unique_states: paths.size,
|
|
215
|
+
failures,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── 状态 → 物品 ───────────────────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
/** 同名物品不能直接放出目标方块的例外。 */
|
|
222
|
+
const ITEM_EXISTS_BUT_UNPLACEABLE: Record<string, string> = {
|
|
223
|
+
farmland: '耕地是拿锄头锄出来的,不是放出来的',
|
|
224
|
+
dirt_path: '土径是拿锹铲出来的,不是放出来的',
|
|
225
|
+
wheat: '小麦方块是种子长出来的(wheat 物品是食材)',
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/** 目标方块与放置物品的名称映射;wall_ 变体由通用规则处理。 */
|
|
229
|
+
const ITEM_ALIAS: Record<string, string> = {
|
|
230
|
+
redstone_wire: 'redstone',
|
|
231
|
+
tripwire: 'string',
|
|
232
|
+
cocoa: 'cocoa_beans',
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
type BlockItemLookup =
|
|
236
|
+
| { item: string }
|
|
237
|
+
| { item: null; reason: string };
|
|
238
|
+
|
|
239
|
+
export type BlueprintPlacementMethod =
|
|
240
|
+
| {
|
|
241
|
+
kind: 'place';
|
|
242
|
+
item: string;
|
|
243
|
+
/** 放置后通过右键调到的功能状态。 */
|
|
244
|
+
postUse?: { property: string; value: string; maxUses: number };
|
|
245
|
+
}
|
|
246
|
+
| {
|
|
247
|
+
kind: 'use';
|
|
248
|
+
item: string;
|
|
249
|
+
target: 'self' | 'below';
|
|
250
|
+
/** 该动作要求保持的功能属性;其余属性仍按 state 尽力口径。 */
|
|
251
|
+
verify?: { property: string; value: string };
|
|
252
|
+
/** 可加工成目标的源方块;首项用于材料统计和补充基材。 */
|
|
253
|
+
baseItem?: readonly string[];
|
|
254
|
+
/** 工具不会按施工格数消耗,整段只要求一件。 */
|
|
255
|
+
reusable?: true;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
type BlueprintPlacementResult =
|
|
259
|
+
| BlueprintPlacementMethod
|
|
260
|
+
| { item: null; reason: string };
|
|
261
|
+
|
|
262
|
+
/** hoe 和 shovel 按物品后缀匹配;耗材要求精确物品名。 */
|
|
263
|
+
export const BLUEPRINT_TOOL_FAMILIES: ReadonlySet<string> = new Set(['hoe', 'shovel']);
|
|
264
|
+
|
|
265
|
+
/** 锄头加工的目标状态映射;仅包含此处支持的源方块。 */
|
|
266
|
+
export const HOE_TILLED: Readonly<Record<string, string>> = {
|
|
267
|
+
grass_block: 'farmland',
|
|
268
|
+
dirt: 'farmland',
|
|
269
|
+
dirt_path: 'farmland',
|
|
270
|
+
farmland: 'farmland',
|
|
271
|
+
coarse_dirt: 'dirt',
|
|
272
|
+
rooted_dirt: 'dirt',
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/** 锹右键能压成土径的地表。 */
|
|
276
|
+
export const SHOVEL_PATH: Readonly<Record<string, string>> = {
|
|
277
|
+
grass_block: 'dirt_path',
|
|
278
|
+
dirt: 'dirt_path',
|
|
279
|
+
coarse_dirt: 'dirt_path',
|
|
280
|
+
mycelium: 'dirt_path',
|
|
281
|
+
podzol: 'dirt_path',
|
|
282
|
+
rooted_dirt: 'dirt_path',
|
|
283
|
+
dirt_path: 'dirt_path',
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
/** 可由该工具加工成 target 的全部源方块;canonical 排首位,供账单与补基材使用。 */
|
|
287
|
+
function sourcesFor(table: Readonly<Record<string, string>>, target: string, canonical: string): string[] {
|
|
288
|
+
return [canonical, ...Object.keys(table).filter((k) => table[k] === target && k !== canonical)];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const FARMLAND_SOURCES = sourcesFor(HOE_TILLED, 'farmland', 'dirt');
|
|
292
|
+
const DIRT_PATH_SOURCES = sourcesFor(SHOVEL_PATH, 'dirt_path', 'dirt');
|
|
293
|
+
|
|
294
|
+
const CROP_ITEM: Record<string, string> = {
|
|
295
|
+
wheat: 'wheat_seeds',
|
|
296
|
+
beetroots: 'beetroot_seeds',
|
|
297
|
+
carrots: 'carrot',
|
|
298
|
+
potatoes: 'potato',
|
|
299
|
+
melon_stem: 'melon_seeds',
|
|
300
|
+
pumpkin_stem: 'pumpkin_seeds',
|
|
301
|
+
torchflower_crop: 'torchflower_seeds',
|
|
302
|
+
nether_wart: 'nether_wart',
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/** 将目标状态映射到执行器支持的施工动作。 */
|
|
306
|
+
export function blueprintPlacementMethod(state: string): BlueprintPlacementResult {
|
|
307
|
+
const parsed = parseBlockState(state);
|
|
308
|
+
const name = vanillaName(parsed.id);
|
|
309
|
+
if (name === null) return { item: null, reason: `命名空间只收 minecraft,给的是 ${parsed.id}` };
|
|
310
|
+
|
|
311
|
+
if (name === 'farmland') {
|
|
312
|
+
return { kind: 'use', item: 'hoe', target: 'self', baseItem: FARMLAND_SOURCES, reusable: true };
|
|
313
|
+
}
|
|
314
|
+
if (name === 'dirt_path') {
|
|
315
|
+
return { kind: 'use', item: 'shovel', target: 'self', baseItem: DIRT_PATH_SOURCES, reusable: true };
|
|
316
|
+
}
|
|
317
|
+
if (name === 'water' || name === 'lava') {
|
|
318
|
+
if (parsed.properties.level !== '0') {
|
|
319
|
+
return { item: null, reason: `${name} 只收 level=0 的源方块,流动状态由游戏自己生成` };
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
kind: 'use', item: `${name}_bucket`, target: 'below',
|
|
323
|
+
verify: { property: 'level', value: '0' },
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
if (name === 'pitcher_crop') {
|
|
327
|
+
return { item: null, reason: 'pitcher_crop 会随生长阶段从一格变两格,蓝图不伪造它的过程状态' };
|
|
328
|
+
}
|
|
329
|
+
const cropItem = CROP_ITEM[name];
|
|
330
|
+
if (cropItem !== undefined) {
|
|
331
|
+
if (parsed.properties.age !== undefined && parsed.properties.age !== '0') {
|
|
332
|
+
return { item: null, reason: `${name} 蓝图只收 age=0;成熟度由生长过程产生` };
|
|
333
|
+
}
|
|
334
|
+
return { kind: 'use', item: cropItem, target: 'below' };
|
|
335
|
+
}
|
|
336
|
+
if (name === 'fire' && parsed.properties.age !== undefined && parsed.properties.age !== '0') {
|
|
337
|
+
return { item: null, reason: 'fire 蓝图只收 age=0;后续火势由游戏演化' };
|
|
338
|
+
}
|
|
339
|
+
if (name === 'fire' || name === 'soul_fire') {
|
|
340
|
+
return { kind: 'use', item: 'flint_and_steel', target: 'below', reusable: true };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const direct = blockStateItem(state);
|
|
344
|
+
if (direct.item === null) return direct;
|
|
345
|
+
let postUse: Extract<BlueprintPlacementMethod, { kind: 'place' }>['postUse'];
|
|
346
|
+
if ((name === 'iron_door' || name === 'iron_trapdoor') && parsed.properties.open === 'true') {
|
|
347
|
+
return { item: null, reason: `${name} 不能徒手切到 open=true;请把蓝图终态画成关闭状态` };
|
|
348
|
+
}
|
|
349
|
+
if (name === 'lever') {
|
|
350
|
+
postUse = { property: 'powered', value: parsed.properties.powered ?? 'false', maxUses: 1 };
|
|
351
|
+
}
|
|
352
|
+
if (name === 'repeater') {
|
|
353
|
+
postUse = { property: 'delay', value: parsed.properties.delay ?? '1', maxUses: 4 };
|
|
354
|
+
}
|
|
355
|
+
if (name === 'comparator') {
|
|
356
|
+
postUse = { property: 'mode', value: parsed.properties.mode ?? 'compare', maxUses: 2 };
|
|
357
|
+
}
|
|
358
|
+
if ((name.endsWith('_door') || name.endsWith('_trapdoor') || name.endsWith('_fence_gate'))
|
|
359
|
+
&& name !== 'iron_door' && name !== 'iron_trapdoor') {
|
|
360
|
+
postUse = { property: 'open', value: parsed.properties.open ?? 'false', maxUses: 1 };
|
|
361
|
+
}
|
|
362
|
+
return { kind: 'place', item: direct.item, ...(postUse ? { postUse } : {}) };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** 映射直接放置目标所需的物品;空气返回 null。属性核验由施工步骤处理。 */
|
|
366
|
+
export function blockStateItem(state: string): BlockItemLookup {
|
|
367
|
+
if (isAirState(state)) return { item: null, reason: '空气格不用放东西' };
|
|
368
|
+
|
|
369
|
+
const id = blockIdOf(state);
|
|
370
|
+
const name = vanillaName(id);
|
|
371
|
+
if (name === null) return { item: null, reason: `命名空间只收 minecraft,给的是 ${id}` };
|
|
372
|
+
|
|
373
|
+
const unplaceable = ITEM_EXISTS_BUT_UNPLACEABLE[name];
|
|
374
|
+
if (unplaceable !== undefined) return { item: null, reason: unplaceable };
|
|
375
|
+
|
|
376
|
+
const items = registry().itemsByName;
|
|
377
|
+
const alias = ITEM_ALIAS[name];
|
|
378
|
+
if (alias !== undefined && items[alias]) return { item: alias };
|
|
379
|
+
if (items[name]) return { item: name };
|
|
380
|
+
|
|
381
|
+
if (name.includes('wall_')) {
|
|
382
|
+
const wallless = name.replace('wall_', '');
|
|
383
|
+
if (items[wallless]) return { item: wallless };
|
|
384
|
+
}
|
|
385
|
+
return { item: null, reason: `没有能直接放出 ${id} 的物品` };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ── 支撑与附着 ────────────────────────────────────────────────────────────────
|
|
389
|
+
|
|
390
|
+
/** 支撑要求:block 匹配指定方块,sturdy 要求完整顶面,attach 排除空气及已列出的无支撑方块。 */
|
|
391
|
+
type BlueprintSupportRequirement =
|
|
392
|
+
| { kind: 'block'; id: string }
|
|
393
|
+
| { kind: 'sturdy' }
|
|
394
|
+
| { kind: 'attach' };
|
|
395
|
+
|
|
396
|
+
interface BlueprintSupportRule {
|
|
397
|
+
/** 支撑格相对这一格的位移(蓝图局部坐标:X 西→东、Y 下→上、Z 北→南) */
|
|
398
|
+
offset: readonly [number, number, number];
|
|
399
|
+
requirement: BlueprintSupportRequirement;
|
|
400
|
+
/** 支撑要求的说明,用于校验失败和图外条件提示。 */
|
|
401
|
+
demand: string;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const BELOW = [0, -1, 0] as const;
|
|
405
|
+
|
|
406
|
+
const FARMLAND_ID = 'minecraft:farmland';
|
|
407
|
+
|
|
408
|
+
/** 已支持作物的支撑方块。 */
|
|
409
|
+
const CROP_SOIL: Record<string, { id: string; label: string }> = {
|
|
410
|
+
wheat: { id: FARMLAND_ID, label: '耕地' },
|
|
411
|
+
beetroots: { id: FARMLAND_ID, label: '耕地' },
|
|
412
|
+
carrots: { id: FARMLAND_ID, label: '耕地' },
|
|
413
|
+
potatoes: { id: FARMLAND_ID, label: '耕地' },
|
|
414
|
+
melon_stem: { id: FARMLAND_ID, label: '耕地' },
|
|
415
|
+
pumpkin_stem: { id: FARMLAND_ID, label: '耕地' },
|
|
416
|
+
torchflower_crop: { id: FARMLAND_ID, label: '耕地' },
|
|
417
|
+
nether_wart: { id: 'minecraft:soul_sand', label: '灵魂沙' },
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const STANDING_TORCHES: ReadonlySet<string> = new Set(['torch', 'soul_torch', 'redstone_torch']);
|
|
421
|
+
const WALL_TORCHES: ReadonlySet<string> = new Set([
|
|
422
|
+
'wall_torch', 'soul_wall_torch', 'redstone_wall_torch',
|
|
423
|
+
]);
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* 仅校验门、作物、火把和床的支撑;未列出的方块不在此校验。
|
|
427
|
+
* 从部件由主步骤处理。
|
|
428
|
+
*/
|
|
429
|
+
export function blueprintSupportRules(state: string): BlueprintSupportRule[] {
|
|
430
|
+
const parsed = parseBlockState(state);
|
|
431
|
+
const name = vanillaName(parsed.id);
|
|
432
|
+
if (name === null) return [];
|
|
433
|
+
|
|
434
|
+
const soil = CROP_SOIL[name];
|
|
435
|
+
if (soil !== undefined) {
|
|
436
|
+
return [{
|
|
437
|
+
offset: BELOW,
|
|
438
|
+
requirement: { kind: 'block', id: soil.id },
|
|
439
|
+
demand: `要种在${soil.label}上`,
|
|
440
|
+
}];
|
|
441
|
+
}
|
|
442
|
+
if (name.endsWith('_door') && parsed.properties.half !== 'upper') {
|
|
443
|
+
return [{ offset: BELOW, requirement: { kind: 'sturdy' }, demand: '底下要一格顶面完整的方块' }];
|
|
444
|
+
}
|
|
445
|
+
if (STANDING_TORCHES.has(name)) {
|
|
446
|
+
return [{ offset: BELOW, requirement: { kind: 'attach' }, demand: '要立在一格托得住它的方块上' }];
|
|
447
|
+
}
|
|
448
|
+
if (WALL_TORCHES.has(name)) {
|
|
449
|
+
const facing = parsed.properties.facing;
|
|
450
|
+
if (facing === undefined || !Object.hasOwn(CARDINAL_VECTORS, facing)) return [];
|
|
451
|
+
// 取负别落出 -0(它和 0 相等但深比较不认)
|
|
452
|
+
const neg = (value: number): number => (value === 0 ? 0 : -value);
|
|
453
|
+
const [dx, dy, dz] = CARDINAL_VECTORS[facing as Cardinal];
|
|
454
|
+
return [{
|
|
455
|
+
offset: [neg(dx), neg(dy), neg(dz)],
|
|
456
|
+
requirement: { kind: 'attach' },
|
|
457
|
+
demand: '要贴在旁边一格方块的侧面上',
|
|
458
|
+
}];
|
|
459
|
+
}
|
|
460
|
+
// 1.20.6 里只有床有 part;床头是从部件,由床脚那一步一起长出来
|
|
461
|
+
if (parsed.properties.part === 'foot') {
|
|
462
|
+
return [{ offset: BELOW, requirement: { kind: 'attach' }, demand: '底下要一格托得住它的方块' }];
|
|
463
|
+
}
|
|
464
|
+
return [];
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** 这一格的施工要先把底下锄成耕地——账单据此把锄算进去。 */
|
|
468
|
+
export function blueprintNeedsTilledSoil(state: string): boolean {
|
|
469
|
+
return blueprintSupportRules(state).some(
|
|
470
|
+
(rule) => rule.requirement.kind === 'block' && rule.requirement.id === FARMLAND_ID,
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** 本实现排除的无支撑顶面方块。 */
|
|
475
|
+
const NO_TOP_SUPPORT_EXACT: Record<string, string> = {
|
|
476
|
+
farmland: '耕地只种得住作物',
|
|
477
|
+
dirt_path: '土径的上表面不完整',
|
|
478
|
+
iron_bars: '铁栏杆顶上托不住东西',
|
|
479
|
+
ladder: '梯子顶上托不住东西',
|
|
480
|
+
chain: '锁链顶上托不住东西',
|
|
481
|
+
rail: '铁轨顶上托不住东西',
|
|
482
|
+
scaffolding: '脚手架的上表面不完整',
|
|
483
|
+
snow: '雪片顶上托不住东西',
|
|
484
|
+
vine: '藤蔓顶上托不住东西',
|
|
485
|
+
redstone_wire: '红石线顶上托不住东西',
|
|
486
|
+
water: '水里放不住东西',
|
|
487
|
+
lava: '岩浆里放不住东西',
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const NO_TOP_SUPPORT_SUFFIX: ReadonlyArray<readonly [string, string]> = [
|
|
491
|
+
['_door', '门的上表面不完整'],
|
|
492
|
+
['_trapdoor', '活板门的上表面不完整'],
|
|
493
|
+
['_fence_gate', '栅栏门的上表面不完整'],
|
|
494
|
+
['torch', '火把顶上托不住东西'],
|
|
495
|
+
['_bed', '床顶上托不住东西'],
|
|
496
|
+
['_carpet', '地毯顶上托不住东西'],
|
|
497
|
+
['_button', '按钮顶上托不住东西'],
|
|
498
|
+
['_pressure_plate', '压力板顶上托不住东西'],
|
|
499
|
+
['_sapling', '树苗顶上托不住东西'],
|
|
500
|
+
['_sign', '告示牌顶上托不住东西'],
|
|
501
|
+
['_banner', '旗帜顶上托不住东西'],
|
|
502
|
+
['_rail', '铁轨顶上托不住东西'],
|
|
503
|
+
];
|
|
504
|
+
|
|
505
|
+
/** 可支撑火把或床、但不满足门完整顶面要求的方块。 */
|
|
506
|
+
const NO_STURDY_TOP_SUFFIX: ReadonlyArray<readonly [string, string]> = [
|
|
507
|
+
['_fence', '栅栏的顶面不是完整一格'],
|
|
508
|
+
['_wall', '墙的顶面不是完整一格'],
|
|
509
|
+
];
|
|
510
|
+
|
|
511
|
+
function noSupportReason(parsed: ParsedBlockState, name: string, sturdy: boolean): string | null {
|
|
512
|
+
const exact = NO_TOP_SUPPORT_EXACT[name];
|
|
513
|
+
if (exact !== undefined) return exact;
|
|
514
|
+
if (CROP_SOIL[name] !== undefined) return '作物顶上托不住东西';
|
|
515
|
+
for (const [suffix, why] of NO_TOP_SUPPORT_SUFFIX) if (name.endsWith(suffix)) return why;
|
|
516
|
+
if (!sturdy) return null;
|
|
517
|
+
for (const [suffix, why] of NO_STURDY_TOP_SUFFIX) if (name.endsWith(suffix)) return why;
|
|
518
|
+
if (name.endsWith('_slab') && parsed.properties.type === 'bottom') {
|
|
519
|
+
return '下半砖的顶面只到半格高';
|
|
520
|
+
}
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/** 支撑格不满足要求时的说明;满足、或规则表点不到那个方块时给 null。 */
|
|
525
|
+
export function blueprintSupportViolation(
|
|
526
|
+
rule: BlueprintSupportRule,
|
|
527
|
+
supportState: string,
|
|
528
|
+
): string | null {
|
|
529
|
+
if (isAirState(supportState)) return '图里那一格是空的';
|
|
530
|
+
const id = blockIdOf(supportState);
|
|
531
|
+
if (rule.requirement.kind === 'block') {
|
|
532
|
+
return id === rule.requirement.id ? null : `图里那一格是 ${id}`;
|
|
533
|
+
}
|
|
534
|
+
const parsed = parseBlockState(supportState);
|
|
535
|
+
const name = vanillaName(parsed.id);
|
|
536
|
+
if (name === null) return null;
|
|
537
|
+
const why = noSupportReason(parsed, name, rule.requirement.kind === 'sturdy');
|
|
538
|
+
return why === null ? null : `图里那一格是 ${id},${why}`;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export const BLUEPRINT_SUPPORT_DOC = [
|
|
542
|
+
'· 作物图自带耕地层:melon_stem/pumpkin_stem/小麦/胡萝卜这些的正下方要画'
|
|
543
|
+
+ ' minecraft:farmland(它会自动垫泥土再锄出来);图里没有那一层,种子就一直放不下去;',
|
|
544
|
+
'· 门、床、火把、作物这些要贴着别的方块才立得住:支撑格要么画进图里,要么确认现场已经有。'
|
|
545
|
+
+ '门底下要一格顶面完整的方块——栅栏、栅栏门、墙、下半砖、另一扇门都撑不住门;',
|
|
546
|
+
'· 想保留现场原样的格(河道、树干、已有墙体)画 minecraft:structure_void,别画成 minecraft:air'
|
|
547
|
+
+ '——air 是"这一格必须清空"。',
|
|
548
|
+
].join('\n');
|
|
549
|
+
|
|
550
|
+
interface PlaceabilityValidation {
|
|
551
|
+
valid: boolean;
|
|
552
|
+
failures: BlueprintStateFailure[];
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/** 按去重后的状态校验施工方式;孤立从部件由 compileBlueprint 按位置校验。 */
|
|
556
|
+
export function validateBlueprintPlaceability(
|
|
557
|
+
blueprint: NormalizedBlueprint,
|
|
558
|
+
): PlaceabilityValidation {
|
|
559
|
+
const failures: BlueprintStateFailure[] = [];
|
|
560
|
+
for (const [state, path] of firstStatePaths(blueprint)) {
|
|
561
|
+
if (isAirState(state)) continue;
|
|
562
|
+
const lookup = blueprintPlacementMethod(state);
|
|
563
|
+
if (lookup.item === null) failures.push({ path, state, reason: lookup.reason });
|
|
564
|
+
}
|
|
565
|
+
return { valid: failures.length === 0, failures };
|
|
566
|
+
}
|