# DPMT桌面版
**Repository Path**: greenptt/dpmt-desktop-edition
## Basic Information
- **Project Name**: DPMT桌面版
- **Description**: DPMT(DataPackMoreTool)的桌面版
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 0
- **Created**: 2026-07-13
- **Last Updated**: 2026-08-22
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# DPMT 生成工具
一个基于**数据驱动**架构的 Minecraft 数据包/资源包可视化生成工具。所有数据类型、UI 绑定、导出路径、命令语法均由 JSON/XML 配置文件定义,核心代码不硬编码任何业务规则。
---
## 目录
- [项目简介](#项目简介)
- [目录结构](#目录结构)
- [快速开始](#快速开始)
- [架构概览](#架构概览)
- [运行原理细节](#运行原理细节)
- [启动流程](#启动流程)
- [静态文件服务](#静态文件服务)
- [版本同步机制](#版本同步机制)
- [数据驱动配置加载](#数据驱动配置加载)
- [数据驱动格式](#数据驱动格式)
- [tool_data.json](#tool_datajson)
- [pack.dpmtmeta](#packdpmtmeta)
- [paths/\*.json(UI 绑定)](#pathsjsonui-绑定)
- [jsonTypes/\*.json(JSON 格式定义)](#jsontypesjsonjson-格式定义)
- [choices/\*.json(选择列表)](#choicesjson选择列表)
- [commandTypes/\*.xml(命令语法)](#commandtypesxml命令语法)
- [工作区格式(Workspace V2)](#工作区格式workspace-v2)
- [导出原理](#导出原理)
- [导入原理](#导入原理)
- [API 接口列表](#api-接口列表)
- [配置文件汇总](#配置文件汇总)
---
## 项目简介
- **前端**:原生 JavaScript(ES Module)+ CSS,无构建步骤,浏览器直接加载
- **后端**:Python 3.13+ 标准库(`http.server`),无第三方依赖
- **ZIP 处理**:前端使用 [JSZip](https://stuk.github.io/jszip/)(MIT/GPLv3 双协议,已随项目分发 LICENSE)
- **核心理念**:一切业务规则数据驱动,代码只提供运行时引擎
支持的数据类型涵盖:原版数据包(配方、进度、战利品表、谓词、函数、附魔、修饰符等)、原版资源包(纹理、模型、语言文件、pack.mcmeta)、DatapackMore 扩展(自定义物品、方块、状态效果)。
---
## 目录结构
```
src/main/ # 应用程序根目录(运行时目录)
├── launcher.py # 打包/exe 启动入口
├── backend/ # Python 后端
│ ├── main.py # HTTP 服务器 + 路由分发
│ ├── config.py # 路径常量、版本同步逻辑
│ ├── logger.py # 日志会话管理
│ └── handlers/
│ ├── api_handler.py # 工具数据、数据驱动配置、命令定义等 API
│ ├── import_handler.py # ZIP 导入 → Workspace 转换
│ └── launcher.py # 游戏启动
│
├── frontend/ # 前端静态资源
│ ├── index.html # 单页入口
│ ├── css/
│ │ ├── main.css # 主样式
│ │ ├── visualEditor.css # 可视化编辑器样式
│ │ └── imageEditor.css # 图片编辑器样式
│ ├── js/
│ │ ├── main.js # 应用主逻辑(App 类)
│ │ ├── config.js # API 端点配置
│ │ ├── components/
│ │ │ ├── editor.js # 字段编辑器
│ │ │ ├── fieldRenderer.js # 字段渲染器(数据驱动)
│ │ │ ├── wizard.js # 新建项目向导
│ │ │ ├── projectTree.js # 项目树
│ │ │ ├── typeRegistry.js # JSON 类型注册表(带缓存)
│ │ │ ├── commandBlockBuilder.js # 命令方块构建器
│ │ │ ├── commandTextEditor.js # 命令文本编辑器(语法补全)
│ │ │ ├── imageEditor.js # 图片编辑器
│ │ │ ├── modal.js # 通用模态框
│ │ │ ├── blocklyEditor.js # Blockly 积木编辑器
│ │ │ └── visualBlockEditor.js # 可视化积木编辑器
│ │ ├── views/projectView.js
│ │ ├── utils/
│ │ │ ├── api.js # 后端 API 封装
│ │ │ ├── i18n.js # 国际化
│ │ │ ├── validation.js # 命名空间/modId 校验
│ │ │ ├── choiceModal.js # 选择弹窗
│ │ │ └── tutorialModal.js # 教程弹窗
│ │ └── lib/
│ │ ├── jszip.min.js # JSZip 库
│ │ ├── jszip-LICENSE.txt
│ │ └── jszip.LICENSE
│ └── assets/ # 图标等静态资源(bootstrap-icons)
│
├── data/ # 运行时数据目录(自动同步,勿手改)
│ ├── jsonTypes/ # JSON 格式定义(核心)
│ │ ├── advancement/ # 进度定义
│ │ ├── core/ # 核心类型(stringlist、itemslist等)
│ │ │ ├── stringlist.json
│ │ │ ├── itemslist.json
│ │ │ ├── floatlist.json
│ │ │ ├── click_event.json
│ │ │ ├── hover_event.json
│ │ │ ├── hover_entity_content.json
│ │ │ ├── score_component.json
│ │ │ ├── text_component.json
│ │ │ ├── text_component_obj.json
│ │ │ ├── text_component_list.json
│ │ │ └── text_component_types.json
│ │ ├── datapackmore/ # DatapackMore 扩展定义
│ │ ├── enchantment/ # 附魔定义
│ │ ├── loot/ # 战利品表定义
│ │ ├── modifier/ # 修饰符定义
│ │ ├── pack/ # pack.mcmeta 定义
│ │ ├── predicate/ # 谓词定义
│ │ ├── recipe/ # 配方定义
│ │ │ ├── master.json
│ │ │ ├── result.json
│ │ │ ├── ingredient.json
│ │ │ ├── shaped_ingredients.json
│ │ │ ├── shapeless_ingredients.json
│ │ │ ├── trim_pattern.json
│ │ │ └── components/
│ │ └── tag/ # 标签定义
│ ├── paths/ # UI 绑定定义
│ ├── choices/ # 选择列表数据
│ │ ├── items/ # 物品ID选择列表
│ │ │ ├── all.json # 物品分类(子菜单)
│ │ │ ├── normal.json # 普通物品列表(带图片)
│ │ │ ├── blocks.json # 方块物品列表(带图片)
│ │ │ └── tagsall.json # 物品标签列表(普通)
│ │ ├── minecraft/ # Minecraft官方ID选择列表
│ │ │ ├── recipe_types/ # 配方类型选择列表
│ │ │ │ └── all.json
│ │ │ ├── enchantments.json
│ │ │ ├── advancements.json
│ │ │ ├── damage_types.json
│ │ │ ├── biome.json
│ │ │ └── ...(40+ 个选择文件)
│ │ ├── datapackmore/ # DatapackMore扩展选择列表
│ │ │ ├── block_model_parents.json
│ │ │ ├── effect_categories.json
│ │ │ ├── face_direction.json
│ │ │ ├── face_rotation.json
│ │ │ ├── item_model_parents.json
│ │ │ ├── item_model_types.json
│ │ │ ├── model_rotation_angle.json
│ │ │ └── rotation_axis.json
│ │ ├── modifier/ # 修饰符选择列表
│ │ │ ├── operation.json
│ │ │ └── slots.json
│ │ └── core/ # 核心选择列表
│ │ ├── click_event_actions.json
│ │ ├── hover_event_actions.json
│ │ ├── text_colors.json
│ │ ├── nbt_sources.json
│ │ ├── sprite_atlases.json
│ │ ├── sprite_types.json
│ │ └── text_component_types.json
│ ├── commandTypes/ # 命令类型定义(XML,新格式)
│ │ ├── manifest.json # 命令类型清单
│ │ ├── execute.xml
│ │ ├── give.xml
│ │ ├── tellraw.xml
│ │ ├── scoreboard.xml
│ │ ├── advancement.xml
│ │ ├── attribute.xml
│ │ ├── clear.xml
│ │ ├── damage.xml
│ │ ├── data.xml
│ │ ├── effect.xml
│ │ ├── fill.xml
│ │ ├── kill.xml
│ │ ├── say.xml
│ │ ├── setblock.xml
│ │ ├── spawnpoint.xml
│ │ ├── summon.xml
│ │ ├── time.xml
│ │ ├── tp.xml
│ │ └── weather.xml
│ ├── commandValueTypes/ # 命令参数值类型定义
│ │ ├── criterion.json
│ │ └── item.json
│ ├── assets/ # 原版资源包资源(用于参考)
│ ├── mod/ # 模组相关数据
│ │ └── datapackmore-1.0.0.jar
│ ├── templates/ # 新建文件模板
│ │ ├── enchantment/
│ │ └── recipe/
│ ├── tempid.json # 当前 data/ 对应的版本 ID 缓存
│ └── tempinjarpath.json # JAR 内路径临时记录
│
├── version/ # 版本数据源(开发时编辑这里)
│ ├── mcje_datapack_1_20_1/
│ ├── mcje_datapack_1_21_11/
│ ├── mcje_datapackmore_items_1_20_1/
│ ├── mcje_datapackmore_items_1_21_1/
│ ├── mcje_datapackmore_items_1_21_11/
│ ├── mcje_datapackmore_items_26_1/
│ └── mcje_datapackmore_items_26_2/
│ ├── pack.dpmtmeta # 版本元数据
│ └── data/ # 该版本的 jsonTypes/paths/choices
│ ├── better.py # 版本管理辅助脚本
│ └── cleanbak.py # 版本清理脚本
│
├── userdata/ # 用户数据
│ ├── tool_data.json # 工具配置(base_path、export_root、上次路径等)
│ └── settings/
│ ├── version.json # 选中的数据格式版本
│ └── language.json # 选中的语言
│
├── assets/ # 应用资源
│ └── launch.png # 启动画面
│
└── log/ # 运行日志(按会话分目录)
```
**项目顶层结构(源代码仓库):**
```
DPMT_WinEdition/
├── src/main/ # 应用程序源码(上述详细结构)
├── src/tools/toggle_tool/ # 切换工具
├── scripts/build/ # 构建脚本(build.py、DPMT.spec、pack.bat 等)
├── scripts/utils/ # 实用脚本
├── docs/ # 文档
├── imports/minecraft/ # 导入资源(如 zh_cn.json)
├── releases/ # 发布目录
├── resources/ # 应用图标资源(app.ico、app.png)
└── README.md # 本文件
```
---
## 快速开始
### 环境要求
- Python 3.13+(注意:`cgi` 模块在 3.13 已移除,本项目已不再依赖它)
- 现代浏览器(支持 ES Module)
### 启动
```bash
python launcher.py
```
服务器监听 `http://localhost:9999`,浏览器访问即可。启动时会自动同步所选版本的数据到 `data/` 目录。
---
## 架构概览
```
┌─────────────────────────────────────────────────────┐
│ 浏览器(前端) │
│ index.html + main.js + components/ │
│ ├─ 数据驱动 UI 渲染(fieldRenderer) │
│ ├─ JSZip 导出/导入(前端生成 ZIP,无需后端往返) │
│ ├─ 命令语法补全(commandTextEditor) │
│ └─ 文件浏览器(前端弹窗,替代 tkinter) │
└──────────────────────┬──────────────────────────────┘
│ HTTP(同域,相对路径)
┌──────────────────────▼──────────────────────────────┐
│ Python 后端(单进程多线程) │
│ socketserver.ThreadingTCPServer :9999 │
│ ├─ 静态文件服务(frontend/ + data/) │
│ ├─ API 路由(/api/*) │
│ ├─ multipart/form-data 手动解析(不依赖 cgi) │
│ └─ 版本同步(version/{id}/data → data/) │
└─────────────────────────────────────────────────────┘
```
**前后端职责划分:**
- 前端:所有 UI 渲染、数据编辑、ZIP 生成/解析、文件路径选择、modId 校验
- 后端:静态文件服务、文件系统读写(保存到用户选定路径)、版本同步、游戏启动
---
## 运行原理细节
### 启动流程
1. `src/main/launcher.py` 调用 `backend.main.start()`
2. 创建日志会话(`log/{时间戳}/`)
3. 读取 `userdata/settings/version.json` 获取选中的数据格式版本 ID
4. 调用 `sync_version_data()`:将 `version/{id}/data/` 拷贝到根 `data/`(基于 `based_on` 递归合并)
5. 写入 `data/tempid.json` 记录当前版本 ID
6. 启动 `ThreadingTCPServer` 监听 9999 端口
7. 前端加载后调用 `/api/tool-data` 和 `/api/datadriven-config` 获取配置
### 静态文件服务
`UnifiedHandler._get_file_path()` 处理两类路径:
- `/data/*` → 映射到项目根的 `data/` 目录(数据驱动配置文件)
- 其他路径 → 映射到 `frontend/` 目录(HTML/CSS/JS/图片)
**关键点**:使用 `urlparse(self.path).path` 去除查询字符串(如 `?v=20260630` 缓存版本号),否则文件查找会失败。路径中包含 `..` 直接拒绝(防路径遍历)。
### 版本同步机制
版本同步系统通过 `based_on` 字段支持**无限层级继承**,实现版本间的数据共享和扩展。
**同步流程**(`backend/config.py: sync_version_data`):
1. 读取目标版本的 `pack.dpmtmeta`,解析 `based_on` 字段
2. 递归追溯继承链,构建从根版本到当前版本的完整路径
- 示例:`mcje_datapackmore_items_1_21_11` → `mcje_datapack_1_21_11`
3. 清空运行时 `data/` 目录(保留 `tempid.json`)
4. 按继承链顺序依次拷贝:
- 先拷贝根版本的 `data/` 到运行时 `data/`
- 再拷贝中间版本的 `data/`(同名文件覆盖)
- 最后拷贝当前版本的 `data/`(同名文件覆盖)
5. 编译 `data/jsonTypes/` 中的 `*flatup` 字段——将 Key 为 `"*flatup"` 的字段展开为被引用类型的所有 parts
6. 写入 `data/tempid.json` 记录当前版本ID,避免重复拷贝
**递归合并规则**:
- **文件级别覆盖**:完全同路径的文件会被覆盖
- **目录级别合并**:目录内的不同子项全部保留
- **新增保留**:当前版本独有的文件/目录直接添加
**继承链示例**:
```
version/mcje_datapackmore_items_1_21_11/
├── pack.dpmtmeta { "based_on": "mcje_datapack_1_21_11" }
└── data/
└── jsonTypes/
└── datapackmore/
└── item.json ← 扩展版本新增
version/mcje_datapack_1_21_11/
├── pack.dpmtmeta { "based_on": null }
└── data/
└── jsonTypes/
├── core/
│ ├── stringlist.json ← 基版本提供
│ └── itemslist.json
├── recipe/
│ ├── master.json ← 基版本提供
│ ├── result.json
│ └── shaped_ingredients.json
同步后的运行时 data/jsonTypes/:
├── core/ ← 来自基版本
│ ├── stringlist.json
│ └── itemslist.json
├── recipe/ ← 来自基版本
│ ├── master.json
│ ├── result.json
│ └── shaped_ingredients.json
└── datapackmore/ ← 扩展版本新增
└── item.json
```
**性能优化**:
- 通过 `tempid.json` 缓存当前版本,启动时快速比对避免重复拷贝
- 用户可在设置中强制重新同步(重启生效)
- 最大继承深度限制为100层(防止无限循环)
#### *flatup 扁平引用编译机制
##### 设计目的
`*flatup` 解决的核心问题是**并列字段的复用与组合**:有一组字段需要在多个地方被引用,且引用后必须跟目标类型的其他字段处于**同一层级**(不能嵌套)。
用 `From:` 引用虽然能复用,但前端会渲染为嵌套容器——字段被包在子容器里,用户需要点开折叠才能看到。有些数据天然是扁平结构的(如实体的全部 NBT 字段),不应该有嵌套容器。
`*flatup` 在**编译时**(同步到 `data/` 的阶段)将被引用类型的所有字段平铺到当前文件中,使开发者可以:
- **按逻辑拆分字段组**:把一组相关的字段定义在一个独立的 jsonTypes 文件中,各文件各司其职
- **在多个类型中组合引用**:同一个字段组可以被多个不同的类型通过 `*flatup` 引用
- **编译期扁平化**:前端看到的 jsonTypes 是展开后的平坦 `parts` 数组,无需处理嵌套
- **源文件变更自动跟进**:字段组文件增删字段,所有引用它的类型重新编译后自动更新
**典型场景**:
- 实体 NBT 继承链(Entity → LivingEntity → Mob → ... → Bee)——每层的字段都是并列关系
- 多个类型共享一组公共字段(如所有容器共有的 inventory 字段)
- 任何需要"把这些字段原样摆在这里"而非"把这些字段包在一个框里"的场景
##### 与 `based_on` 版本继承的区别
| 对比项 | `based_on` 版本继承 | `*flatup` 字段级引用 |
|--------|---------------------|---------------------|
| 作用域 | 版本包之间(跨目录) | 同一版本内的 jsonTypes 文件之间 |
| 解决的问题 | 版本间数据复用(模组扩展继承原版) | 类型层级间字段复用(实体继承链) |
| 粒度 | 文件级别 | 字段级别 |
| 触发时机 | 同步时文件拷贝 | 同步时文件内 `parts` 展开 |
| 编译方式 | 后覆盖前(同名文件替换) | 递归展开(平铺到同一数组) |
##### 机制详解
在 version 源文件中用 `"*flatup"` 字段引用另一个类型的所有 parts,在同步到 `data/` 时将其展开为被引用类型的全部字段。
**典型用法**:实体数据类型的继承链展开(如蜜蜂实体字段由 Entity → LivingEntity → Mob → Breedable → Animal → Bee 逐层继承):
```json
{
"id": "entity_data",
"parts": [
{ "Key": "*flatup", "Type": ["From:predicate/entity_data/entity"] },
{ "Key": "*flatup", "Type": ["From:predicate/entity_data/living_entity"] },
{ "Key": "*flatup", "Type": ["From:predicate/entity_data/mob"] },
{ "Key": "*flatup", "Type": ["From:predicate/entity_data/breedable"] },
{ "Key": "*flatup", "Type": ["From:predicate/entity_data/animal"] },
{ "Key": "*flatup", "Type": ["From:predicate/entity_data/bee"] }
]
}
```
**编译规则**:
| 规则 | 说明 |
|------|------|
| 展开替换 | `Key: "*flatup"` 的字段被替换为被引用类型 `parts` 中的所有字段(包括被引用类型内部的 `*flatup`) |
| 递归展开 | 如果被引用类型中也有 `*flatup`,递归展开,最大深度 10 层 |
| 重复键名 | 展开后出现同名 `Key`,后出现的字段 `Key` 和 `Des` 尾部添加 "(repeat)" 后缀 |
| 跳过条件 | 引用文件不存在、格式错误、Type 不是单一 `From:` 路径、被引用类型是 TIL 列表类型时,保持 `*flatup` 不变 |
**边界行为**:如果继承链中的某个层级类型文件缺失(如动物通用标签引用了一个不存在的文件),该 `*flatup` 保持原样不展开,剩余的 `*flatup` 正常展开,不影响其他字段。
**工作原理**(`backend/config.py` 中的 `_compile_flatup` 系列函数):
1. 遍历 `data/jsonTypes/` 下所有 JSON 文件
2. 对每个文件检查 `parts` 中是否有 `Key: "*flatup"` 的字段
3. 解析 `Type[0]` 获取 `From:` 引用路径,加载被引用文件的内容
4. 递归展开被引用文件内部的 `*flatup`
5. 将展开后的字段列表替换原 `*flatup` 位置
6. 扫描展开后的字段列表,处理重复键名
### 数据驱动配置加载
前端启动时并行请求:
| API | 返回 | 来源目录 |
|-----|------|---------|
| `/api/tool-data` | 工具配置 | `userdata/tool_data.json` |
| `/api/datadriven-config` | `{ui: [...], paths: {...}}` | `data/paths/*.json` 合并 |
| `/api/commands` | 命令定义(旧格式) | `data/commands/` |
**合并 API 优化**:`/api/datadriven-config` 一次性读取 `data/paths/` 下所有 JSON,同时填充 `ui`(数组)和 `paths`(字典),减少 50% IO。
前端按需异步加载 `data/jsonTypes/{path}.json`(通过 `typeRegistry.loadType`,带内存缓存),避免启动时加载全部类型定义。
---
## 数据驱动格式
### tool_data.json
位于 `userdata/tool_data.json`,工具全局配置:
```json
{
"version": "1.0.0",
"base_path": "datapack/data/{namespace}",
"export_root": "datapack",
"types": {},
"in_app": true,
"lastPaths": {
"exportZip": "C:\\Users\\PC\\Downloads\\xx.zip",
"exportModJar": "C:\\Users\\PC\\Downloads\\xx.jar"
}
}
```
| 字段 | 说明 |
|------|------|
| `base_path` | 传统类型导出路径模板,`{namespace}` 替换为命名空间 |
| `export_root` | 导出 ZIP 的最外层根目录名(如 `datapack`),导出时所有文件加此前缀 |
| `types` | 传统类型定义(已基本被数据驱动取代,保留兼容) |
| `in_app` | 是否应用内启动模式 |
| `lastPaths` | 各前端弹窗的上次选择路径,下次弹窗默认定位到此 |
### pack.dpmtmeta
版本元数据,位于 `version/{id}/pack.dpmtmeta`:
**基础版本示例**(原版数据包):
```json
{
"id": "mcje_datapack_1_21_11",
"name": "mcjava版1.21.11原版数据包"
}
```
**扩展版本示例**(带继承和模组路径映射):
```json
{
"id": "mcje_datapackmore_items_1_21_11",
"name": "mcjava版fabric1.21.11模组",
"based_on": "mcje_datapack_1_21_11",
"injarpath": {
"datapack/datapack/data": "data",
"datapack/datapackmore/data": "assets/datapackmore/data",
"datapack/resourcepack/assets": "assets"
}
}
```
| 字段 | 说明 |
|------|------|
| `id` | 版本唯一标识 |
| `name` | 用户可见的显示名 |
| `based_on` | 可选,指向基版本 ID(支持无限层级继承) |
| `injarpath` | 可选,导出模组JAR时的路径映射字典 |
**injarpath 路径映射说明**:
- 键:Workspace中的路径前缀
- 值:JAR内的实际路径
- 示例:`"datapack/datapack/data": "data"` 表示Workspace中 `datapack/datapack/data/xxx` 目录下的文件在JAR中映射为 `data/xxx`
- 用途:DatapackMore模组需要将数据包和资源包文件合并到同一个JAR中,通过此映射实现正确的路径转换
### paths/\*.json(UI 绑定)
位于 `data/paths/`,每个文件定义一类数据在 UI 中的位置和导出路径。
```json
{
"UI_Path": "原版数据包/配方",
"Types": [
{
"save_path": "datapack/data/__NS__/recipe",
"BN": "json",
"Type": "json:recipe/master",
"templates": [
{ "name": "蛋糕配方", "data": "recipe/cake.json" }
]
}
]
}
```
| 字段 | 说明 |
|------|------|
| `UI_Path` | UI 树路径,`/` 分隔两层(如「原版数据包/配方」) |
| `Types[].save_path` | 导出 ZIP 内路径,`__NS__` 替换为命名空间 |
| `Types[].BN` | 文件后缀名(`json`/`mcfunction`/`png`/`mcmeta`) |
| `Types[].Type` | 数据类型,格式见下表 |
| `Types[].templates` | 可选,新建时提供的模板列表 |
**Type 字段取值:**
| 取值 | 含义 | 对应编辑器 |
|------|------|-----------|
| `json:{path}` | JSON 数据,引用 `data/jsonTypes/{path}.json` | 可视化字段编辑器 |
| `mcfunction` | 函数文件 | 命令文本编辑器(带语法补全) |
| `png` | 图片文件 | 图片编辑器 |
**特殊路径规则(__NS__指命名空间):**
- `datapack/data/__NS__/...` → 数据包文件
- `resourcepack/assets/__NS__/...` → 资源包文件
- `datapackmore/data/__NS__/...` → DatapackMore 扩展文件
### jsonTypes/\*.json(JSON 格式定义)
位于 `data/jsonTypes/`,定义 JSON 数据的字段结构。这是整个数据驱动系统的**核心**,决定了可视化编辑器的所有字段显示和验证逻辑。
#### 顶层结构
```json
{
"id": "<类型唯一标识>",
"parts": [...], // 对象型定义(不存在TIL时使用)
"TIL": [...] // 列表型定义(存在时parts失效)
}
```
| 字段 | 说明 |
|------|------|
| `id` | 类型唯一标识,供 `From:` 引用 |
| `parts` | 对象型定义的字段列表(每个字段对应一个键值对) |
| `TIL` | 列表型定义(存在时 `parts` 失效),每项表示列表支持的数据类型 |
#### 完整示例:配方定义(recipe/master.json)
以下是一个真实的配方主定义文件,展示了所有核心特性:
```json
{
"id": "mc_recipe_master",
"parts": [
{
"Key": "type",
"Type": ["S:配方类型¥minecraft/recipe_types/all"],
"MBW": true,
"Des": "配方类型标识,决定后续字段显示"
},
{
"Key": "category",
"Type": ["S"],
"Des": "配方在游戏配方书的分类(如misc/blocks)"
},
{
"Key": "pattern",
"Type": ["From:core/stringlist"],
"MBW": true,
"When": "type",
"WD": [
{ "type": { "id": "type", "Value": "minecraft:crafting_shaped" } }
],
"Des": "有序合成工作台图案(如[\"###\",\"#A#\",\"###\"])"
},
{
"Key": "key",
"Type": ["From:recipe/shaped_ingredients"],
"MBW": true,
"When": "type",
"WD": [
{ "type": { "id": "type", "Value": "minecraft:crafting_shaped" } }
],
"Des": "有序合成符号与物品的对应关系"
},
{
"Key": "ingredients",
"Type": ["From:recipe/shapeless_ingredients"],
"MBW": true,
"When": "type",
"WD": [
{ "type": { "id": "type", "Value": "minecraft:crafting_shapeless" } }
],
"Des": "无序合成材料列表"
},
{
"Key": "ingredient",
"Type": [
"S:单个物品ID¥items/all",
"From:core/itemslist:多个物品列表"
],
"MBW": true,
"When": "type1||type2",
"WD": [
{ "type1": { "id": "type", "Value": "minecraft:smelting" } },
{ "type2": { "id": "type", "Value": "minecraft:blasting" } }
],
"Des": "熔炉/高炉的烧炼原料"
},
{
"Key": "experience",
"Type": ["F"],
"MBW": true,
"When": "type1||type2",
"WD": [
{ "type1": { "id": "type", "Value": "minecraft:smelting" } },
{ "type2": { "id": "type", "Value": "minecraft:blasting" } }
],
"Des": "烧炼完成后玩家获得的经验值(如0.7)"
},
{
"Key": "result",
"Type": ["From:recipe/result"],
"MBW": true,
"Des": "所有配方的最终输出结果(物品ID+数量)"
}
]
}
```
**关键特性解析**:
1. **基础类型字段**:`type` 字段使用 `S:配方类型¥minecraft/recipe_types/all`
- `S` 表示字符串类型
- `配方类型` 是在选择弹窗标题中显示的描述
- `¥minecraft/recipe_types/all` 表示选择列表路径(指向 `data/choices/minecraft/recipe_types/all.json`)
2. **复合类型引用**:`pattern` 字段使用 `From:core/stringlist`
- `From:` 表示引用其他jsonTypes定义
- `core/stringlist` 引用 `data/jsonTypes/core/stringlist.json`
- 该文件定义了一个字符串列表类型
3. **条件显示机制**:`pattern` 字段只在有序合成时显示
- `When: "type"` 引用WD中定义的布尔变量
- `WD` 定义条件:当 `type` 字段的值为 `minecraft:crafting_shaped` 时显示
4. **多类型字段**:`ingredient` 字段支持两种类型
- 用户可以选择"单个物品ID"或"多个物品列表"
- 前端会弹出类型选择对话框
5. **多条件组合**:`ingredient` 字段的 `When: "type1||type2"`
- 使用 `||` 表示"或"逻辑
- 同时支持熔炉和高炉两种配方类型
#### parts[] 字段详解
| 字段 | 说明 | 示例 |
|------|------|------|
| `Key` | 导出 JSON 的键名 | `"Key": "type"` → 导出为 `{ "type": "..." }` |
| `Key: undefined` | 用户自定义键名 | `"Key": "undefined"` → 用户输入键名(如"A"、"B") |
| `Key: "*flatup"` | 编译时扁平展开引用 | `"Key": "*flatup"` → 同步时被替换为引用类型的所有 parts 字段 |
| `id` | 不定键名的ID标识 | `"id": "symbol"` → 存储 key 为 `undefined_symbol_A` |
| `Type` | 允许的类型列表 | `["S", "I"]` → 用户选择字符串或整数类型 |
| `MBW` | 是否必填 | `true` → 必填字段,空值会提示错误 |
| `When` | 条件显示正则 | `"type"` → 当WD中type为true时显示 |
| `WD` | 条件调色板 | 定义When中可用的布尔变量 |
| `Des` | 字段描述 | 显示给用户的说明文字 |
#### Type 取值格式
| 格式 | 含义 | 实际示例 |
|------|------|---------|
| `S` | 字符串(无描述) | `"Type": ["S"]` |
| `I` | 整数 | `"Type": ["I"]` |
| `F` | 浮点数 | `"Type": ["F"]` |
| `B` | 布尔值 | `"Type": ["B"]` |
| `S:描述` | 带描述的字符串 | `"S:配方类型"` → 选择时显示"配方类型" |
| `S:描述¥路径` | 带选择列表的字符串 | `"S:配方类型¥minecraft/recipe_types/all"` → 弹窗选择 |
| `From:path/id` | 引用复合类型 | `"From:core/stringlist"` → 引用列表定义 |
| `From:path/id:描述` | 带描述的引用 | `"From:core/itemslist:多个物品"` → 多类型时显示"多个物品" |
| `From:path/id#(条件)` | 带条件的引用 | `"From:recipe/master#(type=s)"` → 自动推断类型 |
| `From:*empty` | 存在性标记(不渲染UI) | `"Type": ["From:*empty"]` → 不显示编辑器,只要这个字段存在就生效,不管内容是什么 |
> **存在性标记(`From:*empty`)**:有些字段不需要用户编辑任何值,只要这个字段在 JSON 里以空对象 `{}` 形式存在就生效,不写就表示不生效(比如 `minecraft:glider`——写了 `"glider": {}` 就滑翔,不写就不滑翔)。软件用"一个描述为空、可选的对象字段"来实现这个概念。要让字段类型为对象,就得用 `From:` 引用一个目标。目标可以是:
> - `*empty` 关键词:`"Type": ["From:*empty"]`,告诉前端这是一个存在性标记,不渲染编辑界面,导出时生成 `{}`
> - 一个 `parts: []`(空数组)的 JSON 格式文件:引用它时前端不渲染容器
> - 一个 `parts` 里所有字段都因 When 条件不符合而被隐藏的 JSON 格式文件:运行时所有字段被挡掉,容器整体不显示
>
> 三种方式效果一样——字段存在就生成 `{}` 并生效,不写就跳过,前端不给用户编辑界面。
#### 列表型定义(TIL)
当JSON数据是列表而非对象时,使用 `TIL` 字段:
**示例:字符串列表(core/stringlist.json)**
```json
{
"id": "stringlist",
"TIL": ["S"]
}
```
- 定义一个字符串列表类型
- 前端渲染为可添加/删除的列表UI
- 用户点击"+添加"按钮输入字符串
**示例:物品列表(core/itemslist.json)**
```json
{
"id": "itemslist",
"TIL": ["S:物品ID¥items/all"]
}
```
- 定义一个物品ID列表类型
- 每项都从选择列表中选择物品ID
- `¥items/all` 指向 `data/choices/items/all.json`
#### 不定键名机制(undefined)
当字段的 `Key` 为 `undefined` 时,表示用户需要自定义键名:
**示例:有序合成符号映射(recipe/shaped_ingredients.json)**
```json
{
"id": "crafting_shaped_ingredients",
"parts": [
{
"Key": "undefined",
"id": "symbol",
"Type": [
"S:单个物品¥items/all",
"S:单个标签¥items/tagsall",
"From:core/itemslist:多选物品列表"
],
"MBW": true,
"Des": "有序合成符号对应的物品ID/多选物品列表"
}
]
}
```
**工作原理**:
1. 前端渲染时显示"添加符号"按钮
2. 用户点击后输入符号(如"A"、"B"、"#")
3. 然后选择该符号对应的物品/标签/物品列表
4. 存储格式:`"undefined_symbol_A": "minecraft:diamond"`
5. 导出时自动还原为 `"A": "minecraft:diamond"`
**用途**:适用于需要用户自定义键名的场景,如:
- 有序合成的符号映射(pattern中的符号)
- 自定义物品属性
- 条件判断的字段名
#### When/WD 条件显示机制
通过 `WD` 定义布尔变量,`When` 引用这些变量实现字段的条件显示:
**WD 定义语法**:
```json
"WD": [
{ "变量名": { "id": "字段路径", "Value": "匹配值" } }
]
```
**id 字段路径语法**(向上递归查找):
- `type` → 默认 `?0`,取当前层的 `type` 字段
- `type?1` → 向上1层取 `type` 字段
- `type?2` → 向上2层取 `type` 字段
- `0?1` → 向上1层到列表,取第[0]项
- `trigger?1` → 向上1层取 `trigger` 字段
**层级计数规则**:所有容器(对象 `{}` 和列表 `[]`)都算一层。
**When 正则表达式语法**:
- `type` → 单变量引用
- `type1||type2` → 或逻辑
- `type1&&type2` → 与逻辑
- `!type` → 非逻辑
- `(type1||type2)&&type3` → 括号分组
**实际示例**:配方中的烧炼字段
```json
{
"Key": "ingredient",
"When": "type1||type2", // 当type1或type2为true时显示
"WD": [
{ "type1": { "id": "type", "Value": "minecraft:smelting" } },
{ "type2": { "id": "type", "Value": "minecraft:blasting" } }
]
}
```
**执行逻辑**:
1. 前端监听 `type` 字段的值变化
2. 当值为 `minecraft:smelting` 时,`type1` 变为 `true`
3. 当值为 `minecraft:blasting` 时,`type2` 变为 `true`
4. `type1||type2` 为 `true` 时显示 `ingredient` 字段
#### 条件表达式(用于自动推断)
当 `Type` 列表中有多个 `From` 类型时,可以用条件表达式区分:
**语法**:
```
原子: (key) → key 字段存在
(key=type) → key 字段是特定类型(s/i/f/b/o/l)
逻辑: &&(与) ||(或) !(非) ()(分组)
```
**类型代码**:
- `s` = string(字符串)
- `i` = int(整数)
- `f` = float(浮点)
- `b` = bool(布尔)
- `o` = object/dict(对象)
- `l` = list/array(列表)
**示例**:
- `(type)` → type字段存在即可
- `(version=i)` → version字段是整数
- `(name&&!(fallback))` → name存在且fallback不存在
- `(data=o||items=l)` → data是对象或items是列表
**用途**:在导入ZIP转换为Workspace时,自动判断JSON数据应该匹配哪个类型定义。
#### 复合类型嵌套示例
**配方输出结果(recipe/result.json)**:
```json
{
"id": "mc_result",
"parts": [
{
"Key": "id",
"Type": ["S:物品ID¥items/all"],
"MBW": true,
"Des": "输出物品的命名空间ID"
},
{
"Key": "count",
"Type": ["I"],
"Des": "输出物品数量,默认值为1"
},
{
"Key": "components",
"Type": ["From:recipe/components/index"],
"Des": "物品组件数据(如附魔、自定义名称)"
}
]
}
```
**嵌套关系**:
- `recipe/master.json` 的 `result` 字段引用此类型
- `mc_result` 的 `components` 字段再引用 `recipe/components/index`
- 形成多层嵌套的复合类型结构
**前端渲染**:
- 用户填写 `result` 时,看到嵌套的字段编辑器
- `id` 字段:物品ID选择弹窗
- `count` 字段:整数输入框
- `components` 字段:展开显示更多子字段
#### 完整工作流程示例:创建钻石剑配方
以下展示从UI创建到导出的完整流程,演示数据驱动系统的实际应用:
**步骤1:用户在UI树中创建配方文件**
- 用户在左侧项目树中点击"原版数据包/配方"
- 点击"+添加"按钮
- 输入文件名:`diamond_sword`
- 前端根据 `paths/recipes.json` 的 `Type: "json:recipe/master"` 加载配方定义
**步骤2:前端加载配方定义**
- `typeRegistry.loadType("recipe/master")` 加载 `data/jsonTypes/recipe/master.json`
- 解析 `parts` 字段,渲染可视化编辑器
- 渲染第一个字段:`type`(配方类型)
**步骤3:用户选择配方类型**
- 用户点击 `type` 字段
- 前端加载 `data/choices/minecraft/recipe_types/all.json`
- 弹窗显示:有序合成、无序合成、熔炼、高炉冶炼...
- 用户选择:"有序合成"
- 实际写入:`"type": "minecraft:crafting_shaped"`
**步骤4:前端动态显示字段**
- 监听 `type` 字段变化,触发 `WD` 条件检查
- 检测到 `type` 的值为 `minecraft:crafting_shaped`
- WD中的 `type` 变量变为 `true`
- 显示 `pattern` 和 `key` 字段(When条件匹配)
**步骤5:用户填写pattern**
- `pattern` 字段引用 `From:core/stringlist`
- 加载 `data/jsonTypes/core/stringlist.json`(TIL类型)
- 前端渲染为列表编辑器,显示"+添加"按钮
- 用户添加3行:
- `###`
- `#D#`
- `###`
- 实际写入:`"pattern": ["###", "#D#", "###"]`
**步骤6:用户填写key(符号映射)**
- `key` 字段引用 `From:recipe/shaped_ingredients`
- 加载 `data/jsonTypes/recipe/shaped_ingredients.json`
- 该字段 `Key` 为 `undefined`,用户自定义键名
- 前端显示"添加符号"按钮
- 用户点击,输入符号:`#`
- 选择类型:`S:单个物品¥items/all`
- 弹窗加载物品列表,选择:"钻石"
- 实际写入:`"undefined_symbol_#": "minecraft:diamond"`
- 用户继续添加符号 `D`,选择物品"钻石"
- 实际写入:`"undefined_symbol_D": "minecraft:diamond"`
**步骤7:用户填写result**
- `result` 字段引用 `From:recipe/result`
- 加载 `data/jsonTypes/recipe/result.json`
- 嵌套显示 `id`、`count`、`components` 字段
- 用户选择物品:"钻石剑"
- 用户输入数量:1
- 实际写入:
```json
"result": {
"id": "minecraft:diamond_sword",
"count": 1
}
```
**步骤8:Workspace存储格式**
最终Workspace中存储的数据(包含复合标签):
```json
{
"_fileMeta": {
"uiPath": "原版数据包/配方",
"typeIndex": 0,
"filenameId": "diamond_sword"
},
"_d": {
"type": { "_t": 0, "_d": "minecraft:crafting_shaped" },
"pattern": {
"_t": 0,
"_d": [
{ "_t": 0, "_d": "###" },
{ "_t": 0, "_d": "#D#" },
{ "_t": 0, "_d": "###" }
]
},
"undefined_symbol_#": { "_t": 0, "_d": "minecraft:diamond" },
"undefined_symbol_D": { "_t": 0, "_d": "minecraft:diamond" },
"result": {
"_t": 0,
"_d": {
"id": { "_t": 0, "_d": "minecraft:diamond_sword" },
"count": { "_t": 0, "_d": 1 }
}
}
}
}
```
**步骤9:导出ZIP**
- 用户点击"导出ZIP"
- 前端遍历所有Workspace数据
- 对每个配方文件:
- 移除 `_t` 和 `_d` 包装,还原纯JSON
- 还原 `undefined_symbol_X` 为 `X`
- 根据命名空间和 `paths/recipes.json` 的 `save_path` 计算路径
- 最终生成ZIP文件:
```
datapack.zip
└── datapack/
└── data/
└── mymod/
└── recipe/
└── diamond_sword.json
```
**最终导出的diamond_sword.json**:
```json
{
"type": "minecraft:crafting_shaped",
"pattern": ["###", "#D#", "###"],
"key": {
"#": "minecraft:diamond",
"D": "minecraft:diamond"
},
"result": {
"id": "minecraft:diamond_sword",
"count": 1
}
}
```
这个完整示例展示了数据驱动系统如何:
- 通过 `jsonTypes` 定义字段结构和验证规则
- 通过 `choices` 提供友好的选择界面
- 通过 `WD/When` 实现动态字段显示
- 通过 `From` 实现类型引用和嵌套
- 通过 `undefined` 实现用户自定义键名
- 最终导出符合Minecraft标准的JSON文件
### choices/\*.json(选择列表)
位于 `data/choices/`,为 `S:描述¥路径` 类型提供下拉选项。支持三种选项格式,提供友好的用户选择界面。
#### 选项格式
choices文件支持三种不同的选项格式,根据字段类型自动识别:
**1. 普通选项**:用户选择后写入 `RN` 值
```json
{
"RN": "minecraft:crafting_shaped",
"SN": "有序合成"
}
```
**2. 带图片选项**:显示物品/方块图标
```json
{
"RN": "minecraft:apple",
"SN": {
"text": "苹果",
"img": "textures/item/apple"
}
}
```
**3. 子菜单选项**:跳转到下级分类菜单
```json
{
"to": "items/normal",
"SN": "普通物品"
}
```
| 字段 | 说明 |
|------|------|
| `RN` | 实际写入JSON的真实值(Real Name) |
| `SN` | 选择弹窗显示的友好名称 |
| `SN.text` | 带图片选项的显示文本 |
| `SN.img` | 图片路径(相对于 `data/assets/`,不带 `.png` 后缀) |
| `to` | 子菜单跳转路径(指向另一个choices文件) |
#### 完整示例:配方类型选择列表
**文件路径**:`data/choices/minecraft/recipe_types/all.json`
```json
[
{
"SN": "有序合成",
"RN": "minecraft:crafting_shaped"
},
{
"SN": "无序合成",
"RN": "minecraft:crafting_shapeless"
},
{
"SN": "熔炼",
"RN": "minecraft:smelting"
},
{
"SN": "高炉冶炼",
"RN": "minecraft:blasting"
}
]
```
#### 完整示例:物品分类选择(带子菜单)
**文件路径**:`data/choices/items/all.json`
```json
[
{
"to": "items/normal",
"SN": "普通物品"
},
{
"to": "items/blocks",
"SN": "方块物品"
}
]
```
用户选择"普通物品"后,弹窗跳转到 `items/normal.json`,显示具体的物品列表。
#### 完整示例:物品选择列表(带图片)
**文件路径**:`data/choices/items/normal.json`
```json
[
{
"RN": "minecraft:music_disc_precipice.desc",
"SN": {
"text": "Aaron Cherof - Precipice",
"img": "textures/item/music_disc_precipite.desc"
}
},
{
"RN": "minecraft:apple",
"SN": {
"text": "苹果",
"img": "textures/item/apple"
}
}
]
```
#### 前端交互流程
**普通选项流程**:
1. 用户在配方编辑器中点击"配方类型"字段
2. 前端根据 `¥minecraft/recipe_types/all` 路径加载此JSON文件
3. 弹窗显示选项列表:"有序合成"、"无序合成"、"熔炼"等
4. 用户选择"有序合成"
5. 实际写入JSON:`"type": "minecraft:crafting_shaped"`
**子菜单流程**:
1. 用户点击"物品"字段,加载 `¥items/all`
2. 弹窗显示分类:"普通物品"、"方块物品"、"工具"等
3. 用户选择"普通物品"
4. 弹窗跳转到 `items/normal.json`,显示具体物品列表
5. 用户选择"苹果"
6. 实际写入JSON:`"id": "minecraft:apple"`
**带图片流程**:
1. 用户选择物品时,弹窗显示图标+名称
2. 图片从 `data/assets/textures/item/apple.png` 加载
3. 如果图片加载失败,退化为纯文字显示
4. 用户看到图标和名称,更容易识别物品
#### 常用选择列表路径
| 路径 | 内容 | 格式 |
|------|------|------|
| `items/all` | 物品分类 | 子菜单(跳转到子目录) |
| `items/normal` | 普通物品列表 | 带图片选项 |
| `items/blocks` | 方块物品列表 | 带图片选项 |
| `items/tagsall` | 物品标签列表 | 普通选项 |
| `blocks/all` | 方块分类 | 子菜单 |
| `minecraft/recipe_types/all` | 配方类型 | 普通选项 |
| `minecraft/enchantments/all` | 附魔类型 | 带图片选项 |
#### 组织结构
选择列表按照逻辑分类组织在子目录中:
```
data/choices/
├── items/ # 物品相关
│ ├── all.json # 物品分类(子菜单)
│ ├── normal.json # 普通物品列表(带图片)
│ ├── blocks.json # 方块物品列表(带图片)
│ └── tagsall.json # 物品标签列表(普通)
├── blocks/ # 方块相关
│ ├── all.json # 方块分类(子菜单)
│ └── normal.json # 普通方块列表(带图片)
├── minecraft/ # Minecraft官方类型
│ ├── recipe_types/ # 配方类型
│ │ └── all.json # 普通选项
│ └── enchantments/ # 附魔类型
│ └── all.json # 带图片选项
├── datapackmore/ # DatapackMore扩展类型
│ ├── item_types/
│ │ └── all.json # 自定义物品类型
│ └── block_model_parent.json # 方块模型父类
└── core/ # 核心类型
├── click_event_actions.json # 点击事件类型
└── text_colors.json # 文本颜色
```
### commandTypes/\*.xml(命令语法)
位于 `data/commandTypes/`,定义 Minecraft 命令的语法树,用于命令文本编辑器的语法补全和高亮,以及图形化编辑器积木渲染。
**支持换行**:template 字段可以换行书写,解析时自动去除换行和缩进。
```xml
execute [
as |
at |
in |
if (block |entity )|
unless (block |entity )
@...]
[run ]
0x38a112
0x55FF55
```
**命令树语法:**
| 语法 | 含义 |
|------|------|
| `字面量` | 按原样输入,可作为 Tab 补全候选 |
| `字面量#中文#` | 带中文描述,图形化编辑器显示中文 |
| `<参数>` | 用户输入值替换,不可 Tab 补全(仅提示) |
| `<参数#中文#>` | 带中文描述,图形化编辑器显示中文 |
| `[项]` | 可选 |
| `(A\|B\|C)` | 必选其一 |
| `[A\|B\|C]` | 可选其一 |
| `[A\|B@...]` | 无限重复:可选其一,可重复任意次数 |
| `(A\|B@...)` | 无限重复:必选其一,可重复任意次数 |
| 空格 | token 分隔符 |
**color 节点:**
- `literal`:字面量颜色(`0xRRGGBB` 或 `#RRGGBB`)
- `argument`:参数颜色
**@... 无限重复示例:**
- `/execute` 命令的 `as/at/in/if/unless` 可以重复任意次,用 `[as |at |in @...]` 表示
- 文本编辑器会根据用户输入判断是否还在重复项中,直到输入 `run` 或其他命令
- 图形化编辑器会显示"+ 添加"按钮,允许用户添加多个重复项
**运行原理:**
1. `CommandTypesRegistry` 通过 `manifest.json` 或默认列表加载所有 XML
2. `CommandTreeParser` 用递归下降解析器将 template 解析为语法树(sequence/choice/optional/argument/literal/repeatable 节点)
3. `CommandMatcher.getCandidates(tree, tokens)` 基于已输入 token 序列返回当前候选,支持 repeatable 节点循环匹配
4. 编辑器实时匹配并弹出补全列表,Tab 补全字面量,参数仅提示
5. 图形化编辑器 `BlocklyEditor` 将语法树渲染为积木块,repeatable 节点显示为可添加列表
---
## 工作区格式(Workspace V2)
项目以专用文件格式保存,便于识别和管理:
**文件类型**:
- **项目文件**:`.dpmtproject` 后缀,存储是成品zip
- **工作区文件**:`.dpmtworkfile` 后缀,是供工具读取的json,有每项的类型等数据,加载更快更稳定
**文件命名规则**:
- 项目文件:`{项目名}.dpmtproject`
- 工作区文件:`{项目名}_workspace.dpmtworkfile`
格式详见 `WORKSPACE_V2.md`,核心结构:
```json
{
"version": "2.0",
"metadata": {
"exportTime": "2026-05-10T15:00:00Z",
"datapackMoreVersion": "0.2.0",
"projectName": "我的数据包"
},
"namespaces": {
"mymod": {
"_datadriven": {
"原版数据包/配方": [
{
"_fileMeta": { "uiPath": "原版数据包/配方", "typeIndex": 0, "filenameId": "cake" },
"_d": {
"type": { "_t": 0, "_d": "minecraft:crafting_shaped" },
"pattern": { "_t": 0, "_d": ["###", "#E#", "###"] }
}
}
]
}
}
}
}
```
**复合标签**:所有值统一为 `{"_t": typeIndex, "_d": actualData}` 格式
- `_t`:在 `Type` 数组中的索引(单类型字段固定为 0)
- `_d`:实际数据值
- 嵌套对象/列表的每个元素也都是复合标签
**不定键名**:用户自定义键存储为 `undefined_{partId}_{userKey}`,导入时逆向还原。
**文件级多类型**:通过 `_fileMeta.typeIndex` 记录该文件在 `Types[]` 中选择的类型索引。
---
## 导出原理
DPMT支持三种导出方式,分别用于不同的使用场景:
### 1. 导出数据包ZIP(用于Minecraft)
导出完全在前端完成(`main.js: _generateZipBlob`),无需后端往返:
1. 创建 `JSZip` 实例
2. 根据 `tool_data.export_root` 创建根目录(如 `datapack/`),所有文件加到此目录下
3. 遍历项目的每个命名空间:
- **传统类型**(`tool_data.types`):用 `base_path` 模板替换 `{namespace}` 生成路径,模板内容替换 `{id}` 后写入
- **数据驱动类型**(`_datadriven`):
- `mcfunction` → 原文写入 `save_path/filename.mcfunction`
- `png` → base64 解码后写入 `save_path/filename.png`
- `json:path` → 加载 `data/jsonTypes/path.json` 格式定义,递归生成 JSON 内容(解开复合标签,解析 `From:` 引用,处理 `undefined_` 不定键名),写入 `save_path/filename.json`
4. `zip.generateAsync({ type: 'blob', compression: 'DEFLATE' })` 生成 Blob
5. 通过 `/api/save-dialog` 让用户选择保存路径,后端写入磁盘
6. `lastPaths.exportZip` 记录上次路径
**导出 ZIP 结构示例**(`export_root = "datapack"`):
```
datapack/ ← export_root 包裹层
├── datapack/
│ ├── data/
│ │ └── mymod/
│ │ ├── recipe/cake.json
│ │ ├── function/main.mcfunction
│ │ └── advancement/root.json
│ └── pack.mcmeta
└── resourcepack/
└── assets/
└── mymod/
```
### 2. 导出工作区文件(用于保存项目)
导出工作区文件用于保存整个项目,方便后续继续编辑:
**文件格式**:`.dpmtworkfile`
**命名规则**:`{项目名}_workspace.dpmtworkfile`
**导出流程**(`main.js: _exportProject`):
1. 收集当前项目的所有Workspace数据(包含 `_t` `_d` 复合标签)
2. 添加元数据(导出时间、项目名、版本等)
3. 生成JSON格式的工作区数据
4. 通过 `/api/save-dialog` 让用户选择保存路径
5. 前端生成 `Blob` 并触发下载
6. 文件保存为 `{项目名}_workspace.dpmtworkfile`
**用途**:
- 保存完整的项目状态
- 跨设备分享项目
- 后续导入继续编辑
### 3. 导出项目文件(轻量级保存)
导出项目文件仅存储基本信息,不含实际文件内容:
**文件格式**:`.dpmtproject`
**命名规则**:`{项目名}.dpmtproject`
**用途**:
- 快速创建项目引用
- 项目模板分享
- 轻量级项目记录
---
## 导入原理
DPMT支持三种导入方式,分别对应不同的数据来源:
### 1. 导入工作区文件(`.dpmtworkfile`)
导入工作区文件用于恢复之前保存的完整项目状态:
**文件格式识别**:前端通过 `` 限制只能选择工作区文件
**导入流程**(`main.js: _importProject`):
1. 用户选择 `.dpmtworkfile` 文件
2. 前端读取文件内容(JSON格式的Workspace数据)
3. 解析 `metadata`、`namespaces` 等字段
4. 将数据直接加载到当前项目中
5. 恢复所有文件的状态(包含 `_t` `_d` 复合标签)
6. 渲染项目树和编辑器
**用途**:
- 恢复之前保存的项目
- 导入他人分享的项目
- 跨设备同步项目
### 2. 导入项目文件(`.dpmtproject`)
导入项目文件仅恢复基本信息,不含实际文件内容:
**文件格式**:`.dpmtproject`
**导入流程**:
1. 用户选择 `.dpmtproject` 文件
2. 前端读取项目基本信息(名称、创建时间等)
3. 创建空项目或加载项目框架
4. 需要后续添加具体文件内容
**用途**:
- 快速创建项目模板
- 项目引用恢复
### 3. 导入数据包ZIP(用于从现有数据包创建项目)
将现有Minecraft数据包ZIP转换为Workspace项目:
**导入流程**(`/api/import-zip`):
1. 前端 FormData 上传 ZIP 到后端
2. 后端 `_parse_multipart_zip` 手动解析 multipart/form-data(**不依赖已移除的 `cgi` 模块**),提取 `zipfile` 字段
3. `ZipToWorkspaceConverter.convert_zip_to_workspace`:
- 解压到临时目录
- 递归遍历 `.json`/`.mcmeta` 文件
- `_normalize_zip_path` 规范化路径(处理 `datapack/datapack/` 嵌套前缀,兼容 `data/` 无前缀)
- `_match_ui_definition` 用 `save_path` 模板构建正则反推 UI 定义和命名空间
- 多候选时根据 JSON 内容(对象/列表、字段条件)区分
- `_reverse_convert` 将原版 JSON 逆向转换为 Workspace 复合标签格式
4. 返回 Workspace JSON,前端预览后可下载或直接载入
**用途**:
- 从现有数据包创建可视化编辑项目
- 批量转换原版JSON文件
- 逆向工程现有数据包结构
### 项目 ZIP 提取(前端 JSZip)
`doExtractDatapackZip` / `doExtractResourcepackZip` 在前端用 JSZip 处理:
- 识别 `datapack/datapack/` 前缀(即 `export_root` 包裹层),提取内层
- 重新打包为只含 `datapack/` 或 `resourcepack/` 的 ZIP,供直接放入游戏目录
---
## API 接口列表
### GET
| 路径 | 说明 |
|------|------|
| `/api/tool-data` | 工具配置 |
| `/api/datadriven-config` | 合并的数据驱动配置(ui + paths) |
| `/api/datadriven-ui` | UI 配置(向后兼容) |
| `/api/datadriven-paths` | 路径配置(向后兼容) |
| `/api/commands` | 命令定义(旧格式) |
| `/api/versions` | 可用数据格式版本列表 |
| `/api/current-version` | 当前选中版本 |
| `/api/game-versions` | 扫描到的游戏版本 |
| `/api/saves?version=...` | 指定游戏的存档列表 |
| `/api/datapacks?save=...` | 指定存档的 datapacks 列表 |
| `/api/list-dirs?path=...&include_files=0/1` | 目录浏览(前端文件浏览器用) |
| `/api/list-mods` | mods 目录下的 JAR 列表 |
| `/api/export-mod-status` | 导出 Mod 功能是否可用 |
| `/api/languages` | 可用语言列表 |
| `/api/languages/current` | 当前语言包数据 |
| `/api/settings/{filename}` | 读取设置文件 |
| `/data/{path}` | 静态访问 `data/` 目录下文件 |
| `/api/launch?...` | 启动游戏 |
| `/api/launch-progress?...` | 启动进度查询 |
| `/api/debug-logs?...` | 调试日志 SSE 流 |
| `/api/startup-reminders` | 获取启动提醒设置 |
| `/api/extract-textures` | 提取Minecraft版本纹理(POST) |
### POST
| 路径 | 说明 |
|------|------|
| `/api/import` | 导入空项目模板 |
| `/api/import-zip` | 上传 ZIP 转 Workspace |
| `/api/import-datapack` | 导入 datapack ZIP 到存档 |
| `/api/import-resourcepack` | 导入资源包到游戏 |
| `/api/select-version` | 选择数据格式版本 |
| `/api/force-update` | 强制重新同步版本数据 |
| `/api/datapack/delete` | 删除存档中的 datapack |
| `/api/export-mod-jar` | 导出 Mod JAR |
| `/api/add-mod` | 添加 JAR 到 mods 目录 |
| `/api/delete-mod` | 删除 mods 中的 JAR |
| `/api/save-dialog` | 保存文件到前端选定路径 |
| `/api/tool-data/last-path` | 更新某弹窗的上次路径 |
| `/api/install-version-package` | 上传 ZIP 安装版本包 |
| `/api/delete-version` | 删除版本 |
| `/api/set-launch-mode` | 设置启动模式(in_app) |
| `/api/set-shortcuts` | 保存图片编辑器快捷键 |
| `/api/log/frontend` | 前端日志上报 |
| `/api/language` | 设置语言 |
| `/api/language/refresh` | 刷新语言包 |
| `/api/settings/{filename}` | 保存设置文件 |
| `/api/startup-reminders` | 更新启动提醒设置 |
| `/api/extract-textures` | 提取Minecraft版本纹理 |
---
## 配置文件汇总
| 文件/目录 | 作用 | 可手改 |
|-----------|------|--------|
| `userdata/tool_data.json` | 工具全局配置 | 是 |
| `userdata/settings/version.json` | 选中的数据格式版本 ID | 通过 UI 修改 |
| `userdata/settings/language.json` | 选中的语言 | 通过 UI 修改 |
| `version/{id}/pack.dpmtmeta` | 版本元数据 | 是 |
| `version/{id}/data/` | 版本的数据定义源 | 是(开发时编辑这里) |
| `data/` | 运行时数据(自动同步) | 否(会被覆盖) |
| `data/tempid.json` | 当前版本 ID 缓存 | 否 |
| `frontend/js/lib/jszip-LICENSE.txt` | JSZip 开源协议 | 需随库保留 |
---
## 许可证
本项目使用 JSZip(MIT/GPLv3 双协议),其 LICENSE 文件保留于 `frontend/js/lib/jszip-LICENSE.txt`。
## 依赖库
- **pywebview** — 应用窗口模式(`in_app: true` 时启用),用于创建桌面窗口加载前端
- **标准库** — HTTP 服务器 (`http.server` + `socketserver`)、日志、ZIP 处理、PNG 解析等均使用 Python 标准库,无第三方依赖
> 注意:启动画面使用 ctypes + struct + zlib 纯标准库实现,不依赖 PIL/tkinter。