最后更新于 2022/04/23
§1 引言
原版模组指在不修改 Minecraft 游戏本体的前提下,通过命令方块、一键命令、数据包、资源包等方式对游戏的可玩性做出修改。之所以称之为原版模组,乃是为了和使用 Mod Loader/API 环境基于 Java 开发的 Mod 作区分,这些 Mod Loader/API 包括 Forge,Liteloader,Fabric 等。自 Minecraft Java 版 1.13 起的数据包概念问世之后,原版模组的制作已变得十分便捷。本文内的原版模组便是指利用数据包和可能的配套资源包对游戏的可玩性做出修改。
本文内容适用于 Minecraft Java 版 1.19 版本,但大部分内容也适合 1.13-1.18 版本,或只需要做简单修改,请读者自行区分。系统环境为 Windows 10,其它环境下有较小的差异。本文参考和吸取了大量其他玩家的意见、建议和教程等,无法一一列出,在此一并表示感谢。
本文更侧重教程而非开发手册,因此很多内容的完整格式并没有列出,此时请读者自行查阅相关的中文 Minecraft Wiki 链接。由于英文 Minecraft Wiki 往往更新更及时且内容更准确,因此有英文阅读能力的可直接查看英文 wiki 的相关内容。
本文中青铜色楷体
表示其应当被替换为合适的字符串,绿色楷体
表示JSON文件中的注释,点击左下角显/隐可切换显示或隐藏注释。
§1.1 命令基础
本文不会介绍命令的基础知识,相关内容请读者通过如下链接自行了解。
- 命令 此为官方 wiki 的命令页面,其中包括了命令的基本参数介绍:坐标、目标选择器、数据标签 (NBT) 、原始JSON文本、命名空间ID等内容。读者需在学习命令过程中逐步了解这些概念。
- 记分板 此为官方 wiki 的记分板页面,其中包括了记分板的所有准则和相关命令格式,也包含了标签 (tag) 和组 (team) 的相关内容。
- 实体格式和player.dat格式
这两个页面给出了所有物品和实体(含方块实体)的 NBT。在游戏中,实体的 NBT 可通过命令
data get entity/block
来获取,物品的 NBT 可通过手持并输入命令data get entity @s SelectedItem
来获取,以避免记忆大量的 NBT。 - 新人手册-MC命令方块资源 该网站包含了很多基础的教程,不过很多内容会有些过时。还包含少量进阶的内容,对于初学者可能较为复杂,请以理解其逻辑为主。
- 命令进阶 该网站虽名为命令进阶,实际上比前一项页面更为初等,不过更为系统。
若你已对 1.18 之前版本的命令较为熟悉,可在Java版版本记录中查看各版本间差异。
§1.2 工具准备
§1.2.1 文本编辑器
原版模组涉及的文本文件,包括文本文档(.txt)、函数文件(.mcfunction)、JSON文件(.json, .mcmeta)、顶点着色器(.vsh)和片段着色器(.fsh),均需使用 UTF-8
编码格式。注意不要误选了 UTF-8 with BOM
编码格式。文本编辑器多如瀚海,读者可自行选择一种。我仅列出本人常用的两个文本编辑器。
- Visual Studio Code 下载安装后,可以安装插件 Chinese (Simplified) Language Pack for Visual Studio Code 以支持中文语言,以及Data-pack Helper Plus 用于数据包各项内容的语法补全和纠错。
- 记事本为 Windows 系统自带的极简编辑器。Windows10 的记事本已经默认是
UTF-8
编码了。点击查看->状态栏
可以在右下角状态栏看到,因此可以正常使用。旧版本 Windows 的记事本仍然不是,请勿使用。
编辑器右下角可以看到 LF
或 CRLF
,分别表示两种换行符,二者均可正常使用,建议使用 LF
。
为便于查看文件后缀,请将文件->文件夹选项->查看->隐藏已知文件类型的扩展名
去掉勾选。我们可以在编辑器中新建文本文件,或者在文件夹中右键->新建->文本文档
,除 txt
以外的文本文件可通过修改文件后缀得到。
§1.2.2 压缩软件
数据包和资源包均可以为文件夹或 zip 文件,zip 文件需要压缩软件来打开和制作。游戏本体和模组本体的 jar 文件也需要使用压缩软件来打开。常见的压缩软件有:
- 7-Zip 是一款免费的压缩软件。jar 文件可以通过
右键->7-zip->打开压缩包
来打开。 - WinRAR 是一款付费的压缩软件。
- Windows 资源管理器是 Windows10 系统自带的软件。它可以使得 zip 文件如同文件夹般直接打开,压缩则用
选择->右键->发送到->压缩文件夹
,但它的压缩速度十分缓慢。
§1.2.3 NBT 编辑器
我们可以使用 NBTStudio 来打开 dat 文件和其它 NBT 格式文件。
§1.2.4 绘图工具
我们可以使用 Adobe Photoshop 或其它绘图工具来绘制纹理。Windows 自带的画图由于无法生成透明背景,因此不建议使用。
§1.3 游戏文件夹
本节中我们将对游戏文件夹的结构做简单的介绍,我们只介绍原版模组开发中涉及的内容。.minecraft
文件夹是 Minecraft 创建的并用于游戏运行的文件夹,它包含了游戏的所有内容。在第一次启动启动器时,会自动创建 .minecraft
文件夹。参考 .minecraft。
.minecraft
文件夹通常位于你的启动器目录下。如果启动器中设置为各版本独立,则位于 versions/版本号
下。另一种方式是在游戏内点击选项->资源包->打开压缩包文件夹
并返回上级目录,或者点击单人游戏->选中世界->编辑->打开世界文件夹
并返回上上级目录。
游戏本体位于 versions/版本号/版本号.jar
。该文件包含了对应版本的游戏资源和数据文件,使用压缩软件打开后,可以看到
- 原版资源包位于
assets
文件夹内,其中minecraft
文件夹为命名空间minecraft
下的资源文件。 - 原版数据包位于
data
文件夹内,其中minecraft
文件夹为命名空间minecraft
下的数据文件。 pack.mcmeta
为原版资源包和数据包的元信息,1.17版本起不再包含该文件。pack.png
为原版资源包和数据包的图标。
资源包文件夹位于 resourcepacks
,其下方子文件夹或 zip
文件即为一个资源包,具体结构见资源包。服务器下载的资源包位于 server-resource-packs/服务器
,可使用压缩软件打开。
资源文件 部分资源文件不被包含在原版资源包内,而是位于资源文件夹下,这主要包括各种语言文本和音效文件。资源文件索引位于 assets/indexes/版本号.json
,打开后通过键值可知相应资源的 hash 值,对应的资源位于 assets/objects/hash前2位/hash
。例如打开 assets/indexes/1.17.json
,找到键 minecraft/lang/zh_cn.json
的 hash
为 8fb4f6725d8317a37e7f823ff424e66a46b9ef75
,因此简体中文的语言文本位于文件夹 assets/objects/8f/8fb4f6725d8317a37e7f823ff424e66a46b9ef75
,使用文本编辑器打开即可看到游戏内的所有名称的中文译名。注意该文件中的中文均被转化成了相应的 Unicode 表达方式,参考字体。
日志位于 logs/latest.log
,可由此实时查看游戏运行中的各种反馈。对于我们而言,它可以在加载资源包和数据包时告诉我们它们是否有错误以及错误信息,包括错误的文件名称、位置、错误的行列数等,因此这对于我们调试非常重要。简体中文下需要设置文件编码为 gbk
,否则除 ASCII 外的字符会显示乱码。历史日志位于 logs/年-月-日-序号.log.gz
,使用压缩软件打开后使用文本编辑器打开即可查看。崩溃报告位于 crash-reports/crash-年-月-日_时.分.秒-server.txt
,如果是由于资源包或数据包引起的崩溃,可以在该文件中看到原因。
存储的物品栏位于 hotbar.nbt
,存储了游戏内使用 C+数字
存储、X+数字
取出的创造模式物品快捷栏。
存档文件位于 saves/世界名称
,由于世界格式上该内容已较为详尽且与原版模组联系甚远,因此我们仅提及部分内容。该文件夹包含的区块文件、地图文件等内容虽然也可以使用 NBT 编辑器来编辑,但较为不便,我们建议使用 MCEdit、地图文件生成工具等专门的工具来编辑。存档的备份文件位于 backups/年-月-日_时.分.秒_世界名称.zip
,为存档的备份文件,解压后复制到 saves
即可使用。
- 进度位于
advancements/玩家UUID.json
,记录了玩家已完成的进度和进度判据的完成时间。对于需要完成多个判据才能达成的进度,该文件中记录了已完成的那些判据的完成时间。 - 区块、实体、实体兴趣点、袭击等信息分维度存储,主世界、下界、末地、自定义维度的相应文件分别位于根文件夹、
DIM-1
、DIM1
、dimensions/命名空间/路径
下。 - 世界信息文件
level.dat
中WorldGenSettings
包含了世界生成时的所有维度的生成信息,玩家可以在创建新的世界->更多世界的选项->导入设置
使用JSON文件导入,格式见自定义世界。之后还会加入数据包中的自定义维度。 - 数据包文件夹位于
datapacks
,其下方子文件夹或zip
文件即为一个数据包,具体结构见数据包。 - 结构位于
generated/命名空间/structures/文件.nbt
,其记录了游戏内使用结构方块保存的结构,将其移动至数据包内方可使用。
§1.4 JSON文件
参考JSON。数据包的进度、战利品表、战利品表谓词、物品修饰器、配方、标签、维度、维度类型、自定义世界生成,资源包的语言文件、模型、音效、字体、credits.json
等文件均为JSON文件。.mcmeta
文件也是JSON文件,因此格式也是相同的。
JSON文件中用于分割的空格、制表符(Tab)、回车和换行符都是可去的,它们仅用于提高可读性。编写时,使用空格或制表符缩进,以便于查看括号匹配和层次。JSON文件通常包含用于封装文件数据的一对大括号{}
,即它是一个JSON对象,但战利品表谓词和物品修饰器文件的根数据类型还可以为JSON数组。它包含类似 "abc": "def"
这样的 "键": 值
对,一般使用单引号也可以。同一个文件中如果允许有相同的键,则后者会覆盖前者。
JSON数据类型有下述几种。Wiki 上有关页面使用了 NBT 的数据类型标注,但其实并不适用于JSON文件,我们应当将其视为相应内容的可取值范围。
布尔型,值为 true
和 false
。
{ "is_on_fire": false, "is_baby": true, "noise_caves_enabled": false }
数值,值为任何数字。2
和 2.0
没有差异。
{ "count": 2.0, "chance": 0.025, "salt": 14357619 }
字符串,值使用双引号/单引号圈住,可以使用颜色代码如 §6
、换行符 \n
。
{ "condition": "minecraft:random_chance", "tag": "minecraft:wools", "layer0": "cpp:crop/bauhinia_seeds" }
数组,值使用中括号圈住。
{ "scale": [ 0.901, 0.901, 0.901 ], "items": [ "minecraft:zombie_head", "minecraft:skeleton_skull", "minecraft:wither_skeleton_skull", "minecraft:creeper_head" ], "requirements": [ [ "wing_of_sky", "heart_of_crystal", "nova_of_fire" ] ], "terms":[ { "condition": "minecraft:location_check", "predicate": { "biome": "minecraft:snowy_taiga" } }, { "condition": "minecraft:location_check", "predicate": { "biome": "minecraft:snowy_taiga_mountains" } } ] }
对象,使用大括号圈住。整个JSON文件内容本身就是一个JSON对象。
{ "count": { "type": "minecraft:uniform", "min": 1.0, "max": 2.0 }, "modifiers": [ { "name": "legs_armor", "attribute": "generic.armor", "operation": "addition", "amount": 4, "slot": "legs" } ] }
JSON中没有注释的语法,但可以使用不被使用的键来表示注释。通常使用 "_comment", "_comment1", "_comment2"
这种键。
{
"pools": [
{
"rolls": 1,
"entries": [
{
"_comment": "咸味粽子",
"type": "minecraft:item",
"name": "minecraft:cooked_cod",
"functions": [
{
"function": "minecraft:set_nbt",
"tag": "{display:{Name:'{\"italic\":false,\"translate\":\"item.cpp.zongzi_with_salt\"}',Lore:['{\"italic\":false,\"translate\":\"item.cpp.zongzi_with_salt\"}']},id:'cpp:zongzi_with_salt',CustomModelData:12970027}"
}
]
}
]
}
]
}
我们常常在JSON中用整数来表示RGB格式颜色,对应值为: 65536×R+256×G+B
。
§2 数据包
数据包 (datapacks) 可用于覆盖或添加新的函数、进度、战利品表、战利品表谓词、物品修饰器、配方、结构、标签、维度、维度类型和自定义世界生成。数据包文件夹位于 .minecraft/saves/世界名称/datapacks
,其下方子文件夹或 zip
文件即为一个数据包。
安装数据包:单人游戏中,对于新创建的世界,在创建时点击创建新的世界->世界预设->数据包
,将数据包拖入并选择至右侧即可。对于已生成的世界,选择你需要安装数据包的世界,点击编辑->打开世界文件夹->打开文件夹 datapacks
,并将数据包 .zip
文件或文件夹放入其中。多人游戏中,打开服务器文件夹->worlds->datapacks
文件夹,并将数据包 .zip
文件或文件夹放入其中。数据包放置好后,在首次生成该世界/重新进入该世界/下次服务器启动时会启用该数据包。管理员也可以输入 /reload
来加载数据包,但这不适用维度和自定义世界生成。
原版数据包是 Minecraft 自带的一个默认开启的数据包,其 data
文件夹位于 .minecraft/versions/版本号/版本号.jar/data
,其中包含的原版进度、战利品表、配方、结构和标签等内容,是制作数据包的文件样板。注意原版的维度和自定义世界生成不在该路径,可从此处下载这些内容。
对于创建世界时添加的数据包,可以在数据包选择页面调整加载次序。其它情形添加的数据包优先级最高,即最后加载。同时加入的数据包优先级则和文件名顺序相关。数据包的优先级顺序储存在文件 level.dat
中,可以通过 /datapack
命令查看或修改。在游戏中通过命令 /datapack
可以禁用/启用数据包,最后启用的优先级最高。由于数据包加载顺序不定,因此在制作数据包的过程中,尽量不要依赖数据包的加载顺序。原版数据包的优先级一般是最低的。
使用 /datapack list
命令显示数据包名称时,原版数据包为 [vanilla]
,玩家自定义的数据包为 [file/数据包名称]
。
文件夹结构
datapacks/数据包名称或数据包名称.zip/
pack.mcmeta pack.png data 命名空间 advancements 进度名称.json functions 函数名称.mcfunction loot_tables 战利品表名称.json predicates 战利品表谓词名称.json item_modifiers 物品修饰器名称.json structures 结构名称.nbt recipes 配方名称.json tags functions 函数标签名称.json blocks 方块标签名称.json items 物品标签名称.json entity_types 实体类型标签名称.json fluids 流体标签名称.json game_events 游戏事件标签名称.json dimension 维度名称.json dimension_type 维度类型名称.json worldgen noise_settings 噪声设置名称.json biome 生物群系名称.json configured_carver 地形雕刻器名称.json configured_surface_builder 地表生成器名称.json configured_feature 地物名称.json configured_structure_feature 结构地物名称.json template_pool 模板池名称.json processor_list 处理器列表名称.json
由于命名空间ID的命名要求,data
下所有文件和文件夹可使用的字符为 _-.abcdefghijklmnopqrstuvwxyz0123456789
,不可使用大写字母、空格或中文,所有文本文件使用 UTF-8
编码。为了便于传播,发布时可将所有内容压缩为一个 zip
文件。压缩和解压的时候,注意文件层次,应当打开 zip
文件就可以看到数据包的 pack.mcmeta
。稳妥的做法是 打开文件夹->全选->右键->发送到->压缩文件夹
或 打开文件夹->全选->7-Zip->添加到 "文件夹名.zip"
。文件夹格式的数据包若有错误文件仍可以加载,但 zip
格式的会无法加载。
§2.1 元信息和图标
Minecraft 通过文件 pack.mcmeta
来识别数据包,因此该文件是不可或缺的。例如:
pack.mcmeta
{ "pack": { "pack_format": 6, 数据包版本,1.13-1.14 版本为 4,1.15-1.16.1 版本为 5,1.16.2-1.16.5 版本为 6,1.17-1.17.1 版本为 7,1.18-1.18.1 版本为 8,1.18.2 版本为 9,1.19 版本为 10 "description": [数据包描述 { "text": "数据包名称", "color": "green" }, { "text": "简要介绍\nby 某某作者", "color": "gold" } ] }, "filter": { "block": [忽略相应命名空间下相应文件 { "namespace": "命名空间", "path": "文件路径" 支持通用标识符 * } ] } }
其中数据包描述为单个字符串或一个原始JSON文本。创建新的世界时,它会显示在数据包菜单中数据包名称下方。在数据包列表下,光标移动到对应的数据包时会显示此处填写的描述。
1.16版本原版数据包里的 pack.mcmeta
文件为
pack.mcmeta
{ "pack": { "pack_format": 6, "description": "The default data for Minecraft" } }
数据包可以包含一个 pack.png
,它是正方形的图片,用于创建新的世界时在数据包菜单中显示。
§2.2 命名空间
命名空间 (namespace) 为玩家自定义的、可操作的空间。使用独立的命名空间也有利于解决和他人的冲突。数据包下可以有多个命名空间,如果不同数据包中有相同的命名空间,则其中相同的文件名内容会根据加载先后顺序被覆盖。特别地,原版内容被保存在 minecraft
命名空间,想要修改和替换原版的内容只需在你的数据包内建立 minecraft
命名空间和相应的同名文件并修改即可。
标签文件,即 tags
中的文件内容默认追加而不是覆盖。因此标签文件是解决数据包冲突和联动的有力工具。
类型 | 调用格式(命名空间ID) | 文件路径 |
---|---|---|
函数 | 命名空间:路径/文件名 |
命名空间/functions/路径/文件名.mcfunction |
进度 | 命名空间:路径/文件名 |
命名空间/advancements/路径/文件名.json |
战利品表 | 命名空间:路径/文件名 |
命名空间/loot_tables/路径/文件名.json |
战利品表谓词 | 命名空间:路径/文件名 |
命名空间/predicates/路径/文件名.json |
物品修饰器 | 命名空间:路径/文件名 |
命名空间/item_modifiers/路径/文件名.json |
配方 | 命名空间:路径/文件名 |
命名空间/recipes/路径/文件名.json |
结构 | 命名空间:路径/文件名 |
命名空间/strutures/路径/文件名.nbt |
标签-方块 | 命名空间:路径/文件名 |
命名空间/tags/blocks/路径/文件名.json |
标签-物品 | 命名空间:路径/文件名 |
命名空间/tags/items/路径/文件名.json |
标签-函数 | 命名空间:路径/文件名 |
命名空间/tags/functions/路径/文件名.json |
标签-实体类型 | 命名空间:路径/文件名 |
命名空间/tags/entity_types/路径/文件名.json |
标签-流体类型 | 命名空间:路径/文件名 |
命名空间/tags/fluids/路径/文件名.json |
标签-游戏事件 | 命名空间:路径/文件名 |
命名空间/tags/game_events/路径/文件名.json |
维度 | 命名空间:路径/文件名 |
命名空间/dimension/路径/文件名.json |
维度类型 | 命名空间:路径/文件名 |
命名空间/dimension_type/路径/文件名.json |
世界生成-噪声设置 | 命名空间:路径/文件名 |
命名空间/worldgen/noise_settings/路径/文件名.json |
世界生成-生物群系 | 命名空间:路径/文件名 |
命名空间/worldgen/biome/路径/文件名.json |
世界生成-地形雕刻器 | 命名空间:路径/文件名 |
命名空间/worldgen/configured_carver/路径/文件名.json |
世界生成-地表生成器 | 命名空间:路径/文件名 |
命名空间/worldgen/configured_surface_builder/路径/文件名.json |
世界生成-地物 | 命名空间:路径/文件名 |
命名空间/worldgen/configured_feature/路径/文件名.json |
世界生成-结构地物 | 命名空间:路径/文件名 |
命名空间/worldgen/configured_structure_feature/路径/文件名.json |
世界生成-模板池 | 命名空间:路径/文件名 |
命名空间/worldgen/template_pool/路径/文件名.json |
世界生成-处理器列表 | 命名空间:路径/文件名 |
命名空间/worldgen/processor_list/路径/文件名.json |
1.16~1.16.1 版本的维度、维度类型和世界生成文件位置为 minecraft/类型/命名空间/路径/文件名.json
而不是 命名空间/类型/路径/文件名.json
。
若命名空间为 minecraft
,则可直接省略 minecraft:
。本文中我们会混用两种写法,注意区分。除此之外,如战利品表的 type
、战利品表谓词的 condition
等诸多情形的值也是 minecraft
命名空间下的命名空间ID,因此此时也可以省略 minecraft:
。
§2.3 函数
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/functions/路径/文件名.mcfunction |
函数 (Function) 是一系列顺次执行的命令列表,可以被命令 function 命名空间ID
来依次执行函数的每一条命令,还可被调用于进度JSON文件的 rewards->function
。函数每一行表示一个单独的命令,无需 /
开头。执行时会按顺序依次执行,使用 #
开头的行是注释。
如果函数被另一个函数执行,则默认无执行者,执行位置为出生点,除非在执行时继承调用它的函数的执行者和执行位置。如果函数被命令方块执行,则无执行者,执行位置为命令方块位置。如果函数被玩家手动执行,则执行者为玩家自身,执行位置为玩家位置。如果函数被进度奖励,执行者为完成进度的玩家,执行位置为玩家位置。这些情形下的执行者和执行位置均可使用命令 execute
来改变。
如果需要函数在游戏加载/每刻执行,则需要将其加入函数标签中。
cpp:plants/vegt
幻紫花 execute as @s[tag=cpp_crop_purple_illusion] if predicate cpp:near_purple run function cpp:plants/vegt/purple_illusion 稻谷 execute as @s[tag=cpp_crop_rice] if block ~ ~-1 ~ farmland if predicate cpp:near_water run function cpp:plants/vegt/rice 西红柿 execute as @s[tag=cpp_crop_tomato] if block ~ ~-1 ~ farmland run function cpp:plants/vegt/fruit 草莓 execute as @s[tag=cpp_crop_strawberry] if block ~ ~-1 ~ farmland run function cpp:plants/vegt/fruit 蓝莓 execute as @s[tag=cpp_crop_blueberry] if block ~ ~-1 ~ podzol run function cpp:plants/vegt/fruit 菠萝 execute as @s[tag=cpp_crop_pineapple] if block ~ ~-1 ~ coarse_dirt if predicate cpp:near_water run function cpp:plants/vegt/fruit
§2.4 进度
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/advancements/路径/文件名.json |
进度 (Advancement) 为游戏内检测玩家行为,触发后完成并执行奖励的系统,游戏内按 L
即可查看,可被用作命令 advancement
的参数,以授予或移除玩家的特定进度。
注意1.17的物品谓词和方块谓词与旧版本的有差异。
进度JSON格式
{ "display": { 可选,显示数据 "icon": { 一个物品,用于显示进度的图标 "item": "物品命名空间ID", 图标物品 ID "nbt": "字符串" 图标物品nbt标签,可能会影响图标,例如指定 CustomModelData }, "title": 原始JSON文本, 进度的名称 "description": 原始JSON文本, 进度的描述 "frame": "task" (默认) 或 "challenge" 或 "goal", 可选,图标边框,分别为方形边框(任务)、尖形边框(挑战)、圆形边框(目标) "background": "纹理命名空间ID", 仅根进度有,决定该选项卡的背景图 "show_toast": true (默认值) 或 false, 可选,是否在完成此进度后显示提示信息,若无显示效果则无效。 "announce_to_chat": true (默认值) 或 false, 可选,是否在完成此进度时在聊天窗口提示,若无显示效果则无效。 "hidden": true 或 false (默认值), 可选,在进度屏幕隐藏此进度和子进度,直到完成进度。 }, "parent": "进度命名空间ID", 可选,表示上游进度 "criteria": { 需要达成的条件 "条件名称": { 其中一个条件 "trigger": "触发器名称", "conditions": { 该触发器达成时需要满足的条件 } } }, "requirements": [ 可选,每一组条件中至少有一个达成时,该进度才会完成。默认所有条件都需要达成。 [ "条件", ..., "条件" ], [ "条件", ..., "条件" ] ], "rewards": { 可选, 完成进度的奖励 "function": "函数命名空间ID", 完成进度时执行该函数,不可为函数标签。 "loot": [ 完成进度时给予玩家这些战利品表 "战利品表命名空间ID", ..., "战利品表命名空间ID" ], "recipes": [ 完成进度时玩家解锁这些配方 "配方命名空间ID", ..., "配方命名空间ID" ], "experience": 正整数 完成进度时玩家获得的经验 } }
没有 parent
的进度被称为根进度。若其有 display
,则它定义了一个进度的选项卡。
当一个进度没有 display
时,或者其上游的根进度没有 display
时,它不会显示在进度页面中。解锁配方的进度,以及我们只是用来实现某些效果而并非希望将其添加到进度页面的进度,应当省略 display
以隐藏之。
当进度需要所有的条件都达成时才能完成时,我们不需要定义 requirements
,这等价于 "requirements": [["条件 1"],["条件 2"],...,["条件 n"]]
。如果我们需要复杂的组合条件时,就需要使用该字段。requirements
的格式为或的与(合取范式),即其列表中的每一个项(该项还是一个条件的列表)中的均至少有一个条件满足。例如 "requirements": [["条件 a", "条件 b"],["条件 c", "条件 d"]]
表示当 条件 a
和 条件 b
至少有一个满足且 条件 c
和 条件 d
至少有一个满足时,进度会完成。不过较为常见的还是所有条件只要有一个满足即可,这时写作 "requirements": [["条件 1","条件 2",...,"条件 n"]]
。例如下例中两个条件 sealing_wand
, dream_wand
分别表示玩家获取了相应 NBT 物品的触发器,只要其中有一个满足即可完成进度。
cpp/advancements/wand.json
{ "parent": "cpp:root", "display": { "icon": { "item": "minecraft:carrot_on_a_stick", "nbt": "{CustomModelData:12970062}" }, "title":{ "translate": "advancements.cpp.wand.title" }, "description": { "translate": "advancements.cpp.wand.description" }, "frame": "challenge" }, "criteria": { "sealing_wand": { "trigger": "minecraft:inventory_changed", "conditions":{ "items":[ { "nbt": "{id:\"cpp:sealing_wand\"}" } ] } }, "dream_wand": { "trigger": "minecraft:inventory_changed", "conditions":{ "items":[ { "nbt": "{id:\"cpp:dream_wand\"}" } ] } } }, "rewards": { "experience": 200 }, "requirements": [ [ "mahoushoujo", "dream_wand" ] ] }
完整的触发器和触发器的条件请查看进度/JSON格式#触发器列表。我们简要列出常见的触发器:
触发器 | 触发器的条件 |
---|---|
minecraft:impossible |
仅可使用命令触发。这样的进度一般使用函数来判断并直接给予玩家。 |
minecraft:tick |
每个游戏刻触发。常用于判断玩家是否是第一次进入游戏。也可用来仅需判断玩家的战利品表谓词来完成的进度,见下文。 |
minecraft:location |
检查玩家的位置,例如玩家进入某个维度、生物群系、结构等,或者玩家进入某种流体、某个高度等。有些也可以用其它触发器实现。 |
minecraft:enter_block |
玩家进入方块时触发,例如水、传送门、花等。 |
minecraft:placed_block |
玩家放置方块时触发,例如玩家放置了木桶、熔炉、树苗等。在我们自定义方块时很有用。该进度被触发时,方块已经被放置在世界中但玩家手持的方块尚未被清除,因此可以检测玩家手持物的信息。 |
minecraft:inventory_changed |
玩家物品栏变化时触发,例如玩家获取物品。 |
minecraft:consume_item |
玩家消耗了相应物品,例如食用食物、饮用药水等。在我们自定义食物和药水时很有用。该进度被触发时,玩家手持的物品尚未被消耗,因此可以检测玩家手持物的信息。 |
minecraft:entity_hurt_player |
实体伤害玩家时触发。 |
minecraft:entity_killed_player |
实体杀死玩家时触发。 |
minecraft:player_hurt_entity |
玩家伤害实体(包括自己)时触发。 |
minecraft:player_killed_entity |
玩家杀死实体时触发。 |
minecraft:player_interacted_with_entity |
玩家用手中物品与实体互动时触发,例如交易、修改实体数据(驯服、喂养、上鞍、剪羊毛、旋转物品展示框或放入物品、收集龙息、与猪灵换物、修补铁傀儡)、桶装牛奶、桶装鱼、碗装蘑菇煲、碗装迷之炖菜等。 |
minecraft:item_durability_changed |
物品栏中任何物品以任何形式损害时触发,例如使用工具消耗了耐久。当使用拥有耐久附魔的工具而未消耗耐久时,不会触发。 |
minecraft:item_used_on_block |
玩家对方块使用物品时触发,例如对着篝火放置食物、斧给木头剥皮、向花盆中放入花等改变目标方块数据的,或者右击告示牌,或者对着任意方块放置方块。 |
所有的触发器条件均有一个可选的战利品表谓词列表 player
字段用于要求玩家额外需要满足的战利品表谓词。例如
cpp:fatness
{ "display": { "icon": { "item": "minecraft:enchanted_golden_apple" }, "title":{ "translate": "advancements.cpp.fatness.title" }, "description": { "translate": "advancements.cpp.fatness.description" }, "frame": "challenge" }, "parent": "cpp:dream_wand", "criteria": { "fatness": { "trigger": "minecraft:tick", "conditions": { "player": [ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppHealth": { "min": 40 } } } ] } } } }
奖励中的解锁配方一般适用于数据包添加的配方相应的解锁进度,这样的进度无需 display
字段,参考配方。奖励中的经验建议仅在高难度进度完成时给予,这样的进度通常设置 frame
为挑战。奖励中的函数的执行者为完成进度的玩家,位置为玩家的位置。
游戏内或函数内使用 advancement
命令可以手动给予或剥夺进度。进度配合奖励函数剥夺玩家该进度,则该进度可反复触发,可用于诸如检测玩家生物群系、饮食、放置方块等需要循环检测的情形。这样的进度需要缺省 display
字段。例如:
cpp/advancements/food/citrus.json
{ "criteria": { "citrus": { "trigger": "minecraft:consume_item", "conditions": { "item": { "nbt": "{id:\"cpp:citrus\"}" } } } }, "rewards":{ "function": "cpp:diet/citrus" } }
cpp:diet/citrus
advancement revoke @s only cpp:diet/citrus effect give @s saturation 1 1 true
原版进度 如果需要修改原版的进度,只需要在数据包中添加相同路径的相应名称文件并修改之即可。原版的进度被划分为5个选项卡,命名空间ID分别为 minecraft:story/*
, minecraft:nether/*
, minecraft:end/*
, minecraft:adventure/*
, minecraft:husbandry/*
。其中 minecraft:nether/all_effects
, minecraft:adventure/hero_of_the_village
, minecraft:adventure/arbalistic
为隐藏进度。这些进度没有奖励,或只有奖励经验值。
除此之外,原版获取配方的进度命名空间ID为 minecraft:recipes/*
。
§2.5 战利品表
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/loot_tables/路径/文件名.json |
战利品表 (Loot table) 用于生成随机的一些物品,可作为容器的随机物品、实体的掉落物、方块的掉落物、钓鱼的战利品、猪灵以物换物的内容、猫和村民的礼物、进度的完成奖励、loot
命令的参数以给予玩家特定战利品表物品等。
setblock ~ ~ ~ chest{LootTable:"cpp:chests/something"}
give @s barrel{BlockEntityTag:{LootTable:"cpp:chests/something"}}
summon bat ~ ~ ~ {DeathLootTable:"cpp:entities/something"}
loot spawn ~ ~ ~ loot cpp:something
战利品表JSON格式
{ "type": 可选的战利品表种类,为 "minecraft:empty"无任何战利品 或 "minecraft:entity"只可用于实体掉落物 或 "minecraft:block" 只可用于方块掉落物 或 "minecraft:chest"只可用于箱子战利品表 或 "minecraft:fishing"只可用于钓鱼战利品表 或 "minecraft:barter"只可用于猪灵以物易物战利品表 或 "minecraft:gift"只可用于猫或村民赠送的礼物战利品表 或 "minecraft:advancement_reward"只可用于进度奖励 或 "minecraft:command"只可用于命令 或 "minecraft:selector"未知 或 "minecraft:advancement_entity"未知 或 "minecraft:generic",无限制,默认值 "functions": [可选,将会应用到该战利品表所有物品的函数 战利品表函数JSON对象1, 战利品表函数JSON对象2 ], "pools": [随机池列表,随机池之间相互独立 { "rolls": 值提供器, 指定在该随机池的抽取的次数 "bonus_rolls": 值提供器, 可选,默认为0,每点幸运提供的额外抽取次数。最终次数为 rolls+bonus_rolls×幸运 向下取整 "conditions": [可选,抽取该随机池需要满足的条件 战利品表条件JSON对象1, 战利品表条件JSON对象2 ], "functions": [可选,将会应用到该随机池所有物品的函数 战利品表函数JSON对象1, 战利品表函数JSON对象2 ], "entries": [项目列表 { "conditions": [可选,抽取该项目需要满足的条件,如果不满足,该项目会被忽略 战利品表条件JSON对象1, 战利品表条件JSON对象2 ], "type": 项目类型, "functions": [可选,将会应用到该项目所有物品的函数 战利品表函数JSON对象1, 战利品表函数JSON对象2 ], "weight": 值提供器, 可选,默认为1,该项目的基础权重 "quality": 值提供器, 可选,默认为0,每点幸运属性对权重的加成, 最终被选中的概率为 (weight+quality×幸运)/所有项目(weight+quality×幸运)之和 } ] } ] }
即使没有指定战利品表种类,或指定为 minecraft:generic
,游戏也会根据实际应用的场景来决定它的类型。这意味着在相应情形无效的条件和函数不会生效,而且游戏不会报错提示。例如在方块战利品表中检测击杀者。
rolls
, bonus_rolls
, 以及在战利品表、战利品表谓词和物品修饰器中的大部分数值可以有如下形式之一:
值提供器
任意数值(可以为小数) 或 {指定该数值为常数,和前面的写法等价 "type": "minecraft:constant", "value": 任意数值(可以为小数) } 或 {指定该数值的范围 "type": "minecraft:uniform", "min": 任意数值(可以为小数), "max": 任意数值(可以为小数) } 或 {指定该数值服从的二项分布 "type": "minecraft:binomial", "n": 任意数值(可以为小数), 尝试次数 "p": 0~1之间的小数 每增加一次的概率 } 或 {指定该数值来自于记分板 "type": "minecraft:score", "target": "this" 或 "killer" 或 "direct_killer" 或 "player_killer", 指定为该实体、杀死它的实体、直接杀死它的实体(例如箭), 杀死它的玩家 或 "target": { "type": "context", "target": "this" 或 "killer" 或 "direct_killer" 或 "player_killer" 和前面的写法等价 或 "type": "fixed", "name": "UUID或玩家名" }, "objective": "记分板名称", "scale": 数值 可选,放缩比例 }
项目会根据种类的不同而拥有不同的JSON格式:
战利品表项目
{ "type": "minecraft:empty" 空 } { "type": "minecraft:item", 指定物品 "name": "物品命名空间ID" } { "type": "minecraft:loot_table", 指定战利品表 "name": "战利品表命名空间ID" } { "type": "minecraft:tag", 指定物品标签 "name": "物品标签命名空间ID", "expand": true 或 false true 时,从该标签等概率随机选取一个;false 时,选出该标签下所有物品。 } { "type": "minecraft:alternatives", 从子项目中选取第一个满足条件的,这些子项目通常都指定了条件。 "children": [项目列表 项目JSON对象1, 项目JSON对象2 ] } { "type": "minecraft:group", 从所有满足条件的子项目中随机选取,逻辑和随机池类似。 "children": [项目列表 项目JSON对象1, 项目JSON对象2 ] } { "type": "minecraft:sequence", 从第一个条件不满足的子项目之前的所有的子项目中随机选取,即第一个不满足条件的子项目和之后的子项目会被忽略。 "children": [项目列表 项目JSON对象1, 项目JSON对象2 ] } { "type": "minecraft:dynamic", 动态指定物品,一般只对原版的某些方块掉落物有效。 "name": "minecraft:self" 仅适用于玩家头颅、旗帜等,用于返回相同样式物品 或 "minecraft:content" 仅适用于潜影盒,用于返回潜影盒内所有物品 }
我们在设计物品时,可以将物品信息打包成一个战利品表。之后我们便可以在其它战利品表中使用 loot_table
类型来方便地引用,见物品设计。alternatives
类型在需要判断条件时很有用,例如原版的树叶掉落物就使用了该类型。当工具为剪刀或附有精准采集魔咒时,掉落树叶,否则掉落树苗。dynamic
类型配合潜影盒可以掉落不定 id
的物品,见物品输出和修改玩家背包。
战利品表条件 用于对战利品表进行一些条件判断。仅当条件列表 conditions
中每一个条件都被满足时,其同层次的随机池/项目/函数才会被选取。战利品表条件和战利品表谓词JSON对象的格式完全相同。
战利品表函数 用于对战利品表进行一些修改。函数列表 functions
中每一个函数会依次应用于同层次的随机池/项目生成的战利品上,函数的不同顺序可能有不同效果。战利品表函数和物品修饰器JSON对象的格式完全相同。
下面这个例子是对原版牛的掉落物进行的修改。
minecraft/loot_tables/entities/cow.json
{ "type": "minecraft:entity", "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:leather", "functions": [ { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 0, "max": 2 } }, { "function": "minecraft:looting_enchant", "count": { "type": "minecraft:uniform", "min": 0, "max": 1 } } ] } ] }, { "rolls": 1, "entries": [ { "type": "minecraft:alternatives", "children": [ { "conditions": [ { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "flags": { "is_on_fire": false } } } ], "type": "minecraft:item", "name": "minecraft:beef", "functions": [ { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 1, "max": 3 } }, { "function": "minecraft:looting_enchant", "count": { "type": "minecraft:uniform", "min": 0, "max": 1 } } ] }, { "type": "minecraft:item", "name": "minecraft:cooked_beef", "functions": [ { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 1, "max": 3 } }, { "function": "minecraft:looting_enchant", "count": { "type": "minecraft:uniform", "min": 0, "max": 1 } } ] } ] } ] }, { "conditions": [ { "condition": "minecraft:killed_by_player" }, { "condition": "minecraft:random_chance_with_looting", "chance": 0.025, "looting_multiplier": 0.01 } ], "rolls": 1, "entries": [ { "type": "minecraft:loot_table", "name": "cpp:limb_of_ridge" } ] }, { "conditions": [ { "condition": "minecraft:killed_by_player" }, { "condition": "minecraft:random_chance_with_looting", "chance": 0.025, "looting_multiplier": 0.01 } ], "rolls": 1, "entries": [ { "type": "minecraft:loot_table", "name": "cpp:cow_head" } ] } ] }
该例子战利品表表示牛死亡时
- 掉落
0-(2+抢夺等级)
个皮革; - 掉落
1-(3+抢夺等级)
个牛肉,着火则掉落熟牛肉; - 被玩家杀死时,有
(2.5+抢夺等级)%
几率掉落战利品表cpp:limb_of_ridge
的物品; - 被玩家杀死时,有
(2.5+抢夺等级)%
几率掉落战利品表cpp:cow_head
的物品。
战利品表除上述用途外,还可用于生成随机数,见随机数;指定自定义机器的配方,见配方处理等诸多场景。
原版战利品表用于决定方块、实体的默认掉落物以及游戏的一些默认内容。如果需要修改原版的战利品表,只需要在数据包中添加相同路径的相应名称文件并修改之即可。原版的战利品表被划分为实体掉落物 minecraft:entities/*
, 方块掉落物 minecraft:blocks/*
, 箱子战利品表 minecraft:chests/*
和游戏内容(钓鱼、猪灵以物换物、村民礼物、猫的礼物) minecraft:gameplay/*
。
§2.6 战利品表谓词
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/predicates/路径/文件名.json |
战利品表谓词 (Predicate) 用于对所在情形进行条件判断,可作为战利品表的条件、命令 execute
的条件子命令 (if|unless) predicate foo:bar
、目标选择器参数 predicate={foo:bar}
、进度中判断实体条件等。战利品表的条件和战利品表谓词格式相同。
注意1.17和旧版本的物品谓词和方块谓词有差异。
战利品表谓词JSON格式
我们省略了部分信息的格式,请自行前往 wiki 查阅。 { "condition": "minecraft:alternative",下述任一条件满足时通过 "terms": [ 战利品表条件JSON对象1, 战利品表条件JSON对象2 ] } { "condition": "minecraft:inverted",下述条件不满足时通过 "term": 战利品表条件JSON对象 } { "condition": "minecraft:reference",引用其它的战利品表谓词,注意避免循环引用 "name": "战利品表谓词命名空间ID" } { "condition": "minecraft:random_chance",以一定概率通过,通过几率为 chance "chance": 数值 } { "condition": "minecraft:random_chance",以一定概率通过,通过几率为 chance+looting_multiplier×抢夺等级 "chance": 数值, "looting_multiplier": 数值 若并非 entity 类型战利品表该项会被忽略 } { "condition": "minecraft:survives_explosion",block 类型战利品表若方块是被爆炸破坏的,该条件有 1/爆炸半径 的几率通过。其它情形永远通过。 } { "condition": "minecraft:location_check",检查死亡的实体位置/方块位置/命令执行位置。注意该条件用在方块掉落物战利品表时,方块已经被挖掘掉,因此该位置方块总是空气。 "offsetX": 数值, 可选,默认为0,X轴偏移量 "offsetY": 数值, 可选,默认为0,Y轴偏移量 "offsetZ": 数值, 可选,默认为0,Z轴偏移量 "predicate": 位置信息共通标签 检查偏移后的位置 } { "condition": "minecraft:time_check",检测当前游戏内的时间 "value": 范围谓词, "period": 数值 可选,判断前先对该数值取模 } { "condition": "minecraft:weather_check",检测当前游戏内的天气 "raining": "true" 或 "false", 可选,是否正在下雨(含雷雨天) "thundering": "true" 或 "false", 可选,是否正在打雷 } { "condition": "minecraft:value_check",检测 value 是否位于 range 范围内 "value": 值提供器, "range": 值提供器 或 { "min": 值提供器, "max": 值提供器 } } 下述战利品表谓词仅对 block 或 fishing 类型战利品表可用。 { "condition": "minecraft:match_tool",检查挖掘/钓鱼的工具 "predicate": 物品共通标签 } { "condition": "minecraft:block_state_property",检查方块。该条件用在方块掉落物战利品表时,不能被 location_check 替代,因为后者进行判断时方块已经被挖掘掉,其位置总是空气。 "terms": [ "block": "方块命名空间ID", "properties": { "方块状态名": "字符串" 或 布尔值 或 范围谓词 } ] } { "condition": "minecraft:table_bonus",定义不同级别的附魔等级下条件的通过几率 "enchantment": "魔咒命名空间ID", 探测的魔咒 "chances": [ 数值, 没有该魔咒时的通过几率 数值, 该魔咒为1级时的通过几率 数值, 该魔咒为2级时的通过几率 ... 数值 该魔咒为至少n级时的通过几率 ] } 下述战利品表谓词仅对 entity 或 generic 类型战利品表可用。 { "condition": "minecraft:damage_source_properties",检查当前伤害 "predicate": 伤害类型共通标签 } { "condition": "minecraft:killed_by_player",判断击杀者是否为玩家 "inverted": "true" (默认) 或 "false" 可选,true 时当击杀者是玩家时通过,false 时当击杀者不是玩家时通过。 } 下述战利品表谓词仅对 entity 或 generic 类型战利品表以及命令可用。 { "condition": "minecraft:entity_properties",检查实体 "entity": "this" 死亡的实体或命令执行者或 "killer" 击杀者或 "player_killer", 击杀目标的玩家 "scores": { "记分板名称1": 范围谓词, "记分板名称2": 范围谓词 } } { "condition": "minecraft:entity_scores",检查实体记分板 "entity": "this" 或 "killer" 或 "player_killer", 含义同上 "predicate": 实体共通标签 }
范围谓词
数值 或 { "min": 数值, 可选, 最小值 "max": 数值, 可选, 最大值 }
这个例子表示玩家的分数 cppChainTick
至少为 1
时,或者手持物具有特定标签时通过。
cpp/predicates/chain_effect.json
{ "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppChainTick": { "min": 1 } } }, { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "mainhand": { "nbt": "{cppStoredEffects:[{id:\"chain\"}]}" } } } } ] }
战利品表谓词文件也可以为战利品表谓词JSON对象的列表。这个例子表示玩家的手持物或盔甲拥有指定标签,且玩家不拥有 cpp:temperancer
时通过。
cpp/predicates/stored_effects
[ { "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "mainhand": { "nbt": "{cppStoredEffects:[{}]}" } } } }, { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "head": { "nbt": "{cppStoredEffects:[{}]}" } } } }, { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "chest": { "nbt": "{cppStoredEffects:[{}]}" } } } }, { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "legs": { "nbt": "{cppStoredEffects:[{}]}" } } } }, { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "feet": { "nbt": "{cppStoredEffects:[{}]}" } } } } ] }, { "condition": "minecraft:inverted", "term": { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "nbt": "{Inventory:[{tag:{id:\"cpp:temperancer\"}}]}" } } } ]
§2.7 物品修饰器
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/item_modifiers/路径/文件名.json |
物品修饰器 (Item modifier) 用于在命令 /item
中对物品添加战利品表函数。战利品表的函数和物品修饰器格式相同,但物品修饰器不可被战利品表的函数引用。
物品修饰器JSON格式
修改物品数量的函数,最终不会超过该物品的最大可堆叠数。 { "function": "minecraft:set_count", 设置数量 "count": 值提供器, "add": true 或 false 将count加到原本的数量中,还是设置数量为count } { "function": "minecraft:apply_bonus",指定物品数量服从与特定魔咒等级有关的分布 "enchantment": "魔咒命名空间ID",检查该魔咒的等级 "formula": "binomial_with_bonus_count", 使用参数为 n=魔咒等级+extra 和 p=probability 的二项分布。 "parameters": { "extra": 整数, "probability": 数值 } 或 "formula": "uniform_bonus_count", 使用0至 魔咒等级×bonusMultiplier 的均匀分布。 "parameters": { "bonusMultiplier": 数值 } 或 "formula": "ore_drops", 使用矿物默认掉落数的分布,即有1/(魔咒等级+2)几率为之前的函数已设定的数量×(2至(魔咒等级+1)),有2/(魔咒等级+2)几率为之前的函数已设定的数量。 } { "function": "minecraft:limit_count", 限制每一种物品的堆叠数量,即其它函数得到的物品堆叠数量如果超过这个值,会被设定为该值 "limit": 值提供器 限制堆叠数量 或 { "min": 值提供器, 限制堆叠数量最小值 "min": 值提供器 限制堆叠数量最大值 } } { "function": "minecraft:looting_enchant", 抢夺魔咒会额外增加物品数量 等级×count "count": 值提供器, "limit": 数值 可选,默认为0无限制。限制最终数量的上限 } { "function": "minecraft:explosion_decay" 仅用于 block 类型战利品表,当目标方块被爆炸摧毁时,执行该函数的每个物品有1/爆炸半径的几率消失,堆叠的物品会被分为多个单独的物品计算。 } 下列函数可以改变物品NBT。 { "function": "minecraft:copy_name", 将方块实体的 CustomName 标签复制到物品的 display.Name 标签。仅用于 block 类型战利品表 "source": "block_entity" } { "function": "minecraft:copy_state",复制方块的方块状态到物品的BlockStateTag标签。仅用于 block 类型战利品表 "block": 方块命名空间ID,仅当目标方块与该ID相符时执行该函数 "properties": [要复制的方块状态列表 "字符串" ] } { "function": "minecraft:copy_nbt", 复制NBT到物品的tag标签 "source": "block_entity" 来源为被破坏的方块,仅用于 block 类型战利品表 或 "this" 命令执行者;entity 类型战利品表的死亡者;进度奖励中获得进度的玩家;block 类型战利品表的打开或破坏容器的玩家 或 "killer" entity 类型战利品表的击杀者 或 "killer_player" entity 类型战利品表的击杀者,必须是玩家 或 { "type": "context", "target": "this" 或 "killer" 或 "direct_killer" 或 "player_killer" 和前面的写法等价 或 "type": "storage", 设置来源为命令存储空间(storage) "source": "命令存储空间的命名空间ID" }, "ops": [ { "source": "字符串", 要被复制的NBT路径 "target": "字符串", 要复制到的NBT路径,从物品的tag标签开始 "op": "replace" 替换目标的所有存在的内容 或 "append" 追加到一个列表 或 "merge" 融合到一个复合标签 } ] } { "function": "minecraft:set_name", 设置物品自定义名称,该函数可以对JSON文本中组件进行解析。 "name":"原始JSON文本", "entity": 指定JSON文本里中@s表示的实体 "this" 命令执行者;entity 类型战利品表的死亡者;进度奖励中获得进度的玩家;block 类型战利品表的打开或破坏容器的玩家 或 "killer" entity 类型战利品表的击杀者 或 "killer_player" entity 类型战利品表的击杀者,必须是玩家 } { "function": "minecraft:set_lore", 设置物品附加文本(lore)标签,该函数可以对JSON文本中组件进行解析。 "lore":[ "原始JSON文本1", "原始JSON文本2" ], "entity": 指定JSON文本里中@s表示的实体 "this" 命令执行者;entity 类型战利品表的死亡者;进度奖励中获得进度的玩家;block 类型战利品表的打开或破坏容器的玩家 或 "killer" entity 类型战利品表的击杀者 或 "killer_player", entity 类型战利品表的击杀者,必须是玩家 "replace": true 或 false 如果为true,则替换物品当前的lore;如果为false,则在物品lore后追加内容。 } { "function": "minecraft:set_nbt", 为物品添加NBT标签 "tag": "NBT对象" 添加NBT标签,和命令的使用方法类似。注意最外层括号需要书写花括号({})以及内部的引号需要使用转义符(\)标记。 } { "function": "minecraft:set_stew_effect", 为迷之炖菜添加状态效果 "effects": [ { "type":"状态效果命名空间ID" "duration":值提供器 指定状态效果的持续时间 } ] } { "function": "minecraft:enchant_randomly", 为物品附上一个随机的魔咒。魔咒的等级也是随机的。 "enchantments":[ 能够附上的魔咒列表。如果没有此标签,所有可以对该物品附上的魔咒都可能被附上。 "魔咒命名空间ID1", "魔咒命名空间ID2" ] } { "function": "minecraft:enchant_with_levels", 使用指定的附魔等级附魔物品 "treasure": true 或 false, 该物品是否能被附上宝藏魔咒 "levels": 值提供器 指定该附魔的等级,超过99会无效。 } { "function": "minecraft:set_enchantments", 设置物品附魔 "enchantments": { "魔咒命名空间ID": 值提供器, 该魔咒的等级 }, "add": true 或 false 追加魔咒还是覆盖原有魔咒 } { "function": "minecraft:fill_player_head", 给玩家头颅添加指定物品标签 "entity": "this" 命令执行者;entity 类型战利品表的死亡者;进度奖励中获得进度的玩家;block 类型战利品表的打开或破坏容器的玩家 或 "killer" entity 类型战利品表的击杀者 或 "killer_player" entity 类型战利品表的击杀者,必须是玩家 } { "function": "minecraft:set_attributes", 为物品加上属性修饰符 "modifiers": [属性修饰符列表 { "name": "字符串", 修改器的名称 "attribute": "字符串", 此修改器修改的属性的名称 "operation": "addition" 或 "multiply_base" 或 "multiply_total", 修改数值的方法 "amount": 值提供器 要修改的数值 或 { "min": 值提供器, 要修改的数值最小值 "min": 值提供器 要修改的数值最大值 }, "id": "字符串", 可选,修改器的UUID。如果没有指定,则生成新的UUID。不建议手动指定。 "slot": "mainhand" 或 "offhand" 或 "feet" 或 "legs" 或 "chest" 或 "head" 该属性生效的栏位 或 [ 随机选择列表中的一个栏位 "栏位1", "栏位2", ] } ] } { "function": "minecraft:set_banner_pattern", 设置旗帜样式 "patterns": [ { "pattern": "base" 或 "border" 或 "bricks" 或 "circle" 或 "creeper" 或 "cross" 或 "curly_border" 或 "diagonal_left" 或 "diagonal_right" 或 "diagonal_up_left" 或 "diagonal_up_right" 或 "flower" 或 "globe" 或 "gradient" 或 "gradient_up" 或 "half_horizontal" 或 "half_horizontal_bottom" 或 "half_vertical" 或 "half_vertical_right" 或 "mojang" 或 "piglin" 或 "rhombus" 或 "skull" 或 "small_stripes" 或 "square_bottom_left" 或 "sqaure_bottom_right" 或 "square_top_left" 或 "square_top_right" 或 "straight_cross" 或 "stripe_bottom" 或 "stripe_center" 或 "stripe_downleft" 或 "stripe_downright" 或 "stripe_left" 或 "stripe_middle" 或 "stripe_right" 或 "stripe_top" 或 "triangle_bottom" 或 "triangle_top" 或 "triangles_bottom" 或 "triangles_top", 旗帜样式 "color": "white" 或 "orange" 或 "magenta" 或 "light_blue" 或 "yellow" 或 "lime" 或 "pink" 或 "gray" 或 "light_gray" 或 "cyan" 或 "purple" 或 "blue" 或 "brown" 或 "green" 或 "red" 或 "black 该样式的颜色 } ], "append": true 或 false 是否是追加到旗帜已有的样式上,若否则覆盖之 } { "function": "minecraft:set_damage", 设置物品耐久 "damage": 值提供器, 物品已损伤的比例 "add": true 或 false 是否相对于当前耐久来设置 } { "function": "minecraft:set_loot_table", 设置容器物品的战利品表 "type": 方块命名空间ID, 只允许是方块实体 "name": "战利品表命名空间ID", "seed": 数值 战利品表种子,可选。默认为0,不指定种子。 } 下列函数可以改变物品ID。 { "function": "minecraft:furnace_smelt" 物品将变为在熔炉里烧炼后的状态。支持数据包添加的熔炉烧炼配方,但不支持高炉、烟熏炉或营火的烧炼配方。 } { "function": "minecraft:exploration_map", 将普通的地图物品变为一个指引到某个结构的探险家地图 "destination": "结构类型", 理论上应该和/locate命令中的参数一样,实际上每个单词都需要首字母大写。 "decoration": "地图图标ID", 该结构在地图中显示的图标(大小写敏感)。如果设置为mansion或monument,地图物品的图标将会改变。 "zoom": 整数, 默认为2。地图缩放等级 "search_radius": 整数, 默认为50。搜寻以当前区块为中心的(search_radius+1)×(search_radius+1)的区块。 "skip_existing_chunks": true(默认值) 或 false 是否在搜寻时跳过已生成的区块 } { "function": "minecraft:set_contents", 指定掉落物为容器时其内含物,仅对容器的方块战利品表有效 "type": 方块命名空间ID, 只允许是方块实体 "entries": [ 战利品表项目列表 设置为dynamic类型的content,可使容器保留其内含物。但除潜影盒外的容器被破坏时总会将内含物掉出。 ] }
物品修饰器也可以为物品修饰器JSON对象的列表。例如下例中我们提前将需要设置的耐久存入 storage cpp:_
的 Damage
,且已损伤的耐久不小于 25
时将其数量-1。
cpp/item_modifiers/chest_transporter.json
[ { "function": "minecraft:copy_nbt", "source": { "type": "storage", "source": "cpp:_" }, "ops": [ { "op": "replace", "source": "tag.Damage", "target": "Damage" } ] }, { "conditions": [ { "condition": "minecraft:value_check", "range": { "min": 25 }, "value": { "type": "minecraft:score", "target": { "type": "minecraft:fixed", "name": "#damage" }, "score": "cppValue" } } ], "function": "minecraft:set_count", "add": false, "count": 0 } ]
例:将玩家1个副手物品移动到头盔栏。注意引用时应当先检测玩家头盔栏是否为空再执行该函数。
cpp/item_modifiers/one.json
{ "function": "set_count", "count": 1, "add": false }
cpp/item_modifiers/minus.json
{ "function": "set_count", "count": -1, "add": true }
cpp:hat
item entity @s armor.head copy entity @s weapon.offhand cpp:one # 注意有的物品不能放置在头盔栏,此时不应当将副手物品数量-1 execute if data entity @s Inventory[{Slot:103b}] run item entity @s weapon.offhand modify cpp:minus
例:获取函数执行者手持物的最大耐久。
cpp:get_durality
setblock ~ 255 ~ barrel item block ~ 255 ~ container.0 copy entity @s weapon.mainhand cpp:set_damage execute store result score #max_durality cppValue run data get block ~ 255 ~ Items[0].tag.Damage setblock ~ 255 ~ air
cpp/item_modifiers/set_damage.json
{ "function": "minecraft:set_damage", "add": false, "damage": 0 }
§2.8 配方
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/recipes/路径/文件名.json |
配方 (Recipe) 为游戏内指定工作台合成、切石机合成、熔炉烧炼、高炉烧炼、烟熏炉烧炼、营火烧炼和锻造台合成的格式,一般不支持带 NBT 标签。
在默认情形(限制配方关闭)下,玩家总是可以使用所有的配方,且使用一个配方会使玩家自动发现它。因此建议为你的自定义配方添加合适的进度来获取该配方。一旦配方被发现,就将被加入玩家的配方书。配方只会在玩家使用与当前配方类型所匹配的方块时显示。例如,烧炼配方将只在熔炉的界面中显示。当在背包中使用配方书时,只有能在玩家的 2×2
背包合成栏内使用的配方才会显示。已发现的配方储存在玩家 recipeBook
的NBT中。
原版的合成、烧炼使用配方文件即可实现,较为简单,但是无法识别且无视配方材料的NBT(除了特殊合成配方),因此只能实现原版物品的合成和烧炼。同时原版模组若使用了相关物品,则相应的模组物品亦可代替原物品进行合成。想要实现带NBT的合成需要藉由其它方式,见NBT合成与烧炼。
锻造台则会保留要升级的物品的NBT信息而只修改其id。
大部分配方中所使用的配方材料格式可以为下述几种。
配方材料JSON格式
{ "item": "物品命名空间ID" 相应物品 } 或 { "tag": "物品标签命名空间ID" 物品标签中的任一物品 } 或 [ 列表中任一对象的物品 配方材料JSON对象 ]
§2.8.1 有序合成
JSON格式
{ "group": "字符串", 可选,具有相同该标识符的配方会在配方书中显示为一组。 "type": "minecraft:crafting_shaped", 表示有序合成 "pattern": [ 由单字符键所组成的数组,用于描述配方的图案。至多3个字符串,每个字符串长度一致且至多为3。空格代表一个空的合成栏位。 "###", "#A#", "###" ], "key": { 所有该有序合成配方用到的键。 "A": 配方材料JSON对象 或 配方材料JSON对象列表, "#": 配方材料JSON对象 或 配方材料JSON对象列表, }, "result": { "item": "物品命名空间ID", 输出的物品 "count": 正整数 可选,输出的物品数量,默认为1 } }
使用6羊毛+鸡蛋工字形有序合成两个羊刷怪蛋。
cpp/recipes/sheep_spawn_egg.json
{ "type": "minecraft:crafting_shaped", "pattern": [ "WWW", " E ", "WWW" ], "key": { "E": { "item": "minecraft:egg" }, "W": { "tag": "cpp:wools" } }, "result": { "item": "minecraft:sheep_spawn_egg", "count":2 } }
这里 cpp:wools
为自定义的包含所有颜色羊毛的物品标签。
§2.8.2 无序合成
JSON格式
{ "group": "字符串", 可选,具有相同该标识符的配方会在配方书中显示为一组。 "type": "minecraft:crafting_shapeless", 表示无序合成 "ingredients": [ 配方材料列表,该无序合成配方的一系列原料,数量应在1至9个之间。 配方材料JSON对象 或 配方材料JSON对象列表 ], "result": { "item": "物品命名空间ID", 输出的物品 "count": 正整数 可选,输出的物品数量,默认为1 } }
配料可以重复。单个物品配方请使用有序合成而不是无序合成。
2沙子和红石无序合成2红沙。
cpp/recipes/red_sand.json
{ "type": "minecraft:crafting_shapeless", "ingredients": [ { "item": "minecraft:sand" }, { "item": "minecraft:sand" }, { "item": "minecraft:redstone" } ], "result": { "item": "minecraft:red_sand", "count":2 } }
§2.8.3 切石机配方
JSON格式
{ "group": "字符串", 可选,具有相同该标识符的配方会在配方书中显示为一组。 "type": "minecraft:stonecutting", 表示切石机配方 "ingredients": 配方材料JSON对象 或 配方材料JSON对象列表, "result": { "item": "物品命名空间ID", 输出的物品 "count": 正整数 输出的物品数量,默认为1 } }
注意 result.count
不能省略。
使用切石机切割橡木木板为2台阶。
cpp/recipes/stonecutting/oak_slab.json
{ "type": "minecraft:stonecutting", "ingredient": { "item": "minecraft:oak_planks" }, "result": "minecraft:oak_slab", "count": 2 }
§2.8.4 烧炼配方
JSON格式
{ "group": "字符串", 可选,具有相同该标识符的配方会在配方书中显示为一组。 "type": "minecraft:smelting"表示熔炉配方 或 "minecraft:blasting"表示高炉配方 或 "minecraft:smoking"表示烟熏炉配方 或 "minecraft:campfire_cooking"表示营火配方, "ingredients": 配方材料JSON对象 或 配方材料JSON对象列表, "result": "物品命名空间ID", 输出的物品, "experience": 数值, 该配方产生的经验值 "cookingtime": 正整数 可选,该配方的烧炼时间,单位:刻。如果缺省,将使用默认的时间。 }
熔炉配方的默认烧炼时间是200刻,即10秒。高炉配方和烟熏炉配方的默认烧炼时间是100刻,即5秒。营火的默认烧炼时间是100刻,即5秒,但所有的原版营火配方都将烧炼时间修改为了600刻,即30秒。
熔炉烧炼皮革装备、腐肉、鞍为兔子皮。
cpp/recipes/rabbit_hide_from_smelting.json
{ "type": "minecraft:smelting", "ingredient": [ { "item": "minecraft:rotten_flesh" }, { "item": "minecraft:leather_helmet" }, { "item": "minecraft:leather_chestplate" }, { "item": "minecraft:leather_leggings" }, { "item": "minecraft:leather_boots" }, { "item": "minecraft:saddle" } ], "result": "minecraft:rabbit_hide", "experience": 0.1, "cookingtime": 200 }
§2.8.5 锻造台配方
JSON格式
{ "group": "字符串", 可选,具有相同该标识符的配方会在配方书中显示为一组。 "type": "minecraft:smithing", 表示锻造台配方 "base": 配方材料JSON对象, 指定一个要升级的物品 "addition": 配方材料JSON对象, 升级所需的消耗品 "result": "物品命名空间ID" 升级得到的物品 }
使用锻造台使用钻石将铁斧升级为钻石斧。
cpp/recipes/smithing/diamond_axe.json
{ "type": "minecraft:smithing", "base": { "item": "minecraft:iron_axe" }, "addition": { "item": "minecraft:diamond" }, "result": { "item": "minecraft:diamond_axe" } }
§2.8.6 工作台特殊配方
工作台有一些特殊配方由游戏内部的代码处理,无法通过JSON文件来修改。这些配方均涉及物品的NBT修改。当原版数据包被禁用时,你可以用它们来重新启用你所需要的特殊合成配方。
JSON格式
{ "type": "minecraft:crafting_special_armordye" 盔甲染色配方 或 "type": "minecraft:crafting_special_bannerduplicate" 旗帜复制配方 或 "type": "minecraft:crafting_special_bookcloning" 成书复制配方 或 "type": "minecraft:crafting_special_firework_rocket" 使用烟火之星合成烟花火箭的配方 或 "type": "minecraft:crafting_special_firework_star" 烟火之星的合成配方 或 "type": "minecraft:crafting_special_firework_star_fade" 烟火之星的色彩淡化配方 或 "type": "minecraft:crafting_special_mapcloning" 地图复制配方 或 "type": "minecraft:crafting_special_mapextending" 地图比例缩小配方 或 "type": "minecraft:crafting_special_repairitem" 物品修复配方 或 "type": "minecraft:crafting_special_shielddecoration" 给盾牌添加图案的配方 或 "type": "minecraft:crafting_special_shulkerboxcoloring" 潜影盒染色配方 或 "type": "minecraft:crafting_special_tippedarrow" 药箭配方 或 "type": "minecraft:crafting_special_suspiciousstew" 迷之炖菜配方 }
§2.8.7 覆盖原版配方
在数据包的pack.mcmeta
中可以指定忽略的数据包文件,例如下面就屏蔽了原版棕色蘑菇合成兔肉煲的配方,以及用于解锁该配方的相应进度。
pack.mcmeta
{ "pack": { "pack_format": 10 }, "filter": { "block": [ { "namespace": "minecraft", "path": "recipes/rabbit_stew_from_brown_mushroom.json" }, { "namespace": "minecraft", "path": "advancements/recipes/food/rabbit_stew_from_brown_mushroom.json" } ] } }
1.19之前的版本如果想要修改原版的合成或烧炼配方,先使用压缩软件打开版本 .jar
文件,依次打开 data/minecraft/recipes
,找到相应的配方文件,然后在自己的数据包中的相同位置(必然是 minecraft
命名空间下)放入同名文件即可覆盖默认的配方。
将橡木台阶合成数量改为8。
minecraft/recipes/oak_stairs.json
{ "type": "minecraft:crafting_shaped", "group": "wooden_stairs", "pattern": [ "# ", "## ", "###" ], "key": { "#": { "item": "minecraft:oak_planks" } }, "result": { "item": "minecraft:oak_stairs", "count": 8 } }
将原版白色床+墨囊=黑色床的合成配方修改为任意床+墨囊=黑色床。
cpp/tags/items/beds.json
{ "replace": false, "values": [ "minecraft:white_bed", "minecraft:orange_bed", "minecraft:magenta_bed", "minecraft:light_blue_bed", "minecraft:yellow_bed", "minecraft:lime_bed", "minecraft:pink_bed", "minecraft:gray_bed", "minecraft:light_gray_bed", "minecraft:cyan_bed", "minecraft:purple_bed", "minecraft:blue_bed", "minecraft:brown_bed", "minecraft:green_bed", "minecraft:red_bed", "minecraft:black_bed" ] }
minecraft/recipes/black_bed_from_white_bed.json
{ "type": "crafting_shapeless", "group": "dyed_bed", "ingredients": [ { "tag": "cpp:beds" }, { "item": "minecraft:black_dye" } ], "result": { "item": "minecraft:black_bed" } }
如果需要删除原版配方,可使用生存无法获得的方块如基岩=基岩、屏障=屏障、结构空位等物品来合成。配方文件内容为 {}
时会被认为是错误文件而无法覆盖原配方。同时,我们需要将解锁该配方的进度触发器设置为 minecraft:impossible
。
§2.8.8 配方获取
一个完整的配方应当有相应的进度来使玩家获取之,通常触发器为玩家背包含有合成材料。也可以根据需要在函数中使用命令 recipe
给予。
cpp/advancements/recipes/dirt_from_cobblestone_snowball.json
{ "criteria": { "cobblestone": { "trigger": "minecraft:inventory_changed", "conditions": { "items": [ { "items": ["minecraft:cobblestone"], "count": { "min": 8 } } ] } }, "snowball": { "trigger": "minecraft:inventory_changed", "conditions": { "items": [ { "items": ["minecraft:snowball"] } ] } }, "has_the_recipe": { "trigger": "minecraft:recipe_unlocked", "conditions": { "recipe": "cpp:dirt_from_cobblestone_snowball" } } }, "rewards":{ "recipes": ["cpp:dirt_from_cobblestone_snowball"] }, "requirements": [ [ "cobblestone", "snowball", "has_the_recipe" ] ] }
§2.9 结构
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/structures/路径/文件名.nbt |
结构 (Structure) 存储了一个长方体区域的方块和实体信息,玩家可以使用结构方块来创建、保存、调用结构。结构大小一般至多为 48×48×48
,1.15及更早版本则至多为 32×32×32
。
在游戏中获得结构方块后,放置好所需要的方块结构和实体,然后放置结构方块,设置为保存模式,调整好大小,输入名称(命名空间ID),点保存即可(注意区分是否需要保存实体)。然后从 .minecraft/saves/世界名称/generated/命名空间/structures/路径/文件名.nbt
复制到数据包内。调用时,在结构方块中输入命名空间ID来加载(注意区分是否需要加载实体)。
我们也可以在函数中加载,先放置结构方块,然后放置并清除红石块,最后清除结构方块。这个技巧配合战利品表和断言,可用于随机在世界生成结构,也可直接用于模板池。例如:
cpp/functions/generate/enchanting_room.mcfunction
setblock ~ ~ ~ structure_block{posX:-1,posY:0,posZ:-1,name:"cpp:build/enchanting_room",mode:"LOAD"} setblock ~ ~1 ~ redstone_block setblock ~ ~1 ~ air
结构文件的NBT结构为:
- size 结构的长宽高;
- entities 结构包含的实体;
- blocks 结构包含的方块,其中 pos 指定了相对坐标,state 指定了该方块在方块状态列表的编号,从0开始;
- palette 方块状态列表,其中 Name 为方块命名空间ID,Properties 指定该方块的所有方块状态。也可以为palettes,这个列表的每一个项目都是一个方块状态列表,然后调用结构时会根据种子(坐标和完整度)来确定选择哪一个列表来使用,原版的沉船就使用该方式来存储。
通过直接编辑结构文件,我们可以绕过使用结构方块保存的一些限制。在游戏内保存好大致的结构后,使用NBT编辑器打开:
- 通过直接修改结构的
palettes
,可以将设置随机的结构方块状态列表; - 通过直接修改结构的
size
,可以将结构设置为大于48的区域; - 通过修改
blocks
的pos
,我们可以指定长方体区域外位置的方块; - 通过修改
entities
,我们可以删除实体不需要保存的一些NBT,这通常包括Air, FallDistance, Fire, Motion, NoGravity, OnGround, PortalCooldown, Pos, Rotation, UUID
等,blockPos
也可以删除。
这些技巧的使用请参考任意纯方块结构。
如果结构用于结构地物中,当结构只有部分在已加载区块中时,系统会根据结构的size
来确定还有哪些部分没有被加载。blocks
的pos
不在长方体区域内,这可能会导致生成的结构不完整。
§2.10 标签
标签 (Tag) 用于将多个具有相同性质的内容放置在一起以便于调用。Minecraft 中有很多内容都叫做标签,注意区分它们。
标签JSON格式
{ "replace": true 或 false (默认值), 可选,true 表示覆盖之前原版和其它数据包中该标签的内容,false 表示追加当前内容至该标签 "values": [ 字符串或JSON对象的列表 "minecraft:stone", 可以为相应类别的"命名空间ID" "#cpp:logs", 可以为相应类别标签的"#命名空间ID" { "id": "minecraft:dirt", 相应类别的"命名空间ID"或标签的"#命名空间ID" "required": true 或 false, false 表示该条目是可选的,解析可选条目失败不会影响其它条目的加载 } ] }
由于数据包加载次序难以控制,因此 "replace": true
的情形较为少用,一般仅用于覆盖原版标签。
标签的 required
设置为 false 时,可用于添加其他模组的相应ID。这在为数据包添加对模组的支持时很有用,例如cpp/tags/blocks/chests.json。而函数标签则为数据包添加附属和扩展提供了方便的接口。
标签共包括6种,分别用于不同情形。
§2.10.1 方块标签
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/tags/blocks/路径/文件名.json |
方块标签 (Block tag) 可以在用命令测试方块时以 #命名空间ID
的格式调用,只要方块满足了该标签中定义的任何一个方块,命令就会测试通过。方块标签还可在进度、断言、战利品表中以 "tag": "命名空间ID"
的格式调用。调试模式(F3)下,玩家指向方块时会显示含有该方块的所有方块标签。
原版方块标签被用于各种方块属性,我们按照功能分类并列出。我们不列出仅用于其它标签、进度、断言、战利品表、影响生物寻路和影响方块连接的方块标签。
方块标签 | 用途 |
---|---|
minecraft:enderman_holdableats | 末影人可以捡起这些方块 |
minecraft:dragon_immune | 末影龙无法破坏这些方块 |
minecraft:wither_immune | 凋灵无法破坏这些方块 |
minecraft:wither_summon_base_blocks | 可以用来搭建凋灵的方块 |
minecraft:hoglin_repellents | 疣猪兽会远离这些方块 |
minecraft:logs | 鹦鹉可以在这些方块上栖息 影响周围树叶方块的方块状态 distance 树生长时可以替代这些方块 |
minecraft:strider_warm_blocks | 炽足兽不在这些方块当中时会打寒颤 |
minecraft:guarded_by_piglins | 玩家破坏这些方块会激怒附近的猪灵,不影响打开会激怒猪灵的方块 |
minecraft:piglin_repellents | 猪灵会避开这些方块 |
minecraft:flowers | 蜜蜂可以授粉并记住这些方块的位置 邻近的树苗生长后有几率带有蜂巢 |
minecraft:bee_growables | 当蜜蜂对这些植物进行授粉时,它们会生长一个阶段,从该标签中删除方块没有效果 |
minecraft:beehives | 如果将蜂巢或蜂箱移除,则蜜蜂不再会为它们填充蜂蜜,用带有玻璃瓶或剪刀的发射器也不会清除其中的蜂蜜 |
minecraft:campfires | 蜜蜂、鹦鹉和海龟会尝试避开 营火使用这个标签来确定它们是否可被打火石点燃 喷溅型水瓶可将其方块状态变为 lit=false |
minecraft:sand | 海龟蛋可以在这些方块上孵化 |
minecraft:beds | 猫可以坐或睡在这些方块上 村民可以睡在这些方块上 幼年村民可以跳到这些方块上 |
minecraft:prevent_mob_spawning_inside | 生物不能在这些方块里生成 |
minecraft:valid_spawn | 用于确定位置是否是玩家的有效出生位置 |
minecraft:climbable | 玩家进入该方块时是否可以攀爬 |
minecraft:soul_speed_blocks | 穿着附有灵魂疾行魔咒的靴子在这些方块上的行走速度会得到提高 |
minecraft:wool | 这些方块可以被使用剪刀以5倍的速度破坏 放在这些方块上的音符盒会发出吉他音效 |
minecraft:leaves | 这些方块将剪刀识别为正确的工具,使用剑摧毁时速度是正常情况下的1.5倍,使用剪刀破坏的速度是正常情况下的15倍 不阻碍任何地形特征的生成 用于确定某些方块的是否可以放置其上 鹦鹉和豹猫可以在这些方块中生成 |
minecraft:portals | 当骑着一个实体的实体从其上脱离时,它将不会落在在这些方块中以防止不需要的传送,而是会脱离在被骑乘的实体的位置 |
minecraft:rails | 检测是否与铁轨相连。矿车是否可在此方块上行驶。检测是否可以放置矿车。向此标签添加其他方块会导致游戏崩溃。TNT矿车位于这些方块内时,不会破坏所在位置的方块和下方的方块。 |
minecraft:fences | 可以将拴绳连接到这些方块 |
minecraft:anvil | 如果将原版三种铁砧移除,则它们不再可以打开GUI 该标签包含的铁砧的下落方块形式、以及其它方块的使用命令召唤的下落方块形式,可以伤害实体 |
minecraft:underwater_bonemeals | 当在暖洋生物群系中在水下使用骨粉时,该标签中的方块将取代水源方块(在5个水平块和2个垂直块内)。如果该标记中的方块是自定义的,则该行为将应用于任何生物群系中的水源方块。这些方块在默认情况下不会含水 |
minecraft:jungle_logs | 可可豆可种植在这些方块上 |
minecraft:beacon_base_blocks | 可用于信标基座 |
minecraft:bamboo_plantable_on | 竹子可种植在该方块上 |
minecraft:mushroom_grow_block | 其之上的蘑菇可以无视光照放下且可以被骨粉催熟为巨型蘑菇 |
minecraft:soul_fire_base_blocks | 灵魂火会在这些方块上燃烧 |
minecraft:wall_post_override | 会使方块下方的墙变成柱子 |
minecraft:fire | 不影响且可用于激活有效的未激活下界传送门 不会阻挡沙子等方块掉落 能够扑灭火焰的药水会移除这些方块 |
minecraft:saplings | 树生长时可以替代这些方块 |
minecraft:base_stone_overworld | 生成世界时,主世界地下的石头中的点缀(砂砾堆、泥土堆、石头变种堆、矿脉、被虫蚀的方块)可以生成在这些方块的位置 |
minecraft:base_stone_nether | 生成世界时,下界的远古残骸可以生成在这些方块的位置 |
minecraft:corals | 用于生成珊瑚礁 |
minecraft:coral_blocks | 用于生成珊瑚礁 对着该方块顶部的单个海泡菜使用骨粉会使其生长出更多的海泡菜 |
minecraft:infiniburn_overworld | 维度类型为 minecraft:overworld 的维度中火焰可在这些方块上永久燃烧 |
minecraft:infiniburn_nether | 维度类型为 minecraft:the_nether 的维度中火焰可在这些方块上永久燃烧 |
minecraft:infiniburn_end | 维度类型为 minecraft:the_end 的维度中火焰可在这些方块上永久燃烧 |
minecraft:impermeable | 流体和蜂蜜颗粒不会穿过这些方块 |
检测玩家头部是否被方块卡住。
cpp/tags/blocks/fluid.json
{ "replace": false, "values": [ "minecraft:air", "minecraft:cave_air", "minecraft:void_air", "minecraft:bubble_column", "minecraft:water", "minecraft:lava", "#minecraft:fire" ] }
然后执行命令
cpp/functions/check.mcfunction
execute unless block ^ ^ ^ #cpp:fluid
因为命令执行地点为实体的脚,所以我们使用 anchored eyes
来使得局部坐标的位置变为玩家的眼睛。
让末影人无法拿起任何方块。
minecraft/tags/blocks/enderman_holdable
{ "replace": true, "values": [ ] }
§2.10.2 物品标签
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/tags/items/路径/文件名.json |
物品标签 (Item tag) 在配方、进度、断言、战利品表中用 "tag": "命名空间ID"
的格式使用。还可在创造模式物品栏输入 #命名空间ID
来搜索相应标签物品。物品标签命名空间ID命名空间:路径/文件名
对应的文件为 命名空间/tags/items/路径/文件名.json
。
原版物品标签被用于各种物品属性,我们按照功能分类并列出。我们不列出仅用于其它标签、配方、进度、断言、战利品表,影响生物寻路和影响方块连接的物品标签。
物品标签 | 用途 |
---|---|
minecraft:boats | 这些物品可以放入熔炉的燃料槽,并重置燃烧时间为 1200 刻 |
minecraft:banners | 这些物品可以放入熔炉的燃料槽,并重置燃烧时间为 300 刻 |
minecraft:logs | |
minecraft:planks | |
minecraft:wooden_pressure_plates | |
minecraft:wooden_stairs | |
minecraft:wooden_trapdoors | |
minecraft:wooden_buttons | |
minecraft:signs | 这些物品可以放入熔炉的燃料槽,并重置燃烧时间为 200 刻 |
minecraft:wooden_doors | |
minecraft:wooden_slabs | 这些物品可以放入熔炉的燃料槽,并重置燃烧时间为 150 刻 |
minecraft:saplings | 这些物品可以放入熔炉的燃料槽,并重置燃烧时间为 100 刻 |
minecraft:wool | |
minecraft:carpets | 这些物品可以放入熔炉的燃料槽,并重置燃烧时间为 67 刻 |
minecraft:non_flammable_wood | 这些物品不能被用作熔炉的燃料 |
minecraft:piglin_loved | 成年猪灵会捡起这些物品 |
minecraft:piglin_repellents | 猪灵不会捡起这些物品 |
minecraft:flowers | 这些物品可以用来繁殖蜜蜂 |
minecraft:small_flowers | 这些物品可用于喂食棕色哞菇 移除会使其不能用于合成迷之炖菜 |
minecraft:fishes | 海豚会游向持有这些物品的玩家,用它们喂养海豚会使海豚“信任”你 |
minecraft:creeper_drop_music_discs | 苦力怕被骷髅杀死后可以掉落的物品 |
minecraft:arrows | 这些物品可以像箭一样被射出和捡起 |
minecraft:beacon_payment_items | 这些物品可以放置在信标GUI中以选择效果 |
minecraft:planks | 这些物品可以在铁砧中修复木质工具和盾牌 |
让纸可以作为熔炉燃料使用,每次可烧炼一个物品。
minecraft/tags/items/wooden_doors
{ "replace": false, "values": [ "minecraft:paper" ] }
§2.10.3 函数标签
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/tags/functions/路径/文件名.json |
函数标签 (Function tag) 可以在 function
命令中以 #命名空间ID
的形式调用,所有在该标签中指定的函数都会按照它们出现的顺序执行,不会重复执行。函数标签命名空间ID命名空间:路径/文件名
对应的文件为 命名空间/tags/functions/路径/文件名.json
。
minecraft
命名空间中的标签
minecraft/tags/functions/load.json
中的函数会在加载时被执行一次,我们常称之为加载函数、load函数。用于初始化的函数应当添加至该标签。minecraft/tags/functions/tick.json
中的函数每刻会被执行一次,我们常称之为循环函数、主函数、tick函数。需要高频执行的函数应当添加至该标签。
本文中我们有时会不加注明地使用命名空间:load
和命名空间:tick
表示这两个函数标签中的函数。
§2.10.4 实体类型标签
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/tags/entity_types/路径/文件名.json |
实体类型标签 (Entity type tag) 标签可以在实体选择器参数 type
以及战利品表条件中以 #命名空间ID
的形式调用,只要实体满足了该标签中定义的任何一个类型,就会被选中。实体类型标签命名空间ID命名空间:路径/文件名
对应的文件为 命名空间/tags/entity_types/路径/文件名.json
。
原版实体类型标签被用于各种实体属性,我们在此列出。
物品标签 | 用途 |
---|---|
arrows | 使用在瞄准目标进度中 |
beehive_inhabitors | 这些实体可以进入蜂箱。 |
impact_projectiles | 用来决定哪些实体可以击破紫颂果,只有标靶方块可以响应的实体才有效 |
raiders | 决定敲钟时哪些实体获得发光效果,此标签中的实体在骑乘劫掠兽时不会覆盖劫掠兽的AI |
skeletons | 苦力怕在被这些实体杀死时掉落唱片 |
§2.10.5 流体标签
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/tags/fluids/路径/文件名.json |
流体标签 (Fluid tag) 标签很少使用,修改后会影响空气、水和熔岩的性质,其命名空间ID命名空间:路径/文件名
对应的文件为 命名空间/tags/fluids/路径/文件名.json
。
流体和流体对应的方块是不同的,例如 flowing_water
不是一个合法的方块,但流体可以为
{ "minecraft:water", "minecraft:flowing_water", "minecraft:lava", "minecraft:flowing_lava", "minecraft:air", "minecraft:cave_air", "minecraft:void_air" }
原版流体标签 minecraft:water
和 minecraft:lava
用来决定何种流体具有类似水和熔岩的性质,具体见标签#流体,我们在此省略。
§2.10.6 游戏事件标签
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/tags/game_events/路径/文件名.json |
游戏事件标签 (Game event tag) 标签目前仅用于指定影响潜声传感器的事件,其命名空间ID命名空间:路径/文件名
对应的文件为 命名空间/tags/game_events/路径/文件名.json
。
原版游戏事件标签 minecraft:vibrations
用于决定哪些事件会被潜声传感器检测到,minecraft:ignore_vibrations_stepping_carefully
用于决定哪些事件在玩家潜行时不会被潜声传感器检测到,具体见标签#游戏事件。
§2.11 维度和维度类型
维度和维度类型用于自定义维度。维度和自定义世界生成无法通过命令 /reload
在游戏内更新。
§2.11.1 维度
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/dimension/路径/文件名.json |
维度 (Dimension) 规定了维度的生成方式和生物群系。
噪声型维度的的地形由噪声设置 generator.settings
确定,生物群系由 generator.biome_source
的不同可分为五种情形,其中类型为 multi_noise
时,游戏会生成四维柏林噪声图,然后对每个坐标选择噪声最接近的生物群系。其中生物群系的 offset
会影响计算接近程度,从而影响该生物群系的大小,offset
越大,该生物群系越难被选中。
噪声型维度JSON格式
{ "type": "维度类型命名空间ID" 或 维度类型JSON对象, "generator": { "type":"minecraft:noise", "seed":整数, 种子 "settings":"噪声设置命名空间ID" 或 噪声设置JSON对象, "biome_source": { 生物群系设置 "seed":整数, 种子 "type": "minecraft:vanilla_layered", 原主世界默认生成和巨型生物群系生成 "large_biomes": true 或 false, 是否为巨型生物群系 或 "type": "minecraft:multi_noise", 多维噪声型 "min_quart_y": 整数, TBD "max_quart_y": 整数, TBD "biomes": [ 生物群系列表 { "biome": "生物群系命名空间ID", 生物群系 "parameters": { 生物群系属性 "humidity": -2~2 之间的数值范围, 振幅,柏林噪声参数 "erosion": -2~2 之间的数值范围, 振幅,柏林噪声参数 "temperature": -2~2 之间的数值范围, 振幅,柏林噪声参数 "weirdness": -2~2 之间的数值范围, 振幅,柏林噪声参数 "continentalness": -2~2 之间的数值范围, 振幅,柏林噪声参数 "depth": 数值, TBD "offset": 0~1 之间的数值 为地图上的点计算最接近的生物群系时,会根据这个值来调大距离,因此这个值绝对值越大,生物群系越小 } } ] 或 "type": "minecraft:fixed", 单生物群系维度 "biome": "生物群系命名空间ID" 或 "type": "minecraft:checkerboard", 生物群系单元呈正方形按棋盘状排列 "scale": 正整数, 正方形单元的大小 "biomes": [ 生物群系列表 "生物群系命名空间ID" ] 或 "type": "minecraft:the_end" 用于在末路之地中生成的生物群系,以生物群系minecraft:the_end为中心,周围环绕着其它生物群系 } } }
超平坦型维度仅需指定方块层的分布、生物群系和使用的结构参数即可。如果生成要塞,则要塞由原点向外逐个生成在一系列的环上,直到总数达到 count
。其中第 i
个环的要塞数量为 spread × (i+1) × (i+2)/6
向下取整,第 i
个环到原点的平均距离为 distance × (6i-2)
个区块。
超平坦型维度JSON格式
{ "type": "维度类型命名空间ID" 或 维度类型JSON对象, "generator": { "type": "minecraft:flat", "settings": { "layers": [ { "height": 正整数, 该层方块高度 "block": "方块命名空间ID" 该层使用方块 } ], "biome": "生物群系命名空间ID", 该维度使用的单一生物群系 "lakes": true 或 false, 可选,是否生成湖。如果设为true,则水湖和熔岩湖常会生成,即使在生物群系中湖通常不生成。熔岩湖生成时会被主世界不同种类的石头和矿物包围 "features": true 或 false, 可选,是否生成生物群系特有的装饰性结构,比如主世界的树木、花、草、仙人掌,下界的火/灵魂火、菌类、菌索等等 "structures": { 结构设置 "stronghold": { 可选,要塞的生成设置 "count": 1~4095 之间的整数, "spread": 0~1023 之间的整数, 设置为0时会使世界中某些位置上反复生成多个要塞 "distance": 0~1023 之间的整数 }, "structures": { 该维度中会生成的结构,如果需要生成要塞也需在此列出 "结构命名空间ID": { 注意这不是结构地物命名空间ID,自定义的结构地物生成几率设置随同其对应的原版结构 "spacing": 正整数, 两个该种类的结构之间的平均距离,以区块为单位 "separation": 1~(spacing-1) 之间的整数, 两个该种类的结构之间的最小距离,以区块为单位 "salt": 整数 影响结构内部生成 } } } } } }
调试世界维度仅用于调试世界,没有其它可选的设置。
调试世界维度JSON格式
{ "type": "维度类型命名空间ID" 或 维度类型JSON对象, "generator": { "type": "minecraft:debug" } }
原版的维度包括
minecraft:overworld
{ "type": "minecraft:overworld", "generator": { "biome_source": { "seed": 0, "large_biomes": false, "type": "minecraft:vanilla_layered" }, "seed": 0, "settings": "minecraft:overworld", "type": "minecraft:noise" } }
minecraft:the_nether
{ "type": "minecraft:the_nether", "generator": { "biome_source": { "humidity_noise": { "firstOctave": -7, "amplitudes": [ 1.0, 1.0 ] }, "altitude_noise": { "firstOctave": -7, "amplitudes": [ 1.0, 1.0 ] }, "weirdness_noise": { "firstOctave": -7, "amplitudes": [ 1.0, 1.0 ] }, "seed": 0, "biomes": [ { "parameters": { "altitude": 0.0, "weirdness": 0.0, "offset": 0.0, "temperature": 0.0, "humidity": 0.0 }, "biome": "minecraft:nether_wastes" }, { "parameters": { "altitude": 0.0, "weirdness": 0.0, "offset": 0.0, "temperature": 0.0, "humidity": -0.5 }, "biome": "minecraft:soul_sand_valley" }, { "parameters": { "altitude": 0.0, "weirdness": 0.0, "offset": 0.0, "temperature": 0.4, "humidity": 0.0 }, "biome": "minecraft:crimson_forest" }, { "parameters": { "altitude": 0.0, "weirdness": 0.0, "offset": 0.375, "temperature": 0.0, "humidity": 0.5 }, "biome": "minecraft:warped_forest" }, { "parameters": { "altitude": 0.0, "weirdness": 0.0, "offset": 0.175, "temperature": -0.5, "humidity": 0.0 }, "biome": "minecraft:basalt_deltas" } ], "temperature_noise": { "firstOctave": -7, "amplitudes": [ 1.0, 1.0 ] }, "type": "minecraft:multi_noise" }, "seed": 0, "settings": "minecraft:nether", "type": "minecraft:noise" } }
minecraft:the_end
{ "type": "minecraft:the_end", "generator": { "biome_source": { "seed": 0, "type": "minecraft:the_end" }, "seed": 0, "settings": "minecraft:end", "type": "minecraft:noise" } }
§2.11.2 维度类型
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/dimension_type/路径/文件名.json |
维度类型 (Dimension type) 规定维度的一些与地形无关的信息。
维度类型JSON格式
{ "has_ceiling": true 或 false, 该维度是否拥有一个基岩天花板,影响内部的算法,与实际是否拥有基岩天花板无关 "logical_height": 数值, 玩家使用紫颂果或下界传送门可以到达的最大高度 "bed_works": true 或 false, 玩家是否可以使用床 "respawn_anchor_works": true 或 false, 玩家是否可以使用重生锚 "has_raids": true 或 false, 带有不祥之兆的玩家是否可以触发袭击 "has_skylight": true 或 false, 该维度是否有天空光照 "ambient_light": 0~1 之间的数值, 该维度拥有多少环境光 "natural": true 或 false, 当为 false 时,指南针会随机转动;当为 true 时,下界传送门会生成僵尸猪灵 "piglin_safe:true 或 false, 猪灵和疣猪兽是否不会僵尸化 "coordinate_scale": 数值, 当前维度坐标放缩倍率,影响下界传送门和 execute in 指令使用相对坐标/局部坐标的情形 "ultrawarm": true 或 false, 维度是否表现得类似于下界水会蒸发,湿海绵会干。这也会使得熔岩流动更快、扩散更远 "fixed_time": 0~24000 之间的整数, 可选,游戏内的昼夜时间固定值, 缺省则有日夜循环 "min_y": 数值, 此维度中可以存在方块的最低高度 "height": 不超过4096的16的倍数, 此维度中可以存在方块的总高度。维度中可以存在方块的最大高度的值等于min_y与height值之和 "infiniburn": "方块标签命名空间ID" 决定该维度中火可以在什么方块上永久燃烧 }
可用的维度类型
这些是 Minecraft 内部已生成的维度类型命名空间ID,可直接使用。已省略 minecraft:。
minecraft:overworld
{ "logical_height": 256, "infiniburn": "minecraft:infiniburn_overworld", "effects": "minecraft:overworld", "ambient_light": 0.0, "respawn_anchor_works": false, "has_raids": true, "min_y": 0, "height": 256, "natural": true, "coordinate_scale": 1.0, "piglin_safe": false, "bed_works": true, "has_skylight": true, "has_ceiling": false, "ultrawarm": false }
minecraft:overworld_caves
{ "logical_height": 256, "infiniburn": "minecraft:infiniburn_overworld", "effects": "minecraft:overworld", "ambient_light": 0.0, "respawn_anchor_works": false, "has_raids": true, "min_y": 0, "height": 256, "natural": true, "coordinate_scale": 1.0, "piglin_safe": false, "bed_works": true, "has_skylight": true, "has_ceiling": true, "ultrawarm": false }
minecraft:the_nether
{ "logical_height": 128, "infiniburn": "minecraft:infiniburn_nether", "effects": "minecraft:the_nether", "ambient_light": 0.1, "respawn_anchor_works": true, "has_raids": false, "min_y": 0, "height": 256, "natural": false, "coordinate_scale": 8.0, "piglin_safe": true, "bed_works": false, "fixed_time": 18000, "has_skylight": false, "has_ceiling": true, "ultrawarm": true }
minecraft:the_end
{ "logical_height": 256, "infiniburn": "minecraft:infiniburn_end", "effects": "minecraft:the_end", "ambient_light": 0.0, "respawn_anchor_works": false, "has_raids": true, "min_y": 0, "height": 256, "natural": false, "coordinate_scale": 1.0, "piglin_safe": false, "bed_works": false, "fixed_time": 6000, "has_skylight": false, "has_ceiling": false, "ultrawarm": false }
§2.11.3 实例
定义好新的维度之后,我们还需要为其设计传送方式。简单的可以使用物品触发,见右键交互。更常见的则是使用多方块结构为传送检测方式。
定义一个新的维度,其中仅有繁花森林生物群系。使用高花摆放成八边形并在中间放置白色羊毛激活传送门。
cpp/dimension/flower.json
{ "type": "cpp:flower", "generator": { "type": "minecraft:noise", "seed": 20200602, "settings": { "bedrock_roof_position":256, "bedrock_floor_position": 0, "sea_level": 63, "disable_mob_generation": true, "default_block": { "Name": "minecraft:dirt" }, "default_fluid": { "Name": "minecraft:water", "Properties": { "level": "0" } }, "noise": { "top_slide": { "target": 0, "size": 1, "offset": 1 }, "bottom_slide": { "target": 0, "size": 1, "offset": 1 }, "sampling": { "xz_scale": 0.01, "xz_factor": 1.0, "y_scale": 0.01, "y_factor": 1.0 }, "size_vertical": 1, "size_horizontal": 1, "min_y": 0, "height": 128, "density_factor": 1.0, "density_offset": 0.0, "simplex_surface_noise": false }, "structures": { "structures": { "mineshaft": { "spacing": 2000, "separation": 1000, "salt": 20200602 }, "ruined_portal": { "spacing": 2000, "separation": 1000, "salt": 20200602 } } } }, "biome_source": { "biome": "minecraft:flower_forest", "type": "minecraft:fixed", "seed": 20200602 } } }
cpp/dimension_type/flower.json
{ "ultrawarm": false, "natural": false, "coordinate_scale": 1.0, "has_skylight": true, "has_ceiling": false, "ambient_light": 1, "fixed_time": 6000, "piglin_safe": false, "bed_works": true, "respawn_anchor_works": true, "has_raids": false, "logical_height": 256, "infiniburn": "minecraft:infiniburn_overworld" }
cpp/advancements/block/white_wool.json
{ "criteria": { "white_wool": { "trigger": "minecraft:placed_block", "conditions": { "block": "minecraft:white_wool" } } }, "rewards": { "function": "cpp:block/reset/white_wool" } }
cpp:block/reset/white_wool
advancement revoke @s only cpp:block/white_wool scoreboard players set #block_id cppValue 3 function cpp:misc/loc/pos # 旧版本中我们使用药水云作为标记实体 execute as @e[type=marker,distance=..9,tag=cpp_loc_block_pos] at @s align xyz if predicate cpp:flower_portal run function cpp:block/put/white_wool kill @e[type=marker,distance=..9,tag=cpp_loc_block_pos]
cpp:block/put/white_wool
summon marker ~0.5 ~ ~0.5 {Tags:["cpp_aec_marker","cpp_flower_portal"]} setblock ~ ~ ~ end_gateway
cpp/predicates/flower_portal.json
[ { "condition": "minecraft:location_check", "offsetX": 2, "offsetZ": 2, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } }, { "condition": "minecraft:location_check", "offsetX": 2, "offsetZ": -2, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } }, { "condition": "minecraft:location_check", "offsetX": -2, "offsetZ": 2, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } }, { "condition": "minecraft:location_check", "offsetX": -2, "offsetZ": -2, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } }, { "condition": "minecraft:location_check", "offsetX": 3, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } }, { "condition": "minecraft:location_check", "offsetX": -3, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } }, { "condition": "minecraft:location_check", "offsetZ": 3, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } }, { "condition": "minecraft:location_check", "offsetZ": -3, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } } ]
cpp/predicates/flower_portal_done.json
[ { "condition": "minecraft:location_check", "predicate": { "block": { "blocks": ["minecraft:end_gateway"] } } }, { "condition": "minecraft:location_check", "offsetX": 2, "offsetZ": 2, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } }, { "condition": "minecraft:location_check", "offsetX": 2, "offsetZ": -2, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } }, { "condition": "minecraft:location_check", "offsetX": -2, "offsetZ": 2, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } }, { "condition": "minecraft:location_check", "offsetX": -2, "offsetZ": -2, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } }, { "condition": "minecraft:location_check", "offsetX": 3, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } }, { "condition": "minecraft:location_check", "offsetX": -3, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } }, { "condition": "minecraft:location_check", "offsetZ": 3, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } }, { "condition": "minecraft:location_check", "offsetZ": -3, "predicate": { "block": { "tag": "minecraft:tall_flowers", "state": { "half": "lower" } } } } ]
检测传送门是否完整。检测玩家位置来传送。
cpp:tick
execute as @e[type=marker,tag=cpp_flower_portal] at @s unless predicate cpp:flower_portal_done run function cpp:misc/flower_portal execute as @a at @s[predicate=cpp:in_overworld,predicate=cpp:flower_portal_done] run function cpp:misc/to_flower execute as @a at @s[predicate=cpp:in_flower,predicate=cpp:flower_portal_done] run function cpp:misc/to_overworld
cpp:misc/flower_portal
kill @s setblock ~ ~ ~ white_wool
cpp/predicates/in_overworld.json
{ "condition": "minecraft:location_check", "predicate":{ "dimension": "minecraft:overworld" } }
cpp/predicates/in_flower.json
{ "condition": "minecraft:location_check", "predicate":{ "dimension": "cpp:flower" } }
cpp:misc/to_flower
execute in cpp:flower run tp ~ ~ ~ execute at @s run spreadplayers ~ ~ 1 10 false @s
cpp:misc/to_overworld
execute in overworld run tp ~ ~ ~ execute at @s run spreadplayers ~ ~ 1 10 false @s
§2.11.4 自定义世界
自定义世界是一种世界类型,它用自定的特性取代了世界的普通地形。创建世界时,点击更多选项->导入世界->选择JSON文件
即可。这种方式还可以用来创建额外的维度而不借助于数据包,但是默认只能使用命令 /execute in 维度名称
进入额外的维度。
JSON格式
{ "bonus_chest": true 或 false, 是否会生成奖励箱 "generate_features": true 或 false, 是否生成建筑 "seed": 整数, 种子 "dimensions": [ 维度列表 "minecraft:overworld": 维度JSON对象, 主世界 "minecraft:the_nether": 维度JSON对象, 下界 "minecraft:the_end": 维度JSON对象, 末地 "维度命名空间ID": 维度JSON对象 其它维度 ] }
§2.12 自定义世界生成
自定义世界生成 (Custom world generation) 允许数据包改变世界的生成方式,这需要和维度配合使用。本节中列出的很多JSON内容的作用仍然需要测试。点此可下载原版所有的 worldgen
内容 (slicedlime 提供)。注意原版的维度和维度类型并不保存在原版数据包中。
§2.12.1 噪声设置
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/worldgen/noise_settings/路径/文件名.json |
噪声设置 (noise_settings) 用于设置维度生成的噪声。噪声设置文件为JSON文件,格式可参考Noise Settings – Official Minecraft Wiki。噪声设置的命名空间ID命名空间:路径/文件名
对应的文件为 命名空间/worldgen/noise_settings/路径/文件名.json
。
当维度的高度或最低高度发生改变时,再次进入该维度会导致区块 (regions
文件夹下文件)重新生成,但实体 (entities
文件夹下文件)会保留。
噪声设置JSON格式
{ "bedrock_roof_position": 整数, 世界顶部Y坐标-基岩天花板的Y坐标, 如果它不在 0~世界高度范围 范围内, 则无基岩天花板 "bedrock_floor_position": 整数, 基岩地板的Y坐标-世界底部Y坐标,如果它不在 0~世界高度范围 范围内, 则无基岩地板 "sea_level": 整数, 海平面高度 "disable_mob_generation":true 或 false, 是否禁止生物生成 "noise_caves_enabled": true 或 false, 是否生成噪声洞穴 "aquifers_enabled": true 或 false, 是否生成蓄水层 "deepslate_enabled": true 或 false, 是否将世界底部石头替换为深板岩 "ore_veins_enabled": true 或 false, 是否生成矿脉 "min_surface_level": 整数, TBD "default_block": { 决定世界地形顶部下方生成的方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "default_fluid": { 海洋与湖泊所使用的方块 "Name": "命名空间ID", 流体 "Properties": { "流体状态名": "流体状态值" } }, "structures": { 结构设置,参考超平坦型维度JSON格式的结构设置 "stronghold": { 可选,要塞的生成设置 "count": 1~4095 之间的整数, "spread": 0~1023 之间的整数, 设置为0时会使世界中某些位置上反复生成多个要塞 "distance": 0~1023 之间的整数 }, "structures": [ 该维度中会生成的结构,如果需要生成要塞也需在此列出 "结构命名空间ID": { 注意这不是结构地物命名空间ID,自定义的结构地物生成几率设置随同其对应的原版结构 "spacing": 正整数, 两个该种类的结构之间的平均距离,以区块为单位 "separation": 1~(spacing-1) 之间的整数, 两个该种类的结构之间的最小距离,以区块为单位 "salt": 整数 影响结构内部生成 } ] } "noise": { 世界生成参数 "top_slide": { 世界地形的顶部曲线的设置,影响区域以地形顶部为中心,影响的高度为 size × size_vertical×4 "size": 非负整数, "target": 整数, 负值会使山丘更加圆润,正值会使其更加平坦 "offset": 整数 移动影响区域,正值向下移动,负值向上移动 }, "bottom_slide": { 世界地形的底部曲线的设置, 影响区域以地形底部为中心, 影响的高度为 size × size_vertical × 4 "size": 非负整数, "target": 整数, 负值会使浮岛底部更加圆润,正值会生成一个基岩地板 "offset": 整数 移动影响区域,正值向下移动,负值向上移动 }, "sampling": { "xz_scale": 正数值, 伸缩X和Z轴的噪声,更大的值会造成更复杂的水平形状 "xz_factor": 正数值, 平滑水平方向上的噪声 "y_scale": 正数值, 伸缩Y轴的噪声,更大的值会造成更复杂的垂直形状 "y_factor": 正数值 平滑垂直方向上的噪声 }, "size_vertical": 正整数, 改变陆块在Y轴上的比例。1至15之间的值可以逐渐地增加山丘高度,大于20的值将会使得所有陆地高于通常的海平面63,比32更高的值会使得通常的陆地高度到达100+ "size_horizontal": 正整数, 改变陆块在X和Z轴上的比例,生物群系不会因此改变 "min_y": -2048~2031 之间的整数, 此噪声中可以存在方块的最低高度 "height": 16的正整数倍, 世界的总高度,min_y + height 不能超过 2032 "density_factor": 数值, 地形自下而上的梯度的放缩比例,值越大地形越陡峭,负值会使地形上下颠倒。 "density_offset": -1~1 之间的数值, 地形中心高度的偏移比例 "random_density_offset": true 或 false (默认值), 可选,地形中心高度是否有额外的随机偏移比例 "simplex_surface_noise": true 或 false, 是否使用单形噪声替代柏林噪声来生成地形 "island_noise_override": true 或 false (默认值), 可选,该世界是否像末地一样以小岛环绕着主岛的形式生成 "amplified": true 或 false (默认值) 可选,是否为放大化地形 }, "octaves": { "erosion": { "firstOctave": -9, "amplitudes": [ 1.0, 1.0, 0.0, 1.0, 1.0 ] }, "weirdness": { "firstOctave": -7, "amplitudes": [ 1.0, 2.0, 1.0, 0.0, 0.0, 0.0 ] }, "shift": { "firstOctave": -3, "amplitudes": [ 1.0, 1.0, 1.0, 0.0 ] }, "temperature": { "firstOctave": -9, "amplitudes": [ 1.5, 0.0, 1.0, 0.0, 0.0, 0.0 ] }, "humidity": { "firstOctave": -7, "amplitudes": [ 1.0, 1.0, 0.0, 0.0, 0.0, 0.0 ] }, "continentalness": { "firstOctave": -9, "amplitudes": [ 1.0, 1.0, 2.0, 2.0, 2.0, 1.0, 1.0, 1.0, 1.0 ] } } }
可用的噪声设置
这些是 Minecraft 内部已生成的噪声设置命名空间ID,可直接使用。
minecraft:overworld 通常的主世界 minecraft:amplified 放大化的主世界 minecraft:nether 通常的下界 minecraft:caves 类似下界的洞穴却拥有主世界地形特征的世界 minecraft:end 通常的末地 minecraft:floating_islands 浮空岛屿
§2.12.2 生物群系
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/worldgen/biome/路径/文件名.json |
生物群系 (Biome) 规定了生物群系的特性,可用于维度JSON文件的相应参数。
生物群系JSON格式
{ "depth": 数值, 深度,用于地形噪声生成,正值被认为是陆地,负值被认为是海洋 "scale": 数值, 比例,用于地形噪声生成,越大地形越陡峭 "precipitation": "none", "rain" 或 "snow", 降雨量,分别表示永不下雨、下雨和下雪。若为 snow 则自然生成的兔子为红眼白毛或黑白斑点 "category": "none", "taiga", "extreme_hills", "jungle", "mesa", "plains", "savanna", "icy", "the_end", "beach", "forest", "ocean", "desert", "river", "swamp", "mushroom" 或 "nether", ocean 会采用海洋的温度体系;若玩家位于 ocean 和 river 水下会听到水下音乐而非一般的音乐;mushroom 不会生成僵尸围城和灾厄巡逻队;desert 且 precipitation 不为 snow 时生成的兔子为金黄色; water_ambient 类别的生物在 river 生成几率更低;海底神殿仅可能在 "ocean" 或 "river" 生成 "temperature": 数值, 温度, 控制草和树叶的颜色以及雪傀儡是否会受伤 "temperature_modifier": "none" (默认值) 或 "frozen", 可选,使用特殊的草温度选项 "downfall": 数值, 控制草和树叶的颜色, 超过 0.85 会使火焰蔓延更快 "player_spawn_friendly": true 或 false, 该生物群系是否对新玩家友好,世界重生点会被设置到定义为 true 的生物群系 "creature_spawn_probability": 0~1 之间的数值, 生物的基础生成成功率,值越低则该生物群系首次被加载时生成的生物越少 "effects": { "sky_color": RGB颜色的整数形式, 天空的颜色 "fog_color": RGB颜色的整数形式, 迷雾的颜色 "water_color": RGB颜色的整数形式, 水的颜色 "water_fog_color": RGB颜色的整数形式, 水下迷雾的颜色 "foliage_color": RGB颜色的整数形式, 可选,树叶的颜色,若无则根据该群系的 humidity 和 temperature 来设定 "grass_color": RGB颜色的整数形式, 可选,草的颜色,若无则根据该群系的 humidity 和 temperature 来设定 "grass_color_modifier": "none" (默认值), "dark_forest" 或 "swamp", 可选,使用特殊的草颜色 "particle": { 可选, 该生物群系的颗粒效果 "probability": 数值, 颗粒的生成几率 "options": { "type": "block" 或 "falling_dust", 摧毁方块的颗粒或浮起而未下落的方块颗粒 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } 或 "type": "dust", 粉状颗粒 "r": 0~1 之间的数值, 红色分量 "g": 0~1 之间的数值, 绿色分量 "b": 0~1 之间的数值, 蓝色分量 "scale": 数值 颗粒大小放缩 或 "type": "item", 消耗物品的颗粒 "id": "物品命名空间ID" "Count": 整数, 物品数量 "tag": "字符串" 物品NBT } }, "ambient_sound": "音效命名空间ID", 可选,环境音效 "mood_sound": { 可选,氛围音效 "sound": "音效命名空间ID", "tick_delay": 整数, TBD "block_search_extent": 整数, TBD "offset": 数值 TBD }, "additions_sound": { 可选,额外的音效 "sound": "音效命名空间ID", "tick_chance": 数值 TBD }, "music": { 可选,音乐 "sound": "音效命名空间ID", "min_delay": 整数, TBD "max_delay": 整数, TBD "replace_current_music": true 或 false 是否替换已播放的音乐 } }, "surface_builder": "地表生成器命名空间ID", "carvers": {可选 "air": [ 可选,用于填充空气的地形雕刻器列表 "地形雕刻器命名空间ID" ], "liquid": [ 可选,用于填充液体的地形雕刻器列表 "地形雕刻器命名空间ID" ], }, "features": [ 该列表共10项,生成时会根据它们生成地物的种子,然后按列表来依次生成每一项列表中的所有地物,可以为空列表。 [ "地物命名空间ID" 内部特征类型 RAW_GENERATION,原版未使用 ], [ "地物命名空间ID" 内部特征类型 LAKES,原版用于生成湖和熔岩湖 ], [ "地物命名空间ID" 内部特征类型 LOCAL_MODIFICATIONS,原版用于生成针叶林和冰刺之地的石头 ], [ "地物命名空间ID" 内部特征类型 UNDERGROUND_STRUCTURES,原版用于生成地牢和主世界化石 ], [ "地物命名空间ID" 内部特征类型 SURFACE_STRUCTURES,原版用于生成沙漠水井和蓝冰 ], [ "地物命名空间ID" 内部特征类型 STRONGHOLDS,原版未使用 ], [ "地物命名空间ID" 内部特征类型 UNDERGROUND_ORES,原版用于生成主世界石头中的矿脉、泥土砂砾石头变种和水下沙子砂砾粘土块堆 ], [ "地物命名空间ID" 内部特征类型 UNDERGROUND_DECORATION,原版用于生成主世界的被虫蛀的方块堆、下界砂砾和黑石堆、下界矿脉 ], [ "地物命名空间ID" 内部特征类型 VEGETAL_DECORATION,默认用于生成树、竹子、仙人掌、海带、其它地下和海底作物以及涌泉 注意树的位置是固定的,也就是说如果该列表有多个 "type": "minecraft:tree" 的地物,则只有第一个会生效,之后的生成会被阻挡。想要各种地物都有几率生成,需要使用随机选择的地物(WIP) ], [ "地物命名空间ID" 内部特征类型 TOP_LAYER_MODIFICATION,默认用于生成地表结冰 ] ], "starts": [ 该生物群系生成的结构地物列表 "结构地物命名空间ID" ], "spawners": { 生物生成 "生物分类": [ 包括 monster, creature, ambient, water_creature, water_ambient, misc { "type": "生物命名空间ID", "weight": 整数, 权重 "minCount": 整数, 最小数量 "maxCount": 整数 最大数量 } ] }, "spawn_costs": {为特定实体设置生成几率 "生物命名空间ID": {可选 "energy_budget": 数值, 该实体的生成预算,此值越大生成的越多 "charge": 数值 该实体的生成代价,此值越大生成的越少 } } }
可用的生物群系
这些是 Minecraft 内部已生成的生物群系命名空间ID,可直接使用。
minecraft:badlands minecraft:badlands_plateau minecraft:bamboo_jungle minecraft:bamboo_jungle_hills minecraft:basalt_deltas minecraft:beach minecraft:birch_forest minecraft:birch_forest_hills minecraft:cold_ocean minecraft:crimson_forest minecraft:dark_forest minecraft:dark_forest_hills minecraft:deep_cold_ocean minecraft:deep_frozen_ocean minecraft:deep_lukewarm_ocean minecraft:deep_ocean minecraft:deep_warm_ocean minecraft:desert minecraft:desert_hills minecraft:desert_lakes minecraft:dripstone_caves minecraft:end_barrens minecraft:end_highlands minecraft:end_midlands minecraft:eroded_badlands minecraft:flower_forest minecraft:forest minecraft:frozen_ocean minecraft:frozen_river minecraft:giant_spruce_taiga minecraft:giant_spruce_taiga_hills minecraft:giant_tree_taiga minecraft:giant_tree_taiga_hills minecraft:gravelly_mountains minecraft:ice_spikes minecraft:jungle minecraft:jungle_edge minecraft:jungle_hills minecraft:lukewarm_ocean minecraft:lush_caves minecraft:modified_badlands_plateau minecraft:modified_gravelly_mountains minecraft:modified_jungle minecraft:modified_jungle_edge minecraft:modified_wooded_badlands_plateau minecraft:mountains minecraft:mountain_edge minecraft:mushroom_fields minecraft:mushroom_field_shore minecraft:nether_wastes minecraft:ocean minecraft:plains minecraft:river minecraft:savanna minecraft:savanna_plateau minecraft:shattered_savanna minecraft:shattered_savanna_plateau minecraft:small_end_islands minecraft:snowy_beach minecraft:snowy_mountains minecraft:snowy_taiga minecraft:snowy_taiga_hills minecraft:snowy_taiga_mountains minecraft:snowy_tundra minecraft:soul_sand_valley minecraft:stone_shore minecraft:sunflower_plains minecraft:swamp minecraft:swamp_hills minecraft:taiga minecraft:taiga_hills minecraft:taiga_mountains minecraft:tall_birch_forest minecraft:tall_birch_hills minecraft:the_end minecraft:the_void minecraft:warm_ocean minecraft:warped_forest minecraft:wooded_badlands_plateau minecraft:wooded_hills minecraft:wooded_mountains
§2.12.3 地形雕刻器
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/worldgen/configured_carver/路径/文件名.json |
地形雕刻器 (Carver) 用来在地形中雕刻出空气或液体的区域,被调用于生物群系JSON文件的 carvers
。
地形雕刻器JSON格式
{ "type": "cave", "nether_cave", "canyon", "underwater_canyon" 或 "underwater_cave", 雕刻类型 "config": { "lava_level": { "above_bottom": 整数 熔岩层与世界最底部高度差 }, "aquifers_enabled": true 或 false, 是否使用蓄水层 "debug_settings": { TBD,调试设置 "water_state": { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "lava_state": { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "barrier_state": { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "air_state": { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } }, "probability": 0~1 之间的数值, 雕刻几率 "y": { TBD,雕刻的高度范围 "type": "minecraft:uniform" 或 "minecraft:biased_to_bottom", "min_inclusive": { "absolute": 正整数 或 "above_bottom": 正整数 }, "max_inclusive": { "absolute": 正整数 或 "below_top": 正整数 }, "inner": 正整数 仅 biased_to_bottom 类型适用 }, "yScale": 地形雕刻器数值, "horizontal_radius_multiplier": 地形雕刻器数值, "vertical_radius_multiplier": 地形雕刻器数值, "floor_level": 地形雕刻器数值, 仅 cave, nether_cave, underwater_cave 类型适用 "vertical_rotation": 地形雕刻器数值, 仅 canyon, underwater_canyon 类型适用 "shape": { 仅 canyon, underwater_canyon 类型适用 "horizontal_radius_factor": 地形雕刻器数值, "vertical_radius_default_factor": 地形雕刻器数值, "vertical_radius_center_factor": 地形雕刻器数值, "distance_factor": 地形雕刻器数值, "thickness": 地形雕刻器数值, "width_smoothness": 正整数 } } }
地形雕刻器数值
数值 或 { "type": "minecraft:uniform", 均匀分布 "value": { "min_inclusive": 数值, "max_exclusive": 数值 } } 或 { "type": "minecraft:trapezoid", 梯形分布 "value": { "min": 数值, "max": 数值, "plateau": 数值 } }
可用的地形雕刻器
这些是 Minecraft 内部已生成的地形雕刻器命名空间ID,可直接使用。
minecraft:canyon 峡谷 minecraft:underwater_canyon 水下峡谷 minecraft:cave 洞穴 minecraft:underwater_cave 水下洞穴 minecraft:nether_cave 下界洞穴 minecraft:ocean_cave 海洋洞穴 minecraft:prototype_canyon 预设峡谷 minecraft:prototype_cave 预设洞穴 minecraft:prototype_crevice 预设裂缝 minecraft:prototype_ocean_cave 预设海洋洞穴
§2.12.4 地表生成器
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/worldgen/configured_surface_builder/路径/文件名.json |
地表生成器 (Surface builder) 用来刻画地面的起伏和构成地形的默认方块,被调用于生物群系JSON文件中的 surface_builder
。
地表生成器JSON格式
{ "type": "default"默认, "mountain"山地, "shattered_savanna"破碎的热带草原, "gravelly_mountain"砂砾山地, "giant_tree_taiga"巨型针叶林, "swamp"沼泽, "badlands"恶地, "wooded_badlands"繁茂的恶地, "eroded_badlands"被风蚀的恶地, "frozen_ocean"冻洋, "nether"下界, "nether_forest"下界森林, "soul_sand_valley"灵魂沙峡谷, "basalt_deltas"玄武岩三角洲 或 "nope"无, "config": { "top_material": { 顶层方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "under_material": { 顶层以下的方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "underwater_material": { 水下方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } } }
可用的地表生成器
这些是 Minecraft 内部已生成的地表生成器命名空间ID,可直接使用。
minecraft:badlands minecraft:basalt_deltas minecraft:crimson_forest minecraft:desert minecraft:end minecraft:eroded_badlands minecraft:frozen_ocean minecraft:full_sand minecraft:giant_tree_taiga minecraft:grass minecraft:gravelly_mountain minecraft:ice_spikes minecraft:mountain minecraft:mycelium minecraft:nether minecraft:nope minecraft:ocean_sand minecraft:shattered_savanna minecraft:soul_sand_valley minecraft:stone minecraft:swamp minecraft:warped_forest minecraft:wooded_badlands
§2.12.5 地物
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/worldgen/configured_feature/路径/文件名.json |
地物 (Feature) 是指树、花、矿石等,被调用于生物群系JSON文件的 features
中。
地物JSON格式
{ "type": "minecraft:tree", "config": { "trunk_provider": 方块状态类型JSON对象, 树干 "trunk_placer": "fancy_trunk_placer" 时树干方块必须类似原木有方块状态 axis=x|y|z "leaves_provider": 方块状态类型JSON对象, 树叶 "foliage_placer": { 树叶样式 "radius": 均匀分布的整数 (minBase=0, maxBase=8, maxSpread=8), 半径 "offset": 均匀分布的整数 (minBase=0, maxBase=8, maxSpread=8), 位移 "type": "minecraft:acacia_foliage_placer"金合欢树叶型 或 "type": "minecraft:dark_oak_foliage_placer"深色橡树树叶型 或 "type": "minecraft:blob_foliage_placer"通常的橡树树叶和白桦树叶型, "minecraft:bush_foliage_placer"金字塔型, "minecraft:fancy_foliage_placer"球形树叶或 "minecraft:jungle_foliage_placer"丛林树型, "height": 0~16 之间的整数, 树叶高度和尺寸 或 "type": "minecraft:spruce_foliage_placer"云杉树叶型, "trunk_height": 均匀分布的整数 (minBase=0, maxBase=16, maxSpread=8) 树干高度 或 "type": "minecraft:pine_foliage_placer"稀疏云杉树叶型, "height": 均匀分布的整数 (minBase=0, maxBase=16, maxSpread=8) 树叶高度 或 "type": "minecraft:mega_pine_foliage_placer"双层稀疏云杉树叶型, "crown_height": 均匀分布的整数 (minBase=0, maxBase=16, maxSpread=8) 第二层树叶高度 }, "trunk_placer": { 树干样式 "type": "minecraft:straight_trunk_placer"竖直型,类似桦树, "minecraft:forking_trunk_placer"单分叉型,类似金合欢, "minecraft:giant_trunk_placer"2×2竖直型,类似大云杉, "minecraft:mega_jungle_trunk_placer"大丛林木型, "minecraft:dark_oak_trunk_placer"深色橡木型 或 "minecraft:fancy_trunk_placer"多分叉型,类似大橡树, "base_height": 0-32 之间的整数, 基础高度 "height_rand_a": 0-24 之间的整数, 与水平分叉有关的随机高度 "height_rand_b": 0-24 之间的整数 与竖直分叉有关的随机高度 }, "minimum_size": { 最小尺寸 "min_clipped_height": 0-80 之间的整数, 可选,最小剪切高度 "type": "minecraft:two_layers_feature_size", 两层 "limit": 0-81 之间的整数, 默认为 1, 可选,极限尺寸 "lower_size": 0-16 之间的整数, 默认为 0, 可选,最小尺寸 "upper_size": 0-16 之间的整数, 默认为 1 可选,最大尺寸 或 "type": "minecraft:three_layers_feature_size", 三层 "limit": 0-80 之间的整数, 默认为 1, 可选,极限尺寸 "upper_limit": 0-80 之间的整数, 默认为 1, 可选,最小尺寸 "lower_size": 0-16 之间的整数, 默认为 0, 可选,最小尺寸 "middle_size": 0-16 之间的整数, 默认为 1, 可选,最小尺寸 "upper_size": 0-16 之间的整数, 默认为 1 可选,最大尺寸 }, "decorators": [ 树干树叶上的装饰列表 { "type": "minecraft:trunk_vine"树干藤蔓 或 "minecraft:leave_vine"树叶藤蔓 或 "type": "minecraft:cocoa"可可豆 或 "minecraft:beehive"蜂巢, "probability": 0~1 之间的数值 几率 或 "type": "minecraft:alter_ground"地面方块替换, "provider": 方块状态类型JSON对象 } ], "max_water_depth": 整数, 默认为 0, 可选,树在水下最大可生成深度 "ignore_vines": true 或 false (默认值), 可选,是否忽略藤蔓 "heightmap": "WORLD_SURFACE_WG", "WORLD_SURFACE", "OCEAN_FLOOR_WG", "OCEAN_FLOOR", "MOTION_BLOCKING" 或 "MOTION_BLOCKING_NO_LEAVES" 高度地图,用于随机 } } { "type": "minecraft:flower"花 或 "minecraft:random_patch", 随机堆 "config": { "state_provider": 方块状态类型JSON对象, 所使用的方块 "block_placer": { "type": "minecraft:simple_block_placer" 单个 或 "type": "minecraft:double_plant_placer" 大型花 或 "type": "minecraft:column_placer", 柱子 "min_size": 数值, 最小尺寸 "extra_size": 数值 额外尺寸 }, "whitelist": [ TBD,白名单列表 { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } ], "blacklist": [ TBD,黑名单列表 { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } ] "tries": 数值, 默认为 128, 可选,尝试次数 "xspread": 数值, 默认为 7, 可选,x轴扩散范围 "yspread": 数值, 默认为 3, 可选,y轴扩散范围 "zspread": 数值, 默认为 7, 可选,z轴扩散范围 "xspread": 数值, 默认为 7, 可选,x轴扩散范围 "can_replace", : true 或 false (默认值), 可选,是否替换原方块 "project": true (默认值) 或 false, 可选,TBD "need_water": true 或 false (默认值) 可选,是否需要水 } } { "type": "minecraft:block_pile"方块堆 或 "minecraft:nether_forest_vegetation"下界森林草 "config": { "state_provider": 方块状态类型JSON对象, 所使用的方块 } } { "type": "minecraft:spring_feature"涌泉, "config": { "state": { 所使用的流体 "Name": "命名空间ID", 流体 "Properties": { "流体状态名": "流体状态值" } }, "requires_block_below": true (默认值) 或 false, 可选,下方是否需要方块 "rock_count": 数值, 默认为 4, 可选,TBD,岩石数量 "hole_count": 数值, 默认为 1, 可选,TBD,坑洞数量 "valid_blocks": [ TBD,有效的方块列表 "Name": "方块命名空间ID" ] } } { "type": "minecraft:emerald_ore"绿宝石矿, "config": { "target": { 目标方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "state": { 自身方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } } } { "type": "minecraft:huge_red_mushroom"巨型红色蘑菇 或 "minecraft:huge_brown_mushroom"巨型棕色蘑菇, "config": { "cap_provider": 方块状态类型JSON对象, 顶部方块 "stem_provider": 方块状态类型JSON对象, 柄部方块 "foliage_radius": 数值, 默认为 2 可选,顶部大小 } } { "type": "minecraft:iceberg"冰山, "minecraft:forest_rock"森林岩石 或 "minecraft:lake"湖, "config": { "state": { 方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } } } { "type": "minecraft:disk"圆盘 或 "minecraft:ice_patch"冰堆, "config": { "state": { 方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "radius": 均匀分布的整数 (minBase=0, maxBase=4, maxSpread=4) 半径 "half_height": 0~4 之间的整数, 高度的一半 "targets": { 目标方块列表 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } } } { "type": "minecraft:dripstone_cluster", 滴水石锥 "config": { "floor_to_ceiling_search_range": 1~512 之间的整数, 搜寻高度 "height": 均匀分布的整数, (minBase=1, maxBase=64, maxSpread=64), 高度 "radius": 均匀分布的整数, (minBase=1, maxBase=64, maxSpread=64), 半径 "max_stalagmite_stalactite_height_diff": 1~512 之间的整数, 石笋与钟乳石高度最大差 "height_deviation": 1~64 之间的整数, 高度偏差 "dripstone_block_layer_thickness": 均匀分布的整数, (minBase=0, maxBase=64, maxSpread=64), 滴水石块层厚度 "density": 0~1 之间的浮点数提供器, 密度 "wetness": 0~1 之间的浮点数提供器, 湿度 "chance_of_dripstone_column_at_max_distance_from_center": 0~1 之间的浮点数, 距离中心点的最大距离几率 "max_distance_from_edge_affecting_chance_of_dripstone_column": 0~64 之间的整数, 边缘最大距离,影响滴水石柱几率 "max_distance_from_center_affecting_height_bias": 0~64 之间的整数 中心最大距离,影响高度偏差 } } { "type": "minecraft:geode", 紫晶洞 "config": { "blocks": { 用来填充的方块 "filling_provider": 方块状态类型JSON对象, 内部填充方块 "inner_layer_provider": 方块状态类型JSON对象, 内层方块 "alternate_inner_layer_provider": 方块状态类型JSON对象, 镶嵌在内层的方块 "middle_layer_provider": 方块状态类型JSON对象, 中间层方块 "outer_layer_provider": 方块状态类型JSON对象, 外层方块 "inner_placements": [ 内层表面方块列表 { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } ] }, "layers": { "filling": 0.01~50之间的数值, 可选,默认为 1.7 "inner_layer": 0.01~50之间的数值, 可选,默认为 2.2 "middle_layer": 0.01~50之间的数值, 可选,默认为 3.2 "outer_layer": 0.01~50之间的数值 可选,默认为 4.2 }, "crack": { "generate_crack_chance": 0~1之间的数值, 可选,默认为 1 "base_crack_size": 0~1之间的数值, 可选,默认为 2 "crack_point_offset": 0~1之间的数值, 可选,默认为 2 }, "noise_multiplier": 0~1之间的数值, 可选,默认为 0.05 "use_potential_placements_chance": 0~1之间的数值, 可选,默认为 0.35 "use_alternate_layer0_chance": 0~1之间的数值, 可选,默认为 0.35 "placements_require_layer0_alternate": true(默认值) 或 false, 可选,默认为 0.05 "min_outer_wall_distance": 0~10之间的整数, 可选,默认为 4 "max_outer_wall_distance": 0~20之间的整数, 可选,默认为 6 "min_distribution_points": 0~10之间的整数, 可选,默认为 3 "max_distribution_points": 0~20之间的整数, 可选,默认为 5 "min_point_offset": 0~10之间的整数, 可选,默认为 1 "max_point_offset": 0~10之间的整数, 可选,默认为 3 "min_gen_offset": 整数, 可选,默认为 -16 "max_gen_offset": 整数 可选,默认为 16 } } { "type": "minecraft:glow_lichen", 发光地衣 "config": { "search_range": 1~64 之间的整数, 可选,默认为10,搜寻范围 "chance_of_spreading": 0~1 之间的数值, 可选,默认为0.5,扩散几率 "can_place_on_floor": true 或 false(默认值), 可选,是否可放置在地板上 "can_place_on_ceiling": true 或 false(默认值), 可选,是否可放置在天花板上 "can_place_on_wall": true 或 false(默认值), 可选,是否可放置在方块侧面上 "can_be_placed_on": [ 可放置于之上的方块列表 { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } ] } } { "type": "minecraft:large_dripstone", 大型滴水石 "config": { "floor_to_ceiling_search_range": 1~512 之间的整数, 可选,默认为50,从底部向上的搜寻高度 "column_radius": 均匀分布的整数, "height_scale": 0~20 之间的浮点数提供器, "max_column_radius_to_cave_height_ratio": 0~1 之间的数值, "stalactite_bluntness": 0.1~10 之间的浮点数提供器, "stalagmite_bluntness": 0.1~10 之间的浮点数提供器, "wind_speed": 0~2 之间的浮点数提供器, "min_radius_for_wind": 0~100 之间的整数, "min_bluntness_for_wind": 0~5 之间的数值 } } { "type": "minecraft:small_dripstone", 小型滴水石 "config": { "max_placements": 1~100 之间的整数, "empty_space_search_radius": 0~20 之间的整数, "max_offset_from_origin": 0~20 之间的整数, "chance_of_taller_dripstone": 0~1 之间的数值 可选,默认为0.2 } } { "type": "minecraft:ore"矿石 或 "minecraft:scattered_ore", 散在矿石 "config": { "state": { 方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "size": 0~64 的整数, 矿脉大小 "discard_chance_on_air_exposure": 0~1 之间的数值, 暴露在空气中时的丢弃率 "target": { 目标方块要求 "predicate_type": "minecraft:always_true" 断言类型,总是成立 或 "predicate_type": "minecraft:block_match" 断言类型,匹配方块 "block": "方块命名空间ID" 或 "predicate_type": "minecraft:blockstate_match", 断言类型,匹配方块状态 "block_state": { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } 或 "predicate_type": "minecraft:tag_match", 断言类型,匹配方块标签 "tag": "命名空间ID" 方块标签 或 "predicate_type": "minecraft:random_block_match", 断言类型,随机匹配方块 "block": "方块命名空间ID", "probability": 0~1 之间的数值 几率 或 "predicate_type": "minecraft:random_blockstate_match", 断言类型,随机匹配方块状态 "block_state": { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "probability": 0~1 之间的数值 几率 } } } { "type": "minecraft:end_spike"末地黑曜石柱, "config": { "crystal_invulnerable": true 或 false (默认值), 可选,末影水晶是否无敌 "spikes": [ 柱子列表 { "centerX": 整数, 默认为 0, 可选,柱子中心的x坐标 "centerZ": 整数, 默认为 0, 可选,柱子中心的z坐标 "radius": 整数, 默认为 0, 可选,柱子半径 "height": 整数, 默认为 0, 可选,柱子高度 "guarded": true 或 false (默认值) 可选,末影水晶周围是否有铁栏杆笼子保护 } "crystal_beam_target": [ 可选,治愈激光定位到的位置 X坐标, Y坐标, Z坐标 ] } } { "type": "minecraft:end_gateway"末地折跃门, "config": { "exit": [ 可选,当进入末地折跃门方块要把实体传送到的位置 目标位置的X坐标, 目标位置的Y坐标, 目标位置的Z坐标 ], "exact": true 或 false 是否把实体准确传送到指定的坐标而不是传送到这个坐标附近的位置 } } { "type": "minecraft:seagrass"海草 或 "minecraft:bamboo"竹子, "config": { "probability": 0~1 之间的数值 几率 } } { "type": "minecraft:sea_pickle"海泡菜, "config": { "count": 均匀分布的整数 (minBase=-10, maxBase=128, maxSpread=128) } } { "type": "minecraft:simple_block"单个方块, "config": { "to_place": 方块状态类型, 将要放置的方块 "place_on": [ 将要放置的位置下方方块列表 { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } ], "place_in": [ 将要放置的位置的方块列表 { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } ], "place_under": [ 将要放置的位置上方方块列表 { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } ] } } { "type": "minecraft:huge_fungus"巨型菌类, "config": { "valid_base_block": { 底部基座方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "stem_state": { 菌柄方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "hat_state": { 顶部方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "decor_state": { 装饰方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "planted": true 或 false (默认值) 可选,TBD } } { "type": "minecraft:basalt_columns"玄武岩柱子, "config": { "reach": 均匀分布的整数 (minBase=0, maxBase=2, maxSpread=1), TBD "height": 均匀分布的整数 (minBase=1, maxBase=5, maxSpread=5) 高度 } } { "type": "minecraft:delta_feature"三角洲, "config": { "contents": { 三角洲方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "rim": { 三角洲边缘方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "size": 均匀分布的整数 (minBase=0, maxBase=8, maxSpread=8) 三角洲尺寸 "rim_size": 均匀分布的整数 (minBase=0, maxBase=8, maxSpread=8) 三角洲边缘尺寸 } } { "type": "minecraft:netherrack_replace_blobs"下界岩斑纹, "config": { "target": { 替换的目标方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "state": { 方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } }, "radius": 均匀分布的整数 半径 } } { "type": "minecraft:fill_layer", 填充层 "config": { "height": 整数, 高度 "state": { 方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } } } { "type": "minecraft:basalt_pillar"玄武岩柱子 } { "type": "minecraft:blue_ice"蓝冰 } { "type": "minecraft:bonus_chest"出生奖励箱 } { "type": "minecraft:chorus_plant"紫颂植物 } { "type": "minecraft:coral_claw"珊瑚礁 } { "type": "minecraft:coral_mushroom"珊瑚礁 } { "type": "minecraft:coral_tree"珊瑚礁 } { "type": "minecraft:desert_well"沙漠水井 } { "type": "minecraft:end_island"末地岛 } { "type": "minecraft:fossil"化石 } { "type": "minecraft:freeze_top_layer"冰冻顶层 } { "type": "minecraft:glowstone_blob"荧石堆 } { "type": "minecraft:ice_spike"冰刺 } { "type": "minecraft:kelp"海带 } { "type": "minecraft:monster_room"地牢 } { "type": "minecraft:no_bonemeal_flower"不可由骨粉催生的花 } { "type": "minecraft:no_op"空白,用于替换已有地物 } { "type": "minecraft:freeze_top_layer"冰冻顶层 } { "type": "minecraft:freeze_top_layer"冰冻顶层 } 下面为地物修饰 { "type": "minecraft:random_selector"随机选择, "config": { "features": [ 该列表中地物会被随机选中 { "feature": "命名空间ID" 或 地物JSON对象, 地物 "chance": 0~1 之间的数值 被选中的几率 } ], "default": "命名空间ID" 或 地物JSON对象 当上述均没有被选中时的地物 } } 或 { "type": "minecraft:simple_random_selector"简化的随机选择, "config": { "features": [ 从该列表中等几率随机选择一个地物 { "feature": "命名空间ID" 或 地物JSON对象 地物 } ] } } 或 { "type": "minecraft:random_boolean_selector"随机二选一, "config": { "feature_true": "命名空间ID" 或 地物JSON对象, 地物 "feature_false": "命名空间ID" 或 地物JSON对象 地物 } } { "type": "minecraft:decorated"带装饰的地物, "config": { "feature": "命名空间ID" 或 地物JSON对象, 地物 "decorator": "命名空间ID" 或 装饰物JSON对象 装饰物 } }
上述JSON格式中的
方块状态类型
{ "type": "minecraft:plain_flower_provider"通常的花 或 "minecraft:forest_flower_provider"繁花森林的花 或 "type": "minecraft:simple_state_provider"简单情形 或 "minecraft:rotated_block_provider"可旋转, "state": { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } 或 "type": "minecraft:weighted_state_provider", 带权重的方块状态类型 "entries": [ 允许的列表 { "weight": 0~1 之间的数值, 权重 "data": { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } } ] }
均匀分布的整数可以为一个JSON对象或一个非负整数。
均匀分布的整数
{会从base
至base+spread
之间随机选取一个整数作为参数 "base": minBase~maxBase 之间的整数, "spread": 0~maxSpread 之间的整数, 如果无 maxSpread 限制, 则为任意非负整数 } 或 非负整数 固定值,位于 minBase~maxBase 之间
可用的地物
这些是 Minecraft 内部已生成的地物命名空间ID,可直接使用。已省略 minecraft:
。
acacia bamboo bamboo_light bamboo_vegetation basalt_blobs basalt_pillar birch birch_bees_0002 birch_bees_002 birch_bees_005 birch_other birch_tall blackstone_blobs blue_ice bonus_chest brown_mushroom_giant brown_mushroom_nether brown_mushroom_normal brown_mushroom_swamp brown_mushroom_taiga chorus_plant crimson_forest_vegetation crimson_fungi crimson_fungi_planted dark_forest_vegetation_brown dark_forest_vegetation_red dark_oak delta desert_well disk_clay disk_gravel disk_sand end_gateway end_gateway_delayed end_island end_island_decorated end_spike fancy_oak fancy_oak_bees_0002 fancy_oak_bees_002 fancy_oak_bees_005 flower_default flower_forest flower_plain flower_plain_decorated flower_swamp flower_warm forest_flower_trees forest_flower_vegetation forest_flower_vegetation_common forest_rock fossil freeze_top_layer glowstone glowstone_extra huge_brown_mushroom huge_red_mushroom iceberg_blue iceberg_packed ice_patch ice_spike jungle_bush jungle_tree jungle_tree_no_vine kelp_cold kelp_warm lake_lava lake_water large_basalt_columns mega_jungle_tree mega_pine mega_spruce monster_room mushroom_field_vegetation nether_sprouts oak oak_badlands oak_bees_0002 oak_bees_002 oak_bees_005 ore_andesite ore_blackstone ore_coal ore_debris_large ore_debris_small ore_diamond ore_diorite ore_dirt ore_emerald ore_gold ore_gold_deltas ore_gold_extra ore_gold_nether ore_granite ore_gravel ore_gravel_nether ore_infested ore_iron ore_lapis ore_magma ore_quartz_deltas ore_quartz_nether ore_redstone ore_soul_sand patch_berry_bush patch_berry_decorated patch_berry_sparse patch_brown_mushroom patch_cactus patch_cactus_decorated patch_cactus_desert patch_crimson_roots patch_dead_bush patch_dead_bush_2 patch_dead_bush_badlands patch_fire patch_grass_badlands patch_grass_forest patch_grass_jungle patch_grass_normal patch_grass_plain patch_grass_savanna patch_grass_taiga patch_grass_taiga_2 patch_large_fern patch_melon patch_pumpkin patch_red_mushroom patch_soul_fire patch_sugar_cane patch_sugar_cane_badlands patch_sugar_cane_desert patch_sugar_cane_swamp patch_sunflower patch_taiga_grass patch_tall_grass patch_tall_grass_2 patch_waterlilly pile_hay pile_ice pile_melon pile_pumpkin pile_snow pine plain_vegetation red_mushroom_giant red_mushroom_nether red_mushroom_normal red_mushroom_swamp red_mushroom_taiga seagrass_cold seagrass_deep seagrass_deep_cold seagrass_deep_warm seagrass_normal seagrass_river seagrass_simple seagrass_swamp seagrass_warm sea_pickle small_basalt_columns spring_closed spring_closed_double spring_delta spring_lava spring_lava_double spring_open spring_water spruce spruce_snowy super_birch_bees_0002 swamp_tree taiga_vegetation trees_birch trees_giant trees_giant_spruce trees_jungle trees_jungle_edge trees_mountain trees_mountain_edge trees_savanna trees_shattered_savanna trees_water twisting_vines vines void_start_platform warm_ocean_vegetation warped_forest_vegetation warped_fungi warped_fungi_planted weeping_vines
装饰物
装饰物JSON格式
{ "type": "minecraft:chance"随机, "lava_lake"岩浆湖, 或 "water_lake"湖泊 "config": { "chance": 0~1 之间的数值 生成几率 } } 或 { "type": "minecraft:count"生成的数量, "fire"火, "glowstone"荧石 或 "count_multilayer"多层数量 "config": { "count": 均匀分布的整数 (minBase=-10, maxBase=128, maxSpread=128) 数量 } } 或 { "type": "minecraft:count_noise", 生成的数量(噪声型) "config": { "noise_level": 数值, "below_noise": 整数, "above_noise": 整数 } } 或 { "type": "minecraft:count_noise_biased", 生成的数量(带偏差的噪声型) "config": { "noise_to_count_ratio": 整数, "noise_factor": 数值, "noise_offset": 数值, 默认为 0 可选 } } 或 { "type": "minecraft:count_extra", 生成的数量(带额外数量) "config": { "count": 整数, "extra_chance": 数值, "extra_count": 整数 } } 或 { "type": "minecraft:range", "minecraft:range_biased" 或 "minecraft:range_very_biased", 区间数值(无偏好、带偏好、带强烈偏好) "config": { "bottom_offset": 整数, 默认为 0, 可选 "top_offset": 整数, 默认为 0, 可选 "maximum": 整数, 默认为 0 可选 } } 或 { "type": "minecraft:depth_average", TBD "config": { "baseline": 整数, "spread": 整数 } } 或 { "type": "minecraft:carving_mask", TBD "config": { "step": "air" 或 "liquid", 雕刻器生成阶段 "probability": 0~1 之间的数值 几率 } } 或 { "type": "minecraft:decorated, TBD "config": { "outer": "装饰物命名空间ID" 或 装饰物JSON对象, TBD,外部装饰物 "inner": "装饰物命名空间ID" 或 装饰物JSON对象 TBD,内部装饰物 } }
可用的装饰物
这些是 Minecraft 内部已生成的装饰物命名空间ID,可直接使用。已省略 minecraft:
。
nope square heightmap heightmap_spread_double top_solid_heightmap heightmap_world_surface spread_32_above magma emerald_ore end_gateway dark_oak_tree iceberg end_island chance lava_lake water_lake count fire glowstone count_multilayer count_noise count_noise_biased count_extra range range_biased range_very_biased depth_average carving_mask decorated
§2.12.6 结构地物
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/worldgen/configured_structure_feature/路径/文件名.json |
结构地物 (Structure feature) 指定特定类型的结构以及结构的参数,被调用于生物群系JSON文件的 starts
。
结构地物JSON格式
{ "type": "minecraft:pillager_outpost"掠夺者前哨站, "minecraft:village"村庄 或 "minecraft:bastion_remnant"堡垒遗迹 "config": { "start_pool": "模板池命名空间ID", "size": 0~7 之间的整数 TBD } } 或 { "type": "minecraft:mineshaft"矿井, "config": { "type": "normal"普通 或 "mesa"平顶山 "probability": 0~1 之间的数值 几率 } } 或 { "type": "minecraft:ruined_portal"废弃传送门, "config": { "portal_type"类型: "standard"标准, "desert"沙漠, "jungle"丛林, "swamp"沼泽, "mountain"山地, "ocean"海洋 或 "nether"下界 } } 或 { "type": "minecraft:shipwreck"沉船, "config": { "is_beached": true 或 false (默认值)可选,是否搁浅 } } 或 { "type": "minecraft:ocean_ruin"海底废墟, "config": { "biome_temp"生物群系温度: "warm"暖水 或 "cold"冷水 "large_probability": 0~1 之间的数值, 几率 "cluster_probability": 0~1 之间的数值 成群的几率 } } 或 { "type": "minecraft:buried_treasure"被掩埋的宝藏, "config": { "probability": 0~1 之间的数值 几率 } }
可用的结构地物
这些是 Minecraft 内部已生成的结构地物命名空间ID,可直接使用。已省略 minecraft:
。
bastion_remnant buried_treasure desert_pyramid end_city fortress igloo jungle_pyramid mansion mineshaft mineshaft_mesa monument nether_fossil ocean_ruin_cold ocean_ruin_warm pillager_outpost ruined_portal ruined_portal_desert ruined_portal_jungle ruined_portal_mountain ruined_portal_nether ruined_portal_ocean ruined_portal_swamp shipwreck shipwreck_beached stronghold swamp_hut village_desert village_plains village_savanna village_snowy village_taiga
§2.12.8 模板池
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/worldgen/template_pool/路径/文件名.json |
模板池 (Template pool) 使用结构文件来生成结构,其中结构包含的拼图方块会工作以生成更复杂的结构。拼图方块是一个允许游戏使用较小模板生成结构的拼接方块:
name
名称,该拼图方块的标记。pool
目标池,指的是拼图方块将要使用的模板池。target
目标,与该拼图方块连接的另一拼图方块的name
。joint
连接类型,rollable
(默认值) 或aligned
。当拼图方块朝上或朝下时才有,其中aligned
指拼接的结构位置固定,rollable
指的是拼接的结构可随机旋转。final_state
转变为:指的是拼图方块在整个功能被放置后会转变的方块。默认为minecraft:air
。- 层数,
0~7
之间,指生成完之后拼接的结构继续生成的嵌套层数。 - 保留拼图方块,与其拼接的结构生成完之后其包含的拼图方块保留还是转变为
final_state
指定方块。
拼图方块目前仅用于生成掠夺者前哨站、村庄和堡垒遗迹类型的结构地物。这些结构的生成开始于包含拼图方块的模板。每个拼图方块都使用下列流程来生成另一个模板:
- 在目标池内随机选择一个模板。
- 在该模板的拼图方块,至少存在1个模板含有目标拼图方块。如果有多个方块符合条件,那么会随机选择其中的一个。
- 这会使两个拼图方块连接使得第2个拼图方块朝向第1个拼图方块并与之相邻。
- 最后,两个拼图方块会被替换为对应的“转变方块”设置的方块。
初始模板和附加模板里的拼图方块都会重复上述流程,直到没有剩余的拼图方块或者结构足够大为止。使用结构方块加载结构不能复现上述过程。
模板池JSON格式
{ "name": "命名空间ID", 该随机池的名称,用于拼图方块调用 "fallback": "模板池命名空间ID", 结构生成完毕且无法再尝试生成时,可连接另一个模板池继续生成 "elements": [ 结构列表 { "weight": 整数, 权重 "element": { "location": "结构文件命名空间ID", "projection": "rigid" 直接生成相应结构 或 "terrain_matching", 调整地形或结构来与地形匹配 "element_type": "minecraft:empty_pool_element"不生成任何结构, "minecaft:list_pool_element"生成一个地形, "minecraft:legacy_single_pool_element" 只使用location中的结构,使这整个元素变为残骸元素池 或 "minecraft:single_pool_element", 只使用location中的结构,使这整个元素变为单元素池 "processors": "处理器列表命名空间ID", "elements": [ 除了 empty_pool_element 以外均需该标签,格式与上一级标签相同 ] } } ] }
§2.12.9 处理器列表
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/worldgen/processor_list/路径/文件名.json |
处理器列表 (Processor list) 用于指定拼图方块生成结构之后拼图方块本身的处理方式。
处理器列表JSON格式
{ "processors": [ 处理器列表 { "processor_type": "minecraft:rule", "rules": [ 规则列表 { "position_predicate": 方块处理条件JSON对象, 结构生成前当前位置方块 "input_predicate": 方块处理条件JSON对象, 结构种当前位置方块 "location_predicate": 方块处理条件JSON对象, 结构生成前的方块处理条件 "output_state": { 条件满足时放置的方块 "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } } ] 或 "processor_type": "minecraft:block_rot", 随机移除方块 "integrity": 0~1 之间的数值 几率 或 "processor_type": "minecraft:protected_blocks", 保护相应标签的方块不被结构所替换 "value": 方块标签命名空间ID } ] }
方块处理条件JSON对象
{ "predicate_type": "always_true" 总是成立 } 或 { "predicate_type": "axis_aligned_linear_pos", 根据相应轴的方块距离结构初始位置的距离来线性插值通过几率 "axis": "x", "y" 或 "z", 计算哪条轴的距离 "min_dist": 正整数, 方块距离结构初始位置的最短距离 "max_dist": 正整数, 方块距离结构初始位置的最远距离 "min_chance": 0~1 之间的数值, 当方块距离结构初始位置为 min_dist 时该断言通过几率 "max_chance": 0~1 之间的数值, 当方块距离结构初始位置为 max_dist 时该断言通过几率,介于 min_dist 和 max_dist 之间的则线性插值 } 或 { "predicate_type": "block_match", 匹配方块 "block": "方块命名空间ID" } 或 { "predicate_type": "blockstate_match", 匹配方块状态 "block_state": { "Name": "方块命名空间ID", "Properties": { 需要指定该方块的所有方块状态 "方块状态名": "方块状态值" } } } 或 { "predicate_type": "random_block_match", 随机方块匹配 "block": "方块命名空间ID" "probability": 0~1 之间的数值, 方块匹配时的通过几率 } 或 { "predicate_type": "tag_match", 匹配方块标签 "tag": "方块标签命名空间ID" }
可用的处理器列表
这些是 Minecraft 内部已生成的处理器列表命名空间ID,可直接使用。已省略 minecraft:
。
bastion_generic_degradation bottom_rampart bridge empty entrance_replacement farm_desert farm_plains farm_savanna farm_snowy farm_taiga high_rampart high_wall housing mossify_10_percent mossify_20_percent mossify_70_percent rampart_degradation outpost_rot roof side_wall_degradation stable_degradation street_plains street_savanna street_snowy_or_taiga treasure_rooms zombie_desert zombie_plains zombie_savanna zombie_snowy zombie_taiga
§3 资源包
参考资源包,教程/制作资源包。资源包 (resourcepacks) 可以极大地提升原版模组的美观程度,因此现在大部分原版模组都需要使用配套的资源包。资源包文件层次为
resourcepacks/资源包名称或资源包名称.zip/
pack.mcmeta pack.png assets 命名空间 lang zh_cn.json zh_tw.json en_us.json 其它语言.json blockstates 方块状态.json models 模型.json textures 方块状态.json font 字体.json sounds.json sounds 声音.ogg particles 颗粒.json shaders post 后处理着色器渲染管线.json program 着色器程序.json 顶点着色器.vsh 片段着色器.fsh core 核心着色器程序.json 顶点着色器.vsh 片段着色器.fsh texts 文本.txt
其中 assets
的文件结构和 §1.3.1 游戏文件结构中 versions/版本号/版本号.jar/assets
的结构是一样的。
assets
下所有文件和文件夹需使用小写英文、数字或-(折线),_(下划线),.(点)来命名,不可使用大写字母,所有文本文件为使用 UTF-8
编码的JSON文件,图片为 png
格式。资源包可以为文件夹格式或 zip 格式,发布时可将所有内容压缩为一个 zip 文件。压缩和解压的时候,注意文件层次,应当打开 zip 文件就可以看到资源包的 pack.mcmeta、pack.png 和 assets 文件夹。稳妥的做法是打开文件夹->全选->右键->发送到->压缩文件夹
。文件夹格式的资源包若有错误文件仍可以加载,但 zip
格式的会无法加载。
游戏内选择选项->资源包
可以列出所有的资源包。在资源包菜单中,加载次序由下至上,因此上方的资源包内容会覆盖下方的资源包同名内容。由于原版模组往往会修改原版物品自定义模型,因此同时安装多个原版模组的资源包时,很容易出现文件冲突,此时需要玩家手动合并资源包方可使用。具体而言,对比两个资源包的同名文件,将相同的模型文件的 overrides
合并,其它内容选择需要的留下即可。
若资源包位于 saves/世界名称/resources.zip
,则资源包会在进入存档时自动加载(最顶层)。
本节中我们仅对资源包做简单介绍,更详细的文件说明请参阅资源包 - Minecraft Wiki,最详细的官方我的世界百科或论坛纹理资源版相关内容。
纹理包和资源包是不同的东西,目前 Minecraft Java 只有资源包,请不要使用错误的名称。
§3.1 元信息和图标
Minecraft 通过文件 pack.mcmeta
来识别资源包,因此该文件是不可或缺的。例如:
pack.mcmeta
{ "pack": { "pack_format": 6, 资源包版本,1.13-1.14 版本为 4,1.15 版本为 5,1.16.2-1.16.5 版本为 6,1.17-1.17.1 版本为 7,1.18–1.18.2 版本为8,1.19 版本为9 "description": [ 资源包描述 { "text": "Crafting++ v1.10 ", "color": "gold" }, { "translate": "options.resourcepack" }, { "text": "\nruhuasiyu RubberTree", "color": "green" } ] }, "filter": { "block": [忽略相应命名空间下相应文件 { "namespace": "命名空间", "path": "文件路径" 支持通用标识符 * } ] } }
其中资源包描述为单个字符串或一个原始JSON文本。在资源包菜单页面,它会显示在资源包名称下方。
原版资源包里的 pack.mcmeta
文件为
pack.mcmeta
{ "pack": { "pack_format": 6, "description": "The default data for Minecraft" } }
资源包可以包含一个 pack.png
,它是正方形的图片,用于在资源包菜单中显示。
§3.2 命名空间
命名空间 (Namespace) 为玩家自定义的、可操作的空间。使用独立的命名空间也有利于解决和他人的冲突。资源包下可以有多个命名空间,如果不同资源包中有相同的命名空间,则其中相同的文件名内容会根据加载先后顺序被覆盖。特别地,原版内容被保存在 minecraft
命名空间,想要修改和替换原版的模型纹理只需在你的资源包内建立 minecraft
命名空间和相应的同名文件并修改即可。
类型 | 调用格式(命名空间ID) | 文件路径 |
---|---|---|
模型 | 命名空间:路径/文件名 |
命名空间/models/路径/文件名.json |
纹理 | 命名空间:路径/文件名 |
命名空间/textures/路径/文件名.png |
字体 | 命名空间:路径/文件名 |
命名空间/font/路径/文件名.json |
声音 | 命名空间:路径/文件名 |
命名空间/sounds/路径/文件名.ogg |
若命名空间为 minecraft
,则可直接省略 minecraft:
。
建议和数据包使用相同的命名空间名。
§3.3 语言文件
文件路径 | 命名空间/lang/语言代码.json |
---|
语言文件 (Lang) 可以放在任一命名空间下的 lang
文件夹下。原版的语言文件除了 en_us.json
位于原版资源包外,其它的语言文件位于资源文件夹下。
语言文件中可以使用样式代码来实现彩色文字,但我们更建议使用原始JSON文本来实现,以避免在铁砧上重命名时出现多余的字符。建议至少支持简体中文(zh_cn
)、繁体中文(zh_tw
)和英文(en_us
)三种语言,建议所有物品、进度、游戏提示等文本均采用 translate
文本。
assets/custom/lang/zh_cn.json
{ "item.ex.magnet":"磁铁", ... }
assets/custom/lang/en_us.json
{ "item.ex.magnet":"Magnet", ... }
也可以用此方法修改物品的默认名称等内容。例如:
assets/custom/lang/zh_cn.json
{ "item.minecraft.potion.effect.empty": "神秘药水", ... }
则未知的药水的名称不再是不可合成的药水
,而是神秘药水
。
translate
还有个有趣但不一定实用的用法。
assets/custom/lang/zh_cn.json
{ "The":"原版模组《更多的合成》已成功加载,", "resourcepack":"版本", "does":"1.9.1a.", "not":"更多内容请", "Install":"点击此处进入wiki.", "correctly":"作者:", "or":"ruhuasiyu,", "Launch":"RubberTree", ... }
则在数据包中输出相应文本时,若无资源包或资源包错误,则会显示 The resourcepack does not Installl correctly or Launch
以提示玩家资源包未加载。
§3.4 自定义物品模型
由于1.14添加了 CustomModelData
,我们可以拥有几乎无穷多的物品模型。为了便于他人整合和解决冲突,建议将 CustomModelData
的前四位固定以确定模组的独有区段,后四位作为模组不同模型的值,例如 12340000-12349999
之间。此外,建议 CustomModelData
不要超过 16777216
,原因见 custom_model_data 的使用限制。
我们会给出一个例子来说明实现流程。首先在 minecraft
下的胡萝卜钓竿物品模型中添加额外的模型 (overrides
),并引用我们自定义的模型。
assets/minecraft/models/item/carrot_on_a_stick.json
{ "parent": "item/handheld_rod", "textures": { "layer0": "item/carrot_on_a_stick" }, "overrides": [ { "predicate": { "custom_model_data": 12970001 }, "model": "cpp:element/blue_force_of_sky"}, { "predicate": { "custom_model_data": 12970002 }, "model": "cpp:element/green_force_of_water"}, { "predicate": { "custom_model_data": 12970003 }, "model": "cpp:element/cyan_force_of_mountain"}, { "predicate": { "custom_model_data": 12970004 }, "model": "cpp:element/orange_force_of_dirt"}, { "predicate": { "custom_model_data": 12970005 }, "model": "cpp:element/yellow_force_of_earth"}, { "predicate": { "custom_model_data": 12970006 }, "model": "cpp:element/red_force_of_fire"} ] }
注意 custom_model_data
需要按照从小往大的次序,否则会导致后面的覆盖前面的。然后创建自定义的模型
assets/cpp/models/element/red_force_of_fire.json
{ "parent": "item/handheld", "textures": { "layer0": "cpp:element/red_force_of_fire" } }
最后绘制纹理,并将其保存为 assets/craftingpp/textures/element/red_force_of_fire.png
。这样我们就设计好了这个物品模型,在 §5 物品设计我们将会说明如何使用该模型。
§3.5 模型
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/models/路径/文件名.json |
模型 (Model) 是一个描述形状和指定纹理的JSON文件。模型可以理解成一些可以旋转的长方体的拼接,每个长方体的若干个面贴上了纹理图案。长方体的厚度可以为0,也可以只有若干个面。
只有 minecraft
命名空间下特定名称的文件才对应特定的物品/方块模型,例如 minecraft/models/item/carrot_on_a_stick.json
表示胡萝卜钓竿的物品模型,minecraft/models/block/stone.json
表示石头的方块模型。
我们通过几个例子来理解下模型文件的语法。
minecraft/models/item/handheld.json
{ "parent": "item/generated", "display": { "thirdperson_righthand": { "rotation": [ 0, -90, 55 ], "translation": [ 0, 4.0, 0.5 ], "scale": [ 0.85, 0.85, 0.85 ] }, "thirdperson_lefthand": { "rotation": [ 0, 90, -55 ], "translation": [ 0, 4.0, 0.5 ], "scale": [ 0.85, 0.85, 0.85 ] }, "firstperson_righthand": { "rotation": [ 0, -90, 25 ], "translation": [ 1.13, 3.2, 1.13 ], "scale": [ 0.68, 0.68, 0.68 ] }, "firstperson_lefthand": { "rotation": [ 0, 90, -25 ], "translation": [ 1.13, 3.2, 1.13 ], "scale": [ 0.68, 0.68, 0.68 ] } } }
cpp/models/head/rabbit.json
{ "parent": "block/block", "gui_light": "side", "elements": [ { "from": [ 5.5, 6, 5.5 ], "to": [ 10.5, 10, 10.5 ], "faces": { "east": { "uv": [ 8, 2.5, 9.25, 4.5 ], "texture": "#layer" }, "north":{ "uv": [ 9.25, 2.5, 10.5, 4.5 ], "texture": "#layer"}, "west": { "uv": [ 10.5, 2.5, 11.75, 4.5 ], "texture": "#layer" }, "south":{ "uv": [ 11.75, 2.5, 13, 4.5 ], "texture": "#layer" }, "up": { "uv": [ 9.25, 0, 10.5, 2.5 ], "texture": "#layer", "rotation": 180}, "down": { "uv": [ 10.5, 0, 11.75, 2.5 ], "texture": "#layer" } } }, { "from": [ 7.5, 7.5, 5 ], "to": [ 8.5, 8.5, 6 ], "faces": { "east": { "uv": [ 8, 5, 8.25, 5.5 ], "texture": "#layer" }, "north":{ "uv": [ 8.25, 5, 8.5, 5.5 ], "texture": "#layer"}, "west": { "uv": [ 8.5, 5, 8.75, 5.5 ], "texture": "#layer" }, "south":{ "uv": [ 8.75, 5, 9, 5.5 ], "texture": "#layer" }, "up": { "uv": [ 8.25, 4.5, 8.5, 5 ], "texture": "#layer", "rotation": 180}, "down": { "uv": [ 8.5, 4.5, 8.75, 5 ], "texture": "#layer" } } }, { "from": [ 5.5, 10, 9.5 ], "to": [ 7.5, 15, 10.5 ], "rotation": { "origin": [6.5, 10, 10], "axis": "y", "angle": -22.5 }, "faces": { "east": { "uv": [ 14.5, 0.5, 14.75, 3 ], "texture": "#layer" }, "north":{ "uv": [ 14.75, 0.5, 15.25, 3 ], "texture": "#layer"}, "west": { "uv": [ 15.25, 0.5, 15.5, 3 ], "texture": "#layer" }, "south":{ "uv": [ 15.5, 0.5, 16, 3 ], "texture": "#layer" }, "up": { "uv": [ 14.75, 0, 15.25, 0.5 ], "texture": "#layer", "rotation": 180}, "down": { "uv": [ 15.25, 0, 15.75, 0.5 ], "texture": "#layer" } } }, { "from": [ 8.5, 10, 9.5 ], "to": [ 10.5, 15, 10.5 ], "rotation": { "origin": [9.5, 10, 10], "axis": "y", "angle": 22.5 }, "faces": { "east": { "uv": [ 13, 0.5, 13.25, 3 ], "texture": "#layer" }, "north":{ "uv": [ 13.25, 0.5, 13.75, 3 ], "texture": "#layer"}, "west": { "uv": [ 13.75, 0.5, 14, 3 ], "texture": "#layer" }, "south":{ "uv": [ 14, 0.5, 14.5, 3 ], "texture": "#layer" }, "up": { "uv": [ 13.25, 0, 13.75, 0.5 ], "texture": "#layer", "rotation": 180}, "down": { "uv": [ 13.75, 0, 14.25, 0.5 ], "texture": "#layer" } } } ], "display": { "head": { "scale": [ 3.28, 3.28, 3.28 ] }, "gui": { "rotation": [ 30, 225, 0 ], "translation": [0, -2, 0], "scale": [ 1.6, 1.6, 1.6 ] }, "ground": { "rotation": [ 0, 0, 0 ], "translation": [ 0, 3, 0], "scale":[ 0.64, 0.64, 0.64 ] }, "fixed": { "rotation": [ 0, 0, 0 ], "translation": [ 0, 0, 0], "scale":[ 1.28, 1.28, 1.28 ] }, "thirdperson_righthand": { "rotation": [ 75, 45, 0 ], "translation": [ 0, 2.5, 0], "scale": [ 0.96, 0.96, 0.96 ] }, "firstperson_righthand": { "rotation": [ 0, 45, 0 ], "translation": [ 0, 0, 0 ], "scale": [ 1.02, 1.02, 1.02 ] }, "firstperson_lefthand": { "rotation": [ 0, 225, 0 ], "translation": [ 0, 0, 0 ], "scale": [ 1.02, 1.02, 1.02 ] } }, "textures": { "particle": "entity/rabbit/brown", "layer": "entity/rabbit/brown" } }
含义 | 键 | 说明 |
---|---|---|
父模型 | parent |
继承指定位置的模型内容,相当于将其文件内容复制到该位置,可以缺省。Minecraft 已经定义了很多常见模型,我们可以直接调用而无需自己编写。对于自定义的多个类似的模型,我们也可以写好一个模板后继承之。常见的默认模型有
|
光照 | gui_light |
1.15.2版本起,表示光照方向,分为side(从左上背面打光)和front(从正面打光)。错误的选择会导致模型显得非常的暗。 |
显示 | display |
物品在不同位置的显示效果。 |
掉落物显示 | display.ground |
物品作为掉落物的显示效果。 |
头部显示 | display.head |
物品佩戴在生物头部的显示效果。 |
手持物显示 | display.firstperson_lefthand display.firstperson_righthand display.thirdperson_lefthand display.thirdperson_righthand
|
物品在第一人称左手、第一人称右手、第三人称左手、第三人称右手的显示效果。 |
展示框显示 | display.fixed |
物品在物品展示框内的显示效果。 |
展示框显示 | display.gui |
物品在玩家背包或容器内的显示效果。 |
变换 | display.[位置].scale display.[位置].translation display.[位置].rotation
|
物品在相应位置进行的放缩、平移和旋转。至多放缩至4倍,平移至±80。 |
元素 | elements |
列表的每个项确定一个长方体。 |
元素范围 | elements.from elements.to |
确定元素的范围。其三个坐标xyz范围为-16到32之间,不做放缩时,0~16即一个方块的完整大小。放置方块后,方块的x轴从右往左,y轴从下往上,z轴从近往远。在背包或展示框中时,x轴从左往右,y轴从上往下,z轴从远往近。 |
元素旋转 | rotation.origin rotation.axis rotation.angle |
将元素进行旋转,分别表示旋转中心、旋转的坐标轴、旋转的角度。其中角度只能是0, 22.5, 45, -22.5, -45 。 |
元素的面 | elements.face |
为方块的6个面纹理信息,东南西北上下6个面由xyz轴的方向确定,例如+x轴的面为东。 |
元素纹理引用 | elements.face.[方向].texture |
指定所使用的纹理。建议绘制纹理时按照元素的南面展开图来排列各个面的纹理,即 或分为6个单独的纹理。可以为纹理的引用路径或以#开头的变量,使用变量的好处是便于修改。 |
元素纹理范围 | elements.face.[方向].uv |
指定纹理的选取范围。将纹理放缩为16×16大小,该数值指定的便是相应区域。例如纹理文件为64×64大小,则 "uv": [ 3.5, 3, 4, 4 ] 使用的是距左上角横向14-16、纵向12-16的2×4个像素。 |
纹理 | textures |
指定元素纹理引用中的变量所指的纹理文件位置。可以缺省并在子模型中指定以确定最终模型。 |
颗粒纹理 | textures.particle |
指定作为物品或方块模型时,物品被损耗或方块被挖掘时显示的颗粒纹理。 |
更详细的语法参考模型 - Minecraft Wiki,最详细的官方我的世界百科。
如果我们循着模型 minecraft:item/handheld
往上,最终会得到模型 builtin/generated
。这种模型是内建的,不能通过资源包来修改。
由于 scale
至多为4,而 from to
至多为-16
到32
,因此模型最多可以放大至12
倍。想要更大的模型可以通过分段来旋转拼接而成,参阅
模型在不同位置仅有放缩旋转平移的差别,想要实现视觉上的明显不同,可参阅【1.14】物品头部/背包/手持显示不同纹理/模型。
方块状态我们一般不会用到,而且语法比较简单,可直接参阅 wiki,这里不做赘述。
§3.6 纹理
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/textures/路径/文件名.json |
纹理 (Texture) 的绘制需要用到诸如 Photoshop 之类的画图软件,具体请参考纹理版或网络上的相关教程。
当模型调用的纹理不存在时,会显示紫黑色的纹理。如果模型是正确的,你仍然可以看出正确的模型形状。
纹理可以是动态的。动态纹理需要高为宽的整数倍,除非指定了 width
和 height
。当纹理的高度为宽度的 x
倍时,可以有 x
帧,分别对应纹理自上而下均匀划分的 x
个块,从 0
开始。还需要一个 mcmeta
文件确定动画,例如 foo.png
对应
foo.png.mcmeta
{ "animation": { "interpolate": true 或 false, 可选,是否需要插值过渡,默认为 false "frametime": 正整数, 每一帧的默认时长,单位刻 "width": 数值, 每一帧的宽度,在帧不是正方形时需要该项来指定宽度 "height": 数值, 每一帧的高度,在帧不是正方形时需要该项来指定高度 "frames": [ 指定播放帧的次序 0, 帧的序号 1, 4, 3, {"index":2,"time": 2}, 单独指定这一帧的序号和时长,单位刻 3 ] } }
§3.7 音效
命名空间ID | 命名空间:路径/文件名 |
---|---|
音效文件路径 | 命名空间/sounds/路径/文件名.ogg |
sounds.json | 命名空间/sounds.json |
音效 (Sound) 文件为 assets/命名空间/sounds
文件夹下的 ogg
文件,它需要JSON文件 assets/命名空间/sounds.json
来调用。音效文件会覆盖之前资源包的同名文件,但 sounds.json
默认会追加到上一个资源包中的相应内容。原版的音效文件和 sounds.json
并不位于游戏本体资源包,而是位于资源文件夹下。
音效JSON格式
{ "声音事件名": { 声音事件名通常是按照类别以点(.)分割的 "replace": true 或 false (默认值), 替换还是追加之间的资源包中 sounds.json 的定义 "subtitle": "字符串", 可选,当游戏中开启了“显示字幕”时会在该声音事件被播放时将该字符串通过语言文件翻译为声音字幕 "sounds": [ 此声音事件触发时会随机选择列表中一个音效来播放 "音效文件命名空间ID" 或 { "type": "sound" (默认值) 或 "event", 可选,指定 name 的含义 "name": "音效文件命名空间ID" 或 "声音事件名", type 为 sound 时为音效文件命名空间ID,type 为 event 时为声音事件名 "volume": 0~1 之间的数值, 可选,默认为1,该音效的音量 "pitch": 数值, 可选,默认为1,该音效的音调 "weight": 数值, 可选,默认为1,该音效被选中的权重 "stream": true 或 false (默认值), 可选,是否以流式播放,当声音较长时设为true可避免卡顿,例如原版音乐和唱片 "attenuation_distance": 数值, 可选,默认为16,基于距离的音效大小衰减率,用于传送门、信标和潮涌核心 "preload": true 或 false (默认值), 可选,是否在加载资源包时就加载该音效,而不是在播放音效的时候再加载,水下环境音效为 true } ] } }
例:
assets/rf/sounds.json
{ "item.rf.juicer": { "subtitle": "item.rf.juicer", "sounds": [ "rf:juicer" ] }, "item.rf.swing": { "sounds": [ "rf:lightsaber/swing1", "rf:lightsaber/swing2", "rf:lightsaber/swing3", "rf:lightsaber/swing4" ] } }
assets/rf/lang/zh_cn.json
{ "item.rf.juicer": "饮用果汁", "item.rf.swing": "挥出光剑" }
§3.8 字体
命名空间ID | 命名空间:路径/文件名 |
---|---|
文件路径 | 命名空间/font/路径/文件名.json |
字体 (Font) 提供了每个字符对应的资源位置和额外信息,其中 minecraft:default
给出了默认字体的样式,而 minecraft:alt
给出了附魔台所用的默认字体。在原始JSON文本中,若未指定字体则为 minecraft:default
的字体样式。
字体JSON格式
{ "providers": [ { "type": "bitmap", 可以为 bitmap (位图),legacy_unicode (旧版Unicode字体),ttf (TrueType字体),我们只考虑第一种 "file": "minecraft:font/mana0.png", 这些字符的资源位置,需要包含文件扩展名 "chars": [ 一个字符串列表,列表所有元素的字符数必须相同 "\ue010" 每一项元素对应纹理等距分割的一行,每一项元素中的字符对应等距的一列 ], "height": 91, 可选,该字符的显示的像素高度,可以为负 "ascent": 85 该字符向上移动的像素高度,这个值会使得字符的显示结果出现垂直偏移 } ] }
如果 type
为 space
,则表示该字符表示空格,此时格式为
{
"providers": [
{
"type": "space",
"advances": {
"\ue010": 91,
该字符表示的空白长度
"\ue011": -10
}
]
}
我们来看一个例子。
assets/custom/font/default.json
{ "providers": [ { "type": "space", "advances": { " ": 4, "A": -1024, "\uF81A": -190 } }, { "type": "bitmap", "file": "custom:font/mana0.png", "height": 91, "ascent": 85, "chars": ["\ue010"] }, { "type": "bitmap", "file": "custom:font/mana1.png", "height": 91, "ascent": 85, "chars": ["\ue011"] } ] }
命令
tellraw @s {"text": "\ue010\ue011", "font": "custom:default"}
我们可以用 \u
开头来表示不容易打出的字符,参考Unicode® Character Table,这与直接使用相应字符是等价的。
对于单纯的空格,我们需要使用 space
字形提供器。
利用这种方式,我们可以做到在成书、玩家快捷栏上方、聊天区域、物品名称等地方显示图片。利用负长度空格和垂直偏移,我们可以将图片显示在屏幕的任一位置。
例:我们在自定义字体中将0~9和:的纹理放大了一倍,并将其移动到左上角,使得其显示比通常位置高。然后利用负宽度空格将字符位置重新定位到快捷栏左侧,显示第二行文字。
assets/cpp/font/default.json
{ "providers": [ { "type": "space", "advances": { " ": 4, "A": -1024, "\uF81A": -190 } }, { "type": "bitmap", "file": "cpp:font/immunized_from_poison.png", "height": 18, "ascent": 18, "chars": ["C"] }, { "type": "bitmap", "file": "cpp:font/immunized_from_blindness.png", "height": 18, "ascent": 18, "chars": ["D"] }, { "type": "bitmap", "file": "cpp:font/immunized_from_mining_fatigue.png", "height": 18, "ascent": 18, "chars": ["E"] }, { "type": "bitmap", "file": "cpp:font/ascii1.png", "height": 18, "ascent": 18, "chars": ["\uF910\uF911\uF912\uF913\uF914\uF915\uF916\uF917\uF918\uF919\uF91A\uF91B\uF91C\uF91D\uF91E\uF91F"] }, { "type": "bitmap", "file": "cpp:font/immunized_from_wither.png", "height": 8, "ascent": 8, "chars": ["F"] }, { "type": "bitmap", "file": "cpp:font/immunized_from_darkness.png", "height": 8, "ascent": 8, "chars": ["G"] }, { "type": "bitmap", "file": "cpp:font/weight_index.png", "height": 8, "ascent": 8, "chars": ["H"] }, { "type": "bitmap", "file": "cpp:font/chain.png", "height": 8, "ascent": 8, "chars": ["I"] }, { "type": "bitmap", "file": "cpp:font/immunized_from_mutation.png", "height": 8, "ascent": 8, "chars": ["J"] }, { "type": "bitmap", "file": "cpp:font/sculking.png", "height": 8, "ascent": 8, "chars": ["K"] } ] }
cpp:misc/fatness
data remove storage cpp:_ title setblock ~ 255 ~ oak_sign scoreboard players operation #t cppValue = @s cppVacPoi scoreboard players operation #t cppValue /= #3600 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t cppValue = @s cppVacPoi scoreboard players operation #t cppValue /= #600 cppValue scoreboard players operation #t cppValue %= #6 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t cppValue = @s cppVacPoi scoreboard players operation #t cppValue /= #60 cppValue scoreboard players operation #t cppValue %= #10 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t cppValue = @s cppVacPoi scoreboard players operation #t cppValue /= #10 cppValue scoreboard players operation #t cppValue %= #6 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t cppValue = @s cppVacPoi scoreboard players operation #t cppValue %= #10 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t cppValue = @s cppVacBli scoreboard players operation #t cppValue /= #3600 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t cppValue = @s cppVacBli scoreboard players operation #t cppValue /= #600 cppValue scoreboard players operation #t cppValue %= #6 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t cppValue = @s cppVacBli scoreboard players operation #t cppValue /= #60 cppValue scoreboard players operation #t cppValue %= #10 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t cppValue = @s cppVacBli scoreboard players operation #t cppValue /= #10 cppValue scoreboard players operation #t cppValue %= #6 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t cppValue = @s cppVacBli scoreboard players operation #t cppValue %= #10 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t cppValue = @s cppVacMin scoreboard players operation #t cppValue /= #3600 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t cppValue = @s cppVacMin scoreboard players operation #t cppValue /= #600 cppValue scoreboard players operation #t cppValue %= #6 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t cppValue = @s cppVacMin scoreboard players operation #t cppValue /= #60 cppValue scoreboard players operation #t cppValue %= #10 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t cppValue = @s cppVacMin scoreboard players operation #t cppValue /= #10 cppValue scoreboard players operation #t cppValue %= #6 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t cppValue = @s cppVacMin scoreboard players operation #t cppValue %= #10 cppValue function cpp:misc/number_to_ascii scoreboard players operation #t1 cppValue = @s cppVacWit scoreboard players operation #t1 cppValue /= #3600 cppValue scoreboard players operation #t2 cppValue = @s cppVacWit scoreboard players operation #t2 cppValue /= #600 cppValue scoreboard players operation #t2 cppValue %= #6 cppValue scoreboard players operation #t3 cppValue = @s cppVacWit scoreboard players operation #t3 cppValue /= #60 cppValue scoreboard players operation #t3 cppValue %= #10 cppValue scoreboard players operation #t4 cppValue = @s cppVacWit scoreboard players operation #t4 cppValue /= #10 cppValue scoreboard players operation #t4 cppValue %= #6 cppValue scoreboard players operation #t5 cppValue = @s cppVacWit scoreboard players operation #t5 cppValue %= #10 cppValue data modify block ~ 255 ~ Text1 set value '[{"score":{"name":"#t1","objective":"cppValue"}},":",{"score":{"name":"#t2","objective":"cppValue"}},{"score":{"name":"#t3","objective":"cppValue"}},":",{"score":{"name":"#t4","objective":"cppValue"}},{"score":{"name":"#t5","objective":"cppValue"}}]' scoreboard players operation #t1 cppValue = @s cppVacDar scoreboard players operation #t1 cppValue /= #3600 cppValue scoreboard players operation #t2 cppValue = @s cppVacDar scoreboard players operation #t2 cppValue /= #600 cppValue scoreboard players operation #t2 cppValue %= #6 cppValue scoreboard players operation #t3 cppValue = @s cppVacDar scoreboard players operation #t3 cppValue /= #60 cppValue scoreboard players operation #t3 cppValue %= #10 cppValue scoreboard players operation #t4 cppValue = @s cppVacDar scoreboard players operation #t4 cppValue /= #10 cppValue scoreboard players operation #t4 cppValue %= #6 cppValue scoreboard players operation #t5 cppValue = @s cppVacDar scoreboard players operation #t5 cppValue %= #10 cppValue data modify block ~ 255 ~ Text2 set value '[{"score":{"name":"#t1","objective":"cppValue"}},":",{"score":{"name":"#t2","objective":"cppValue"}},{"score":{"name":"#t3","objective":"cppValue"}},":",{"score":{"name":"#t4","objective":"cppValue"}},{"score":{"name":"#t5","objective":"cppValue"}}]' scoreboard players operation #t1 cppValue = @s cppVacMut scoreboard players operation #t1 cppValue /= #3600 cppValue scoreboard players operation #t2 cppValue = @s cppVacMut scoreboard players operation #t2 cppValue /= #600 cppValue scoreboard players operation #t2 cppValue %= #6 cppValue scoreboard players operation #t3 cppValue = @s cppVacMut scoreboard players operation #t3 cppValue /= #60 cppValue scoreboard players operation #t3 cppValue %= #10 cppValue scoreboard players operation #t4 cppValue = @s cppVacMut scoreboard players operation #t4 cppValue /= #10 cppValue scoreboard players operation #t4 cppValue %= #6 cppValue scoreboard players operation #t5 cppValue = @s cppVacMut scoreboard players operation #t5 cppValue %= #10 cppValue data modify block ~ 255 ~ Text3 set value '[{"score":{"name":"#t1","objective":"cppValue"}},":",{"score":{"name":"#t2","objective":"cppValue"}},{"score":{"name":"#t3","objective":"cppValue"}},":",{"score":{"name":"#t4","objective":"cppValue"}},{"score":{"name":"#t5","objective":"cppValue"}}]' execute as @s[scores={cppFat=..-100}] run data modify storage cpp:_ title append value '{"translate":"title.actionbar.fat0"}' execute as @s[scores={cppFat=-99..-50}] run data modify storage cpp:_ title append value '{"translate":"title.actionbar.fat1"}' execute as @s[scores={cppFat=-49..49}] run data modify storage cpp:_ title append value '{"translate":"title.actionbar.fat2"}' execute as @s[scores={cppFat=50..100}] run data modify storage cpp:_ title append value '{"translate":"title.actionbar.fat3"}' execute as @s[scores={cppFat=100..}] run data modify storage cpp:_ title append value '{"translate":"title.actionbar.fat4"}' title @s actionbar [{"text":"C ","font":"cpp:default"},{"storage":"cpp:_","nbt":"title[0]","interpret":true},"\uF91A",{"storage":"cpp:_","nbt":"title[1]","interpret":true},{"storage":"cpp:_","nbt":"title[2]","interpret":true},"\uF91A",{"storage":"cpp:_","nbt":"title[3]","interpret":true},{"storage":"cpp:_","nbt":"title[4]","interpret":true}," D ",{"storage":"cpp:_","nbt":"title[5]","interpret":true},"\uF91A",{"storage":"cpp:_","nbt":"title[6]","interpret":true},{"storage":"cpp:_","nbt":"title[7]","interpret":true},"\uF91A",{"storage":"cpp:_","nbt":"title[8]","interpret":true},{"storage":"cpp:_","nbt":"title[9]","interpret":true}," E ",{"storage":"cpp:_","nbt":"title[10]","interpret":true},"\uF91A",{"storage":"cpp:_","nbt":"title[11]","interpret":true},{"storage":"cpp:_","nbt":"title[12]","interpret":true},"\uF91A",{"storage":"cpp:_","nbt":"title[13]","interpret":true},{"storage":"cpp:_","nbt":"title[14]","interpret":true}," \uF81AF ",{"block":"~ 255 ~","nbt":"Text1","interpret":true,"font":"default"},{"text":" G ","font":"cpp:default"},{"block":"~ 255 ~","nbt":"Text2","interpret":true,"font":"default"},{"text":" J ","font":"cpp:default"},{"block":"~ 255 ~","nbt":"Text3","interpret":true,"font":"default"},{"text":" H ","font":"cpp:default"},{"score":{"name":"@s","objective":"cppFat"},"font":"default"},{"storage":"cpp:_","nbt":"title[15]","interpret":true,"font":"default"}] setblock ~ 255 ~ air
cpp:misc/number_to_ascii
execute if score #t cppValue matches 0 run data modify storage cpp:_ title append value '{"text":"\\uF910","font":"cpp:default"}' execute if score #t cppValue matches 1 run data modify storage cpp:_ title append value '{"text":"\\uF911","font":"cpp:default"}' execute if score #t cppValue matches 2 run data modify storage cpp:_ title append value '{"text":"\\uF912","font":"cpp:default"}' execute if score #t cppValue matches 3 run data modify storage cpp:_ title append value '{"text":"\\uF913","font":"cpp:default"}' execute if score #t cppValue matches 4 run data modify storage cpp:_ title append value '{"text":"\\uF914","font":"cpp:default"}' execute if score #t cppValue matches 5 run data modify storage cpp:_ title append value '{"text":"\\uF915","font":"cpp:default"}' execute if score #t cppValue matches 6 run data modify storage cpp:_ title append value '{"text":"\\uF916","font":"cpp:default"}' execute if score #t cppValue matches 7 run data modify storage cpp:_ title append value '{"text":"\\uF917","font":"cpp:default"}' execute if score #t cppValue matches 8 run data modify storage cpp:_ title append value '{"text":"\\uF918","font":"cpp:default"}' execute if score #t cppValue matches 9 run data modify storage cpp:_ title append value '{"text":"\\uF919","font":"cpp:default"}'
§3.9 着色器
着色器 (Shader) 用于描绘如何渲染游戏,包含后处理着色器和核心着色器。
§3.9.1 后处理着色器
后处理着色器渲染管线文件 | minecraft/shaders/post/文件名.json |
---|---|
着色器程序文件 | minecraft/shaders/program/文件名.json |
顶点着色器文件 | minecraft/shaders/program/文件名.vsh |
片段着色器文件 | minecraft/shaders/program/文件名.fsh |
后处理着色器会在游戏已经渲染好画面以后再起效,它们能够接受整个屏幕的像素作为输入,然后逐像素地输出。除了一些有限的特例以外,着色器所能接受到的唯一数据就是正显示在屏幕上的内容。我们只能通过在资源包替换 minecraft/shaders/post/文件名.json
下的下述文件来修改:
creeper.json
将在以苦力怕视角旁观时启用;invert.json
将在以末影人视角旁观时启用;spider.json
将在以蜘蛛视角旁观时启用;entity_outline.json
将在屏幕中有具有发光状态效果的实体时启用;transparency.json
将在玩家启用了“极佳”图像品质时启用。
在后处理着色器渲染管线中,除了玩家自定义的缓冲层,系统还预设了
- 为发光着色器准备的、预先填充好的特殊缓冲层:
minecraft:main
(没有水、方块实体和其他一些东西) 和final
(纯色的实体,颜色是该实体所在队伍的颜色); - 为实体视角着色器准备的、预先填充好的特殊缓冲层:
minecraft:main
(所有内容都已渲染完成)。
当屏幕渲染完成时,若玩家处于上述5种情形,则会对调用相应的后处理着色器渲染管线文件来依次对缓冲层进行着色器程序操作,并最终返回到缓冲层 minecraft:main
。
后处理着色器渲染管线JSON格式
{ "targets": [ 缓冲层 "字符串" 使用默认窗口高度和宽度 或 { "name": "字符串", "width": 整数, 宽度 "height": 整数 高度 } ], "passes": [ { "name": "prog1", 着色器程序 "intarget": "minecraft:main", 输入缓冲层 "outtarget": "foo" 输出缓冲层 }, { "name": "prog2", "intarget": "foo", "auxtargets": [ 为着色器程序提供缓冲层或图片 { "name": "字符串", 该参数名,以便着色器程序能访问它 "id": "字符串", 由 targets 定义的缓冲区,或者 assets/命名空间/textures/effect 下的纹理(使用命名空间ID来引用) "width": 整数, 如果引用的是纹理,该项必须,指纹理的宽度 "height": 整数, 如果引用的是纹理,该项必须,指纹理的高度 "bilinear": true 或 false 如果引用的是纹理,该项必须,指纹理的缩放算法为双线性还是邻近 } ], "outtarget": "bar" }, { "name": "blit", blit 指仅复制缓冲层内容而不做任何修改 "uniforms": { 为着色器程序提供一个浮点数数组 { "name": "字符串", 将要传递的参数名 "values": 数值 }, "intarget": "bar", "outtarget": "minecraft:main" } ] }
我们可以在 minecraft/shaders/program
文件夹下创建自定义名称的着色器程序文件。其中顶点着色器会对每个顶点起效,将顶点的位置作为输入,并产生一个经过变换的位置作为输出。片段着色器会对每个像素起效,并逐像素产生输出层。
着色器程序JSON格式
{ "blend": { OpenGL混合设置,我们使用常用设置 "func": "add", "srcrgb": "one", "dstrgb": "zero" }, "vertex": "foo", 将要使用的顶点着色器 .vsh 文件的文件名。 "fragment": "foo", 将要使用的片段着色器 .fsh 文件的文件名。 "attributes": [ "Position" ], "samplers": [ { "name": "DiffuseSampler" }, 指由 intarget 给予的缓冲层的变量名 { "name": "DitherSampler" } 其它名称的需要由 auxtargets 给予 ], "uniforms": [ 全局变量,数据类型type为 float, vec2, vec3, vec4, int, ivec2, ivec3, ivec4, matrix4x4, matrix3x3 或 matrix2x2 { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, { "name": "InSize", "type": "float", "count": 2, "values": [ 1.0, 1.0 ] }, { "name": "OutSize", "type": "float", "count": 2, "values": [ 1.0, 1.0 ] }, { "name": "BlurDir", "type": "float", "count": 2, "values": [ 1.0, 1.0 ] }, { "name": "Radius", "type": "float", "count": 1, "values": [ 5.0 ] } ] }
这里全局变量的 count
为 values
数组的长度,当为浮点数 (float
) 时,实际为 count
长度的浮点数数组 float, vec2, vec3, vec4
;int
类似,为 int, ivec3, ivec3, ivec4
;矩阵为 matrix4x4, matrix3x3, matrix2x2
。
顶点着色器和片段着色器程序可参考原版着色器的 vsh, fsh
文件。着色器中的数据类型包括标量 bool, int, uint, float, double
,向量 bven, iven, uven, ven, dven
,n=2,3,4
为向量长度;矩阵 matn, matnxm
为列优先的矩阵,n,m=2,3,4
。构建变量时,可以使用其它变量作为其部分。全局变量可以被声明为
in
,只能被顶点着色器读取,它自动包含了当前顶点的 Position(位置)属性;uniform
,可以被顶点着色器与片段着色器读取,对所有的顶点或像素都会保持恒定不变,它们的值可以通过后处理 JSON 文件传入,否则将会使用在着色器程序 JSON 文件中定义的默认值;out
,由顶点着色器声明并赋值,然后可在片段着色器中被读取,这些值会在顶点间插值计算出来。
着色器程序中的 main
函数会在该着色器程序被调用时执行。更多关于着色器程序的内容请点击上方链接来阅读。
例:这个片段着色器会放大图像中心圆形区域图案。屏幕上左下角像素坐标为 (0,0)
,右上角为 (1,1)
。
#version 150 uniform sampler2D DiffuseSampler; in vec2 texCoord; in vec2 oneTexel; out vec4 fragColor; void main(){ float distFromCenter = distance(texCoord, vec2(0.5, 0.5)); if (distFromCenter < 0.38) { vec2 zoomedCoord = ((texCoord - vec2(0.5, 0.5)) * 0.2) + vec2(0.5, 0.5); fragColor = texture(DiffuseSampler, zoomedCoord); } else if (distFromCenter >= 0.38 && distFromCenter < 0.4) { fragColor = vec4(0.7, 0.4, 0.1, 1.0); } else { fragColor = texture2D(DiffuseSampler, texCoord); } }
§3.9.2 核心着色器
所有能在屏幕上看到的东西都是由核心着色器渲染的,核心着色器文件位于 assets/minecraft/shaders/core
文件夹下。游戏的每个部分会调用相应的核心着色器来渲染。如果我们不希望对着色器进行全局修改,我们可以通过纹理坐标检测在渲染什么,然后进行相应的着色器程序。然而,纹理的 atlas 依赖于加载的所有资源包,因此一旦添加了纹理,纹理坐标将会发生改变。更详细的内容请点击上方链接来阅读。
§3.10 文本
制作人员名单 | minecraft/texts/credits.json |
---|---|
终末之诗 | minecraft/texts/end.txt |
闪烁标语 | minecraft/texts/splashes.txt |
玩家首次进入末地的主世界返回传送门时,会播放终末之诗和制作人员名单。文本PLAYERNAME将会被玩家名称代替。我们可以修改之以显示模组的一些内容。文件 splashes.txt
中每一行表示一个单独的闪烁标语,可以使用样式代码。
1.16以及更早版本可使用样式代码,以及使用 [C]
来表示居中,且这些版本制作人员名单是txt格式。
§4 规划
当我们有了一定的命令基础和数据包与资源包的相关知识之后,我们可以开始考虑做一个模组了。模组的目的是在原版的基础上做出一定的修改,模组的核心是内容,命令和技巧都只是为实现这一目的的手段。通常的设计路线如下图所示:
平衡性可以从合成难度和触发条件等方面来调整。
原版模组不比基于 Forge/Liteloader 等 API 的模组,在实现效率上一般会有所欠缺,因此我们在制作和测试过程中,应当优先保证流畅度,再考虑内容的充实性。
模组的整个生命周期中,所有内容都应当有完整的文档记录,以便于随时查看和修改。
为你的模组设定合理的版本管理方式,例如使用 Git 托管,有利于保存模组的所有历史版本。这可以减少因误操作导致的损失。
§4.1 名称设计
模组的所有内容都应当被合理地命名,尽量采用简洁且有意义的命名,同时便于他人处理数据包冲突。这些内容包括数据包下所有的文件和文件夹名以及记分板、标签、组的名称等。建议各种名称均使用大驼峰、小驼峰或下划线记法。如果模组包含多个模块,可以在不同数据包下使用相同的命名空间、不同的文件夹,来分门别类。
类型 | 示例 | 说明 |
---|---|---|
数据包名称 | craftingpp |
建议使用模组全名,空格和特殊字符使用下划线代替或忽略,也可使用中文名称。 |
资源包名称 | craftingpp_resourcepack |
建议使用数据包名称+资源包等。 |
命名空间 | craftingpp |
过短的命名空间容易冲突,可使用模组名称的全程或缩写来表示。命名空间必须由小写或下划线组成。建议整个模组使用一个命名空间,或若干相同前缀的命名空间。 |
物品名称 | 红色火之力 |
建议使用物品英文名称对应的小写+下划线写法作为物品id,具体设计见下一节。 |
记分板和组 | cppValue |
建议使用命名空间为前缀的驼峰记法,因为记分板名称长度不可超过16。长度允许的话,使用下划线记法也可以。 |
实体标签 | cpp_entities_checker |
建议使用命名空间为前缀。 |
自定义NBT键 | exGenratorMarker |
建议使用命名空间为前缀。 |
记分板假名 | #temp |
临时变量建议使用#开头,配置参数建议使用$开头,因为#开头的计分板不会在记分板列表中显示而$开头的可以。因记分板已使用前缀,此处可以不使用前缀。 |
tick函数 | _main.mcfunction |
即函数标签 minecraft:tick 下的函数,见 §2.10.1 预设标签。 |
load函数 | load.mcfunction |
即函数标签 minecraft:load 下的函数,见 §2.10.1 预设标签。 |
§4.2 调试
加载中出现的资源包和数据包错误一般会在 logs/latest.log
中提示,见 §1.3.1 游戏文件结构。测试时,打开这个文件,可以看到具体是哪个文件的哪个位置发生了错误。常见错误包括
日志 | 说明 |
---|---|
Unable to load model: '模型引用名' referenced from: 某物品#inventory: java.lang.IllegalStateException: Not aJSONObject: "parent" |
该物品模型文件中调用的另一模型出错,错误出在 "parent" |
Using missing texture, unable to load |
相应路径的纹理不存在。 |
Couldn't load function at 函数路径 java.util.concurrent.CompletionException: java.lang.IllegalArgumentException: Whilst parsing command on line 行号 at position 列号 |
相应函数的相应位置出错。 |
Couldn't read function tag list 标签引用名 from 标签路径 in data pack 数据包 |
相应数据包的相应路径的标签出错,检查下是否调用了不存在的物品、方块或出错的函数,或是逗号使用错误。 |
使用 /datapack list
来查看你的模组是否被识别了,如果压根没识别说明你的模组缺少 pack.mcmeta
或其错误。
如果进游戏异常卡顿,输入 /function 相应高频函数
,如果提示执行了65535条函数,那么可能是函数使用了无限自我递归调用。检查相应的函数递归是否跳出条件有误。
调试时,可添加适当的 tellraw
命令来查看方块、实体、记分板值等,来确定何处出错。
§4.3 前置与附属
使用他人已写好的前置可以在减少自己的工作量,例如
等。这些前置已经包含了诸如生成随机数、处理模组方块、合成、容器等内容,这样开发者就不用再花费精力在这些事件的处理上,只需要调用它们提供的接口即可。使用这些数据包之前,应当对其内容有充分的了解再考虑引用,他人的代码未必是合理高效无漏洞的。
建议在前置中添加函数
foo:datapack
scoreboard players set #datapack_version fooValue 101
这样他人便可通过在load函数
中添加
bar:load
scoreboard objective add fooValue dummy scoreboard players set #datapack_version fooValue 0 function foo:datapack execute unless score #datapack_version fooValue matches 101 run tellraw @a {"text":"[Bar模组]:缺少必要的数据包前置[Foo模组v1.0.1]!"}
以在缺少相应版本的前置时提醒使用者。
附属一般通过函数标签来实现。例如在你的模组中添加了一种机器,里面对物品进行了一些处理:
foo:machine/tick
execute if block ~ ~ ~ barrel{Items:[{Slot:3b,id:"minecraft:mycelium"},{Slot:12b,id:"minecraft:dirt"}]} run item block ~ ~ ~ container.15 replace minecraft:mycelium function #foo:item_processer
那么其它开发者就可以通过在函数标签 #foo:item_processer
中添加相应的函数命令来实现更多的机器配方。
§4.4 发布
当你设计并制作好全部内容且通过测试后,将你的数据包以及其它可能的内容,如资源包、地图、data文件等一同发布。采用合适的版本号管理,并在发布时注明你所使用的命名空间、记分板、组、标签、地图区段、资源包等内容,以便于其他开发者整合或避免冲突。
如果你认为分别发布数据包和资源包两个压缩文件不够方便的话,你可以将二者内容放在同一个文件夹下压缩,使得二者共用 pack.mcmeta
和 pack.png
。使用时,只需将其分别复制一份到数据包和资源包文件夹使用即可。
如果你的模组内容较多,可以建立一个wiki页面来方便玩家查询资料。例如国内最大的中文 Minecraft 模组百科MC 百科,建立模组后,可以申请编辑员来方便地管理你的模组wiki页面。也可以 Github 的项目中创建 wiki。
§5 物品设计
§5.1 通用处理
由于原版模组从不添加原版不存在的物品,所以我们需要为物品加上NBT来进行区分。我们以一例来看模组物品通常包含哪些NBT。
添加物品红色火之力,不可叠加,右键触发执行命令。我们可使用胡萝卜钓竿/诡异疣钓竿作为物品本体,这里我们以胡萝卜钓竿为例。我们建议为模组添加的所有物品添加相应的战利品表来便于获取/修改/合成等。
cpp/loot_tables/red_force_of_fire.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "item", "name": "minecraft:carrot_on_a_stick", "functions": [ { "function": "set_name", "name": { "color": "#FF0000", "translate": "item.ex.red_force_of_fire" } }, { "function": "set_nbt", "tag": "{id:'cpp:red_force_of_fire',CustomModelData:12970013}" } ] } ] } ] }
NBT | 说明 |
---|---|
id:"cpp:red_force_of_fire" |
建议使用id:"命名空间:物品名称" 来表示原版模组物品的唯一标签,以与其它模组的物品区分。 |
display:{Name:'{"translate":"item.ex.red_force_of_fire"}'} |
物品的显示名称,建议使用translate 文本,以支持多种语言,见 §3.3 语言文件。 |
CustomModelData:12970013 |
物品的自定义模型数据,见 §3.4 自定义物品模型。 |
dict:["iron_dust"] |
可选的矿物辞典,用于表示同类物品,并不一定指矿物类物品。例如矿物、矿物粉、矿物粒、水果等。矿物辞典可用来解决不同的原版模组/插件的同种物品互相调用的问题,见原版模组矿物辞典。 |
然后为物品添加模型,见 §3.5 模型。
assets/minecraft/models/item/carrot_on_a_stick.json
{ "parent": "item/handheld_rod", "textures": { "layer0": "item/carrot_on_a_stick" }, "overrides": [ { "predicate": { "custom_model_data": 12970013 }, "model": "cpp:tools/red_force_of_fire"} ] }
assets/cpp/models/tools/red_force_of_fire.json
{ "parent": "item/handheld_rod", "textures": { "layer0": "cpp:tools/red_force_of_fire" } }
最后,为其绘制纹理 assets/cpp/textures/tools/red_force_of_fire.png
。这样我们便将这个物品初步设计完成。
我们会在本节接下来的段落介绍不同功能物品的设计。对于方块形式,我们则会在后续章节中介绍。
§5.2 右键交互
对于右键具有交互功能的物品,我们可以使用胡萝卜钓竿,通过使用胡萝卜钓竿的记分板来探测。例如使用上一节中的示例物品:
cpp:load
scoreboard objectives add cppUseCSt minecraft.used:minecraft.carrot_on_a_stick
cpp:tick
execute as @a[scores={cppUseCSt=1..}] run function cpp:use_cst
cpp:use_cst
execute as @s[predicate=cpp:hand/red_force_of_fire] run function cpp:tools/red_force_of_fire scoreboard players reset @s cppUseCSt
cpp/predicates/hand/red_force_of_fire.json
{ "condition": "minecraft:alternative", "terms":[ { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "mainhand":{ "items": ["minecraft:carrot_on_a_stick"], "nbt": "{id:'cpp:red_force_of_fire'}" } } } }, { "condition": "minecraft:inverted", "term": { "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "mainhand":{ "items": ["minecraft:carrot_on_a_stick"] } } } }, { "condition": "minecraft:inverted", "term": { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "offhand":{ "items": ["minecraft:carrot_on_a_stick"], "nbt": "{id:'cpp:red_force_of_fire'}" } } } } } ] } } ] }
如果模组含多个以胡萝卜钓竿为本体的物品需要探测,我们可以先判断是主手还是副手使用的,然后再分情形讨论。
cpp:use_cst
execute as @s[predicate=cpp:mainhand/carrot_on_a_stick] run function cpp:use_cst_main execute as @s[predicate=!cpp:mainhand/carrot_on_a_stick] run function cpp:use_cst_off scoreboard players reset @s cppUseCSt
cpp:use_cst_main
execute as @s[predicate=cpp:mainhand/red_force_of_fire] run function cpp:tools/red_force_of_fire execute as @s[predicate=cpp:mainhand/blue_force_of_sky] run function cpp:tools/blue_force_of_sky ...
cpp:use_cst_off
execute as @s[predicate=cpp:offhand/red_force_of_fire] run function cpp:tools/red_force_of_fire execute as @s[predicate=cpp:offhand/blue_force_of_sky] run function cpp:tools/blue_force_of_sky ...
cpp/predicates/mainhand/carrot_on_a_stick.json
{ "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "mainhand":{ "items": ["minecraft:carrot_on_a_stick"] } } } }
cpp/predicates/mainhand/red_force_of_fire.json
{ "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "mainhand":{ "nbt": "{id:'cpp:red_force_of_fire'}" } } } }
cpp/predicates/offhand/red_force_of_fire.json
{ "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "offhand":{ "nbt": "{id:'cpp:red_force_of_fire'}" } } } }
如果需要的话,我们还可以手动模拟胡萝卜钓竿的耐久损耗。
cpp:damage_cst
execute as @s[predicate=cpp:mainhand/carrot_on_a_stick] run data modify storage cpp:damage Item set from entity @s SelectedItem execute as @s[predicate=!cpp:mainhand/carrot_on_a_stick] run data modify storage cpp:damage Item set from entity @s Inventory[{Slot:-106b}] function cpp:damage execute if score #damage cppValue matches 26.. run data remove storage cpp:damage Item setblock ~ 255 ~ shulker_box data modify storage cpp:damage Item.Slot set value 0b data modify block ~ 255 ~ Items append from storage cpp:damage Item loot replace entity @s[predicate=cpp:mainhand/carrot_on_a_stick] weapon.mainhand 1 mine ~ 255 ~ tnt{drop_content:1b} loot replace entity @s[predicate=!cpp:mainhand/carrot_on_a_stick] weapon.offhand 1 mine ~ 255 ~ tnt{drop_content:1b} setblock ~ 255 ~ air
处理耐久的函数 cpp:damage
见 §10.3 耐久处理。
投掷物如雪球、末影珍珠等可用于一次性右键工具,不同的物品有着各自的特点。投掷带自定义NBT标签的物品,对应的投掷物实体NBT Item
会包含这些NBT标签。注意,在1.14和更早版本,由于Mojang先消耗手持物再存储手持物信息,导致投掷最后一个物品时没有存储相应的 Item
信息,此时我们需要提前1刻存储玩家手持物信息方可。
扔出 minecraft:experience_bottle{exXpNumber:99s}
生成 exXpNumber
点经验值的经验球。
cpp:tick
execute as @e[type=experience_bottle] if data entity @s Item.tag.exXpNumber at @s run function cpp:xp/throw
cpp:xp/throw
summon experience_orb ~ ~ ~ {ags:["cpp_XpNumber"]} execute store result entity @e[type=experience_orb,tag=cpp_XpNumber,limit=1,distance=..1] Value short 1 run data get entity @s Item.tag.cpp_XpNumber tag @e[type=experience_orb,tag=cpp_XpNumber,limit=1,distance=..1] remove cpp_XpNumber kill @s
注意使用发射器发射该附魔之瓶仍然会表现如同普通附魔之瓶。
§5.3 食物
我们一般要求食物的本体无合成用途,且和其它生物没有交互,饥饿值较低以便于通过饱和效果实现高饥饿值。由此,可选的有:
物品 | 优点 | 缺点 |
---|---|---|
曲奇 | 饥饿值低,容易通过饱和效果实现至少2点饥饿值的食物。无合成用途。 | 和鹦鹉可交互。 |
苹果 | 饥饿值低,容易通过饱和效果实现至少4点饥饿值的食物。 | 可用于合成金苹果。 |
熟鳕鱼 | 饥饿值低,容易通过饱和效果实现至少5点饥饿值的食物。无合成用途。 | - |
面包 | 饥饿值低,容易通过饱和效果实现至少5点饥饿值的食物。无合成用途。和村民可交互。 | - |
迷之炖菜 | 容易实现状态效果。不可堆叠,食用后有碗。 | 饥饿值过高,只能通过饱和效果实现至少8点饥饿值的食物。 |
食用肉松面包回复10点饥饿值,并获得30秒的速度效果,且可扔给村民。
cpp/loot_tables/meat_floss_bread.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:bread", "functions": [ { "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"translate\":\"item.ex.meat_floss_bread\"}'},id:'cpp:meat_floss_bread',CustomModelData:12970001}" } ] } ] } ] }
cpp/advancements/foods/meat_floss_bread.json
{ "criteria": { "meat_floss_bread": { "trigger": "minecraft:consume_item", "conditions": { "item": { "nbt": "{id:'cpp:meat_floss_bread'}" } } } }, "rewards":{ "function": "cpp:foods/meat_floss_bread" } }
cpp:foods/meat_floss_bread
effect give @s saturation 1 4 effect give @s speed 30 advancement revoke @s only cpp:foods/meat_floss_bread
这里,1秒的饱和IV可以恢复5点饥饿值,加上面包本身的5点,正好为10点。
如果我们想要制作不可堆叠类食物,可以为食物附加上随机的盔甲属性,且令其在 chest
栏位生效。由于一般食物无法放入玩家胸甲栏位,因此该属性并不会有实际效果。
cpp/loot_tables/colorful_vegetable.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:cooked_cod", "functions": [ { "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"italic\":false,\"translate\":\"item.cpp.colorful_vegetable\"}'},id:'cpp:colorful_vegetable',CustomModelData:12970002,HideFlags:63}" }, { "function": "minecraft:set_attributes", "modifiers": [ { "slot": "chest", "name": "food_armor", "attribute": "generic.armor", "amount": 0, "operation": "addition" } ] } ] } ] } ] }
更为简便的做法是使用迷之炖菜,注意会剩下碗。迷之炖菜可以自带药水效果,无需进度和函数来额外实现药水效果,但由于只能设置为1级,因此额外的饥饿值仍然需要进度。
食用缤纷菜蔬回复6点饥饿值,并获得30秒的跳跃提升效果。
cpp/loot_tables/colorful_vegetable.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:suspicious_stew", "functions": [ { "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"translate\":\"item.ex.colorful_vegetable\"}',Lore:['{\"translate\":\"lore.ex.leap\"}']},id:'cpp:colorful_vegetable',CustomModelData:12970002,Effects:[{EffectDuration:600,EffectId:8b}]}" } ] } ] } ] }
对于想要使用饥饿效果来降低饥饿值的情形,注意饥饿效果会先消耗饱食度,且如果玩家食用后饥饿值已满,还需要通过记分板来实时检测上一刻玩家的饥饿值以确定当前的饥饿值应当为多少。因此实现较为繁琐,这里便不做详解。
§5.4 药水
我们使用药水来实现自定义的药水效果。
饮用潮汐药水后获得8分钟的潮涌之力效果。
cpp/loot_tables/potion_of_tide.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:potion", "functions": [ { "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"translate\":\"item.ex.potion_of_tide\"}'},id:"cpp:potion_of_tide",CustomPotionColor:1950417,CustomPotionEffects:[{Id:29b,Amplifier:0b,Duration:9600}]}" } ] } ] } ] }
如果我们想要药水可以堆叠,可以使用食物(64堆叠数)或蜂蜜瓶(16堆叠数)来实现,但要注意它们可以恢复饥饿值,且可能可参与合成。我们需要手动为食物加上显示药水效果的Lore
。
饮用天空药水(16堆叠数)后回复6点饥饿值,并获得6分钟的速度II和6分钟的缓降效果。
cpp/loot_tables/agentia_of_sky.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:honey_bottle", "functions": [ { "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"translate\":\"item.ex.agentia_of_sky\"}',Lore:['[{\"translate\":\"effect.minecraft.speed\",\"italic\":\"false\",\"color\":\"blue\"},{\"text\":\" II (6:00)\"}]','[{\"translate\":\"effect.minecraft.slow_falling\",\"italic\":\"false\",\"color\":\"blue\"},{\"text\":\" (6:00)\"}]','\" \"','{\"translate\":\"potion.whenDrank\",\"italic\":\"false\"}','[{\"text\":\"+40% \",\"italic\":\"false\",\"color\":\"blue\"},{\"translate\":\"attribute.name.generic.movementSpeed\"}]']},id:'cpp:agentia_of_sky',CustomModelData:12970101}" } ] } ] } ] }
cpp/advancements/agentia_of_sky.json
{ "criteria": { "consume": { "trigger": "minecraft:consume_item", "conditions": { "item": { "nbt": "{id:'cpp:agentia_of_sky'}" } } } }, "rewards":{ "function": "cpp:potion/agentia_of_sky" } }
cpp:potion/agentia_of_sky
effect give @s speed 360 1 effect give @s slow_falling 360 advancement revoke @s only cpp:potion/agentia_of_sky
§5.5 自定义状态效果
原版定义新的状态效果是不可能的,但是我们可以通过进度来模拟食用特定物品后的效果。我们使用记分板来记录时长,模拟药水的颗粒效果,并在actionbar
显示剩余时长。
饮用泥土药水后获得12分钟的隐身和12分钟的连锁效果,这里我们不涉及实际效果的实现。
cpp/loot_tables/agentia_of_dirt.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:honey_bottle", "functions": [ { "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"translate\":\"item.ex.agentia_of_dirt\"}',Lore:['[{\"translate\":\"effect.minecraft.invisibility\",\"italic\":\"false\",\"color\":\"blue\"},{\"text\":\" (12:00)\"}]','[{\"translate\":\"lore.ex.chain\",\"italic\":\"false\",\"color\":\"blue\"},{\"text\":\" (12:00)\"}]']},id:'cpp:agentia_of_dirt',CustomModelData:12970104}" } ] } ] } ] }
cpp/advancements/agentia_of_dirt.json
{ "criteria": { "consume": { "trigger": "minecraft:consume_item", "conditions": { "item": { "nbt": "{id:'cpp:agentia_of_dirt'}" } } } }, "rewards":{ "function": "cpp:potion/agentia_of_dirt" } }
cpp:potion/agentia_of_dirt
effect give @s invisibility 720 execute unless score @s cppChainTick matches 14400.. run scoreboard players set @s cppChainTick 14400 advancement revoke @s only cpp:potion/agentia_of_dirt
cpp:tick
execute as @a[scores={cppChainTick=1..}] at @s anchored eyes run function cpp:chain/type
cpp:chain/type
execute positioned ^ ^ ^0.2 run particle entity_effect ~ ~-1 ~ 0.734375 0.37890625 0.3046875 1 0 scoreboard players remove @s cppChainTick 1 [省略实际效果部分]
我们使用周期为20刻的低频来显示剩余时长。
cpp:load
function cpp:tick20
cpp:tick20
execute as @a[scores={cppChainTick=1..}] run function cpp:chain/showtime schedule function cpp:tick20 20t
cpp:chain/showtime
scoreboard players operation #min cppValue = @s cppChainTick scoreboard players operation #min cppValue /= #20 cppValue scoreboard players operation #sec cppValue = #min cppValue scoreboard players operation #min cppValue /= #60 cppValue scoreboard players operation #sec cppValue %= #60 cppValue execute as @s[scores={cppChainTick=20..}] if score #sec cppValue matches 10.. run title @s actionbar [{"translate":"title.effect.chain"},{"score":{"name":"#min","objective":"cppValue"},"color":"gray"},{"text":":"},{"score":{"name":"#sec","objective":"cppValue"},"color":"gray"}] execute as @s[scores={cppChainTick=20..}] if score #sec cppValue matches ..9 run title @s actionbar [{"translate":"title.effect.chain"},{"score":{"name":"#min","objective":"cppValue"},"color":"gray"},{"text":":"},{"text":"0","color":"gray"},{"score":{"name":"#sec","objective":"cppValue"},"color":"gray"}] title @s[scores={cppChainTick=..19}] actionbar [{"text":" "}]
我们无法在背包页面显示图标和时长,但是我们可以在特定分辨率下在右上角显示图标。这里需要使用§3.8 字体的负空格字体技巧。我们假设当前分辨率为1920×1080
,通过试验得知使用actionbar
占据整个右上角需要的大小为480×182
,因此我们使用298
像素宽的空格和182×182
的图案。
assets/cpp/font/default.json
{ "providers": [ { "type": "bitmap", "file": "cpp:font/space.png", "ascent": -32768, "height": 297, "chars": ["1"] }, { "type": "bitmap", "file": "cpp:font/chain.png", "height": 182, "ascent": 182, "chars": ["2"] } ] }
其中文件 assets/cpp/textures/font/space.png
为 1×1
的透明文件,assets/cpp/textures/font/chain.png
为 182×182
的文件,右上角为需要显示的状态效果图案。我们使用自定义的字体以避免占据原版字符。
cpp:chain/showtime
title @s actionbar {"font":"cpp:default","text":"12"}
状态效果即将结束时的闪烁效果也可以类似实现,这里不再赘述。
§5.6 头饰
我们使用雕刻过的南瓜来实现头饰。
可佩戴在头部的花环。
cpp/loot_tables/garland.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:carved_pumpkin", "functions": [ { "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"translate\":\"item.ex.garland\"}'},id:'cpp:garland',CustomModelData:12970124,exHatSlot:'hand'}" } ] } ] } ] }
由于雕刻过的南瓜可以放置,为了避免我们需要处理放置方块事件,细节见 §6 方块设计。我们假设已探测到南瓜位置,且玩家放置的方块的NBT信息已存储在存储区cpp:block Item
,我们对其进行破坏并生成原物品。
cpp/block/carved_pumpkin.json
setblock ~ ~ ~ air kill @s summon item ~ ~ ~ {Item:{id:"minecraft:firework_star",Count:1b,tag:{CustomModelData:12971000}},Tags:["cpp_temp"]} data modify entity @e[type=item,tag=cpp_temp,distance=..0.1,limit=1] Item set from storage cpp:block Item tag @e[type=item,tag=cpp_temp,distance=..0.1,limit=1] remove cpp_temp
我们将资源包内文件 minecraft/textures/misc/pumpkinblur.png
设置为完全透明的图片以清除雕刻过的南瓜戴在头上的视野限制效果。
对于头饰而言,我们希望其像盔甲一样在背包物品栏显示为物品的平面纹理,而在玩家头部显示为立体模型。因此我们需要两套模型,并处理物品进入玩家头部和手部的事件,见【1.14】物品头部/背包/手持显示不同纹理/模型。
minecraft/models/item/carved_pumpkin.json
{ "parent": "block/orientable", "textures": { "top": "block/pumpkin_top", "front": "block/carved_pumpkin", "side": "block/pumpkin_side" }, "overrides": [ { "predicate": { "custom_model_data": 12970024 }, "model": "cpp:decor/garland"}, { "predicate": { "custom_model_data": 12970124 }, "model": "cpp:decor/garland1"} ] }
在手部时,为普通物品模型。
cpp/models/decor/garland1.json
{ "parent": "item/generated", "textures": { "layer0": "cpp:decor/garland_item" } }
在头部时,我们将其在背包的显示旋转使得只能看到方块底部,而底部纹理为对应的普通物品纹理。其它5个面用于显示花环的立体纹理模型。
cpp/models/decor/garland.json
{ "parent": "cpp:decor/hat", "textures": { "hat": "cpp:decor/garland", "item": "cpp:decor/garland_item" } }
cpp/models/decor/hat.json
{ "parent": "block/block", "display": { "gui": { "rotation": [ -90, 0, 0 ] } }, "elements": [ { "from": [ 0, 0, 0 ], "to": [ 16, 16, 16 ], "faces": { "down": { "uv": [ 0, 0,16,16 ], "texture": "#item", "cullface": "down" }, "up": { "uv": [ 4, 0, 8, 4 ], "texture": "#hat", "cullface": "up" }, "north": { "uv": [ 4, 4, 8, 8 ], "texture": "#hat", "cullface": "north" }, "south": { "uv": [12, 4,16, 8 ], "texture": "#hat", "cullface": "south" }, "west": { "uv": [ 8, 4,12, 8 ], "texture": "#hat", "cullface": "west" }, "east": { "uv": [ 0, 4, 4, 8 ], "texture": "#hat", "cullface": "east" } } } ] }
cpp/predicates/mainhand_hat.json
{ "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "mainhand":{ "items": ["minecraft:carved_pumpkin"], "nbt": "{exHatSlot:'head'}" } } } }
cpp/loot_tables/offhand_hat.json
{ "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "offhand":{ "items": ["minecraft:carved_pumpkin"], "nbt": "{exHatSlot:'head'}" } } } }
cpp/loot_tables/head_hat.json
{ "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "head":{ "items": ["minecraft:carved_pumpkin"], "nbt": "{exHatSlot:'hand'}" } } } }
cpp:tick
execute as @a[predicate=cpp:mainhand_hat] run function cpp:decor/mainhand_hat execute as @a[predicate=cpp:offhand_hat] run function cpp:decor/offhand_hat execute as @a[predicate=cpp:head_hat] run function cpp:decor/head_hat
cpp:decor/mainhand_hat
setblock ~ 255 ~ shulker_box{Items:[{Slot:0b,id:"minecraft:carved_pumpkin",Count:1b}]} data modify block ~ 255 ~ Items[0] merge from entity @s SelectedItem execute store result score #temp cppValue run data get block ~ 255 ~ Items[0].tag.CustomModelData execute store result block ~ 255 ~ Items[0].tag.CustomModelData int 1 run scoreboard players add #temp cppValue 100 data modify block ~ 255 ~ Items[0].tag.exHatSlot set value "hand" loot replace entity @s weapon.mainhand 1 mine ~ 255 ~ tnt{drop_content:1b} setblock ~ 255 ~ air
cpp:decor/off_hat
setblock ~ 255 ~ shulker_box{Items:[{Slot:0b,id:"minecraft:carved_pumpkin",Count:1b}]} data modify block ~ 255 ~ Items[0].Count set from entity @s Inventory[{Slot:-106b}].Count data modify block ~ 255 ~ Items[0].tag set from entity @s Inventory[{Slot:-106b}].tag execute store result score #temp cppValue run data get block ~ 255 ~ Items[0].tag.CustomModelData execute store result block ~ 255 ~ Items[0].tag.CustomModelData int 1 run scoreboard players add #temp cppValue 100 data modify block ~ 255 ~ Items[0].tag.exHatSlot set value "hand" loot replace entity @s weapon.offhand 1 mine ~ 255 ~ tnt{drop_content:1b} setblock ~ 255 ~ air
cpp:decor/head_hat
setblock ~ 255 ~ shulker_box{Items:[{Slot:0b,id:"minecraft:carved_pumpkin",Count:1b}]} data modify block ~ 255 ~ Items[0].Count set from entity @s Inventory[{Slot:103b}].Count data modify block ~ 255 ~ Items[0].tag set from entity @s Inventory[{Slot:103b}].tag execute store result score #temp cppValue run data get block ~ 255 ~ Items[0].tag.CustomModelData execute store result block ~ 255 ~ Items[0].tag.CustomModelData int 1 run scoreboard players remove #temp cppValue 100 data modify block ~ 255 ~ Items[0].tag.exHatSlot set value "head" loot replace entity @s armor.head 1 mine ~ 255 ~ tnt{drop_content:1b} setblock ~ 255 ~ air
这里我们使用了潜影盒战利品表技巧来修改玩家背包物品,见 §2.5 战利品表。
§5.7 盔甲
头饰通过添加属性可以实现为头盔,但是胸甲、护腿和靴子无法通过资源包和数据包来修改其穿戴在身上时的显示纹理,因为这三个栏位只接受相应类别的物品。我们只能通过诸如修改皮革盔甲颜色来达到视觉上的不同。例如:绿宝石靴子穿在脚上时,增加5点盔甲、2点盔甲韧性、4点生命值。注意,这个靴子和耐久和皮革靴子一样低。
cpp/loot_tables/emerald_boots.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:leather_boots", "functions": [ { "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"translate\":\"item.ex.emerald_boots\"}',color:1564002},id:'cpp:emerald_boots'}" }, { "function": "minecraft:set_attributes", "modifiers": [ { "amount": 3, "name": "cpp_armor", "attribute": "generic.armor", "operation": "addition","slot": "feet" }, { "amount": 2, "name": "cpp_armorToughness", "attribute": "generic.armorToughness", "operation": "addition", "slot": "feet"}, { "amount": 4, "name": "cpp_maxHealth", "attribute": "generic.maxHealth", "operation": "addition", "slot": "feet" } ] } ] } ] } ] }
§5.8 工具和武器
工具和武器同样可以通过原版工具和武器修改得到。例如:使用玻璃镐挖掘玻璃时,玻璃掉落自身。
cpp/loot_tables/glass_pickaxe.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:stone_pickaxe", "functions": [ { "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"translate\":\"item.ex.glass_pickaxe\"}'},id:'cpp:glass_pickaxe',CustomModelData:12970001}" } ] } ] } ] }
我们对所有的玻璃和玻璃板的战利品表进行修改。
minecraft/loot_tables/blocks/red_stained_glass.json
{ "type": "minecraft:block", "pools": [ { "rolls": 1, "conditions": [ { "condition": "minecraft:reference", "name": "cpp:drop_glass" } ], "entries": [ { "type": "minecraft:item", "name": "minecraft:red_stained_glass" } ] } ] }
cpp/predicates/drop_glass.json
{ "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:match_tool", "predicate": { "enchantments": [ { "enchantment": "minecraft:silk_touch", "levels": { "min": 1 } } ] } }, { "condition": "minecraft:match_tool", "predicate": { "nbt": "{id:'cpp:glass_pickaxe'}" } } ] }
这些物品的耐久都是不可修改的,但是我们可以将其设置为不可破坏,然后每次使用后模拟耐久降低的过程。
cpp:load
scoreboard objectives add exUseSPick minecraft.used:minecraft.stone_pickaxe
cpp:tick
execute as @s[scores={exUseSPick=1..}] run function cpp:tools/glass_pickaxe/reset
cpp:tools/glass_pickaxe/reset
scoreboard players reset @s exUseSPick execute as @s[nbt={SelectedItem:{tag:{id:"cpp:glass_pickaxe"}}}] run function cpp:tools/glass_pickaxe/damage
cpp:tools/glass_pickaxe/damage
data modify storage cpp:temp Item set from entity @s SelectedItem function cpp:damage_tool execute if score #damage cppValue matches 99.. run data remove storage cpp:temp Item setblock ~ 255 ~ shulker_box data modify block ~ 255 ~ Items append from storage cpp:temp Item loot replace entity @s weapon.mainhand 1 mine ~ 255 ~ tnt{drop_content:1b} setblock ~ 255 ~ air
函数 cpp:damage_tool
见§5.2 右键交互,这里省略。这里我们使用 §10.2 修改玩家背包中的潜影盒战利品表来将物品输出到玩家手中。
§5.9 画
我们使用指定内含物的物品展示框来实现自定义的画。例如:放置经典画作后显示一幅画,右键可以切换。
我们为烟火之星 CustomModelData
为 12974001-12974100
指定100个平面画模型。
cpp/loot_tables/classical_painting.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:item_frame", "functions": [ { "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"translate\":\"item.ex.classical_painting\"}'},id:'cpp:classical_painting',CustomModelData:12970002,EntityTag:{Item:{id:'minecraft:firework_star',Count:1b,tag:{CustomModelData:12974001,exClear:1b}},Tags:['cpp_item_frame_classical_painting','cpp_special_item_frame']}}" } ] } ] } ] }
cpp:tick
execute as @e[type=item_frame,tag=cpp_special_item_frame] at @s run function cpp:item_frame/special
cpp:item_frame/special
execute as @s[nbt={Facing:0b}] if block ~ ~1 ~ #cpp:fluid run function cpp:item_frame/special_break execute as @s[nbt={Facing:1b}] if block ~ ~-1 ~ #cpp:fluid run function cpp:item_frame/special_break execute as @s[nbt={Facing:2b}] if block ~ ~ ~1 #cpp:fluid run function cpp:item_frame/special_break execute as @s[nbt={Facing:3b}] if block ~ ~ ~-1 #cpp:fluid run function cpp:item_frame/special_break execute as @s[nbt={Facing:4b}] if block ~1 ~ ~ #cpp:fluid run function cpp:item_frame/special_break execute as @s[nbt={Facing:5b}] if block ~-1 ~ ~ #cpp:fluid run function cpp:item_frame/special_break execute unless block ~ ~ ~ #cpp:fluid run function cpp:item_frame/special_break execute unless data entity @s Item.id run function cpp:item_frame/special_break execute as @s[nbt={ItemRotation:1b}] run function cpp:item_frame/special_rot
方块标签 cpp:fluid
见 §2.10 标签。
cpp:item_frame/special_break
execute as @s[tag=cpp_item_frame_classical_painting] run loot spawn ~ ~ ~ loot cpp:classical_painting kill @s
cpp:item_frame/special_rot
execute store result entity @s Item.tag.CustomModelData int 1.00000008 run data get entity @s Item.tag.CustomModelData data modify entity @s ItemRotation set value 0b data modify entity @s[nbt={Item:{tag:{CustomModelData:12974101}}}] Item.tag.CustomModelData set value 12974001
自定义地图也可以用来显示自定义的图案,这个方法可避免使用资源包,但需将data
文件与数据包一同发布。首先确定好长宽比例,然后使用MC Map Item Tool在线转换。注意这个网站生成的地图已经不符合1.14+版本的地图格式了,请在本地生成锁定图案的地图后将该网站生成的地图的colors
这个NBT复制过来。这些地图文件位于世界名称/data
文件夹。发布时,将其和数据包一同发布。使用命令 give @s minecraft:filled_map{map:[数值]}
获取相应地图。选择负数的自定义地图的编号,或者手动将data
文件夹中的idcounts
调大,可避免和玩家在游戏内生成的地图编号冲突。
§6 方块设计
原版模组无法添加方块,一般的做法是修改方块物品的 CustomModelData
的模型后,在方块位置生成用于显示的实体。参考
实体一般使用头戴相应物品的盔甲架或者存储有相应物品的物品展示框或荧光物品展示框。相比于盔甲架,物品展示框的优点是:
- 渲染卡顿更小;
- 使用荧光物品展示框可以避免对盔甲架修改
Fire
来保持其被实体方块覆盖时不变黑。
缺点是:
- 不可自由旋转,例如用于显示地面的告示牌模型就相对麻烦一点;
- 不可自由移动,只能位于整数坐标处;
- 不可使用物品标签谓词,盔甲架可以将物品复制到主副手来判断其是否属于某物品标签,而物品展示框不行;
- 不可使用工具进行
loot mine/kill
; - 拥有CustomName时会显示物品名称。
在这些缺点不影响时,我们更建议使用物品展示框。本节中我们以盔甲架为例。首先我们根据 §5 物品设计来将方块对应的物品设计好,然后我们需要定位到方块所在位置。我们使用进度判断玩家放置了某物品,然后剥夺进度,获取该方块位置。由于玩家可以紧靠其它不完整方块来放置,这会导致该方块位置不一定在玩家视线上。一种做法是记录所有方块的碰撞箱,然后判断玩家视线与何方块的何面相交,从而得到方块位置,见超精准的射线追踪碰撞检测器。另一种在放弃精准度的前提下,我们可以将视线向相邻6个位置移动一格检测。为了简便,我们采用第二种做法。
最后,我们在方块位置放置盔甲架。我们需要判断玩家是主手还是副手放置的,并将相应的物品信息复制到盔甲架头部。不直接使用 loot
命令复制到盔甲架头部是为了在之后的破坏事件中保留原物品的额外信息,例如玩家重命名的名称等。反之,若我们不希望保留这些额外信息,则可以直接使用 loot
命令输入到盔甲架头部。
对于特殊的方块物品,我们需要对盔甲架进行预处理或调整,例如
- 有朝向的我们需要根据玩家朝向来放置不同朝向的方块。
- 告示牌我们需要根据朝向来调整盔甲架位置和朝向。
- 树苗花草作物我们需要放置后调整
CustomModelData
,因为一般手持为平面纹理模型。 - 需要添加额外标签以便于处理的。
§6.1 视线追踪法
我们使用递归向前来获取方块位置。
cpp/advancements/blocks/acacia_leaves.json
{ "criteria": { "acacia_leaves": { "trigger": "minecraft:placed_block", "conditions": { "block": "minecraft:acacia_leaves" } } }, "rewards": { "function": "cpp:blocks/acacia_leaves/reset" } }
cpp:blocks/acacia_leaves/reset
advancement revoke @s only cpp:blocks/acacia_leaves execute as @s[predicate=cpp:mainhand/acacia_leaves] run data modify storage cpp:_ weapon.Item set from entity @s SelectedItem execute as @s[predicate=!cpp:mainhand/acacia_leaves] run data modify storage cpp:_ weapon.Item set from entity @s Inventory[{Slot:-106b}] execute store result score #put_block_cmd cppValue run data get storage cpp:_ weapon.Item.tag.CustomModelData execute if score #put_block_cmd cppValue matches 12970000..12979999 at @s anchored eyes positioned ^ ^ ^ run function cpp:blocks/acacia_leaves/locate
cpp:blocks/acacia_leaves/locate
function cpp:blocks/acacia_leaves/ray execute unless entity @e[type=marker,distance=..7,tag=cpp_block_pos] positioned ~ ~1 ~ run function cpp:blocks/acacia_leaves/ray execute unless entity @e[type=marker,distance=..8,tag=cpp_block_pos] positioned ~ ~-1 ~ run function cpp:blocks/acacia_leaves/ray execute unless entity @e[type=marker,distance=..8,tag=cpp_block_pos] positioned ~1 ~ ~ run function cpp:blocks/acacia_leaves/ray execute unless entity @e[type=marker,distance=..8,tag=cpp_block_pos] positioned ~-1 ~ ~ run function cpp:blocks/acacia_leaves/ray execute unless entity @e[type=marker,distance=..8,tag=cpp_block_pos] positioned ~ ~ ~1 run function cpp:blocks/acacia_leaves/ray execute unless entity @e[type=marker,distance=..8,tag=cpp_block_pos] positioned ~ ~ ~-1 run function cpp:blocks/acacia_leaves/ray execute at @e[type=marker,distance=..8,tag=cpp_block_pos,sort=nearest,limit=1] align xyz positioned ~0.5 ~ ~0.5 run function cpp:blocks/acacia_leaves/put kill @e[type=marker,distance=..10,tag=cpp_block_pos]
cpp:blocks/acacia_leaves/ray
execute if entity @s[distance=..10] if block ~ ~ ~ acacia_leaves align xyz positioned ~0.5 ~ ~0.5 unless entity @e[type=armor_stand,distance=..0.5] run summon marker ~ ~ ~ {Tags:["cpp_block_pos"]} execute if entity @s[distance=..10] unless entity @e[type=marker,distance=..6,tag=cpp_block_pos] positioned ^ ^ ^0.005 run function cpp:blocks/acacia_leaves/ray
cpp:blocks/acacia_leaves/put
data modify storage cpp:_ weapon.Item.Count set value 1b summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Small:1b,Marker:1b,NoGravity:1b,DisabledSlots:7967,Fire:32767s,Tags:["cpp_leaves","cpp_need_fire"]} execute as @e[type=armor_stand,tag=cpp_leaves,distance=..0.1,limit=1] run function cpp:blocks/acacia_leaves/tag
cpp:blocks/acacia_leaves/tag
execute if score #put_block_cmd cppValue matches 12970301 run tag @s add cpp_fruit_leaves execute if score #put_block_cmd cppValue matches 12970302 run tag @s add cpp_ore_leaves execute if score #put_block_cmd cppValue matches 12970303 run tag @s add cpp_wool_leaves execute if score #put_block_cmd cppValue matches 12970304 run tag @s add cpp_sakura_leaves data modify entity @s ArmorItems[3] set from storage cpp:_ weapon.Item
注意到我们在视线追踪是进行了当前方块内是否已有盔甲架的判断。这在处理完整方块时是可以省略的,但是对于非完整方块,视线上可能已经有之前放置过的模组物品对应的盔甲架,使用该项判断可以避免在一个格子内放置两个盔甲架,而忽略掉后放的方块。
如果为了避免误伤玩家放置的盔甲架,可以通过判断盔甲架是否拥有某特定 tag
。然而,这个做法仍然对其它模组的方块无效,因为你无法知道其它模组使用了何种标签。更进一步我们可以判断当前方块处是否有头部佩戴拥有 tag.id
物品的盔甲架。具体代码读者可自行实现。
§6.2 计算交点法
该方法通过记分板来计算玩家视线和方块的各个面的交点位置。最后判断这些方块是否满足我们的条件。我们省略与上一节重复的一些内容。
cpp:blocks/acacia_leaves/locate
function cpp:locate/init execute as @e[type=marker,distance=..10,tag=cpp_locate_block] at @s if block ~ ~ ~ acacia_leaves align xyz positioned ~0.5 ~ ~0.5 unless entity @e[type=armor_stand,tag=cpp_leaves,distance=..0.1] run tag @s add cpp_locate_block_position execute unless entity @e[type=marker,distance=..6.1,tag=cpp_locate_block_position] run function cpp:locate/neighbor execute as @e[type=marker,distance=..10,tag=cpp_locate_block_neighbor] at @s if block ~ ~ ~ acacia_leaves align xyz positioned ~0.5 ~ ~0.5 unless entity @e[type=armor_stand,tag=cpp_leaves,distance=..0.1] run tag @s add cpp_locate_block_position execute at @e[type=marker,distance=..7.1,tag=cpp_locate_block_position,sort=nearest,limit=1] align xyz positioned ~0.5 ~ ~0.5 run function cpp:blocks/acacia_leaves/put kill @e[type=marker,distance=..10,tag=cpp_locate_block]
我们判断玩家离原点的距离来区分三个距离区间,从而离原点更近的地方可以获得不超过 2/7100 的误差,而距离稍远处误差会有所增大。
cpp:locate/init
execute anchored eyes run summon marker ^ ^ ^ {Tags:["cpp_locate_block"]} execute anchored eyes run summon marker ^ ^ ^1 {Tags:["cpp_locate_block_front"]} execute store result score #x cppValue run data get entity @e[type=marker,distance=..3,tag=cpp_locate_block,limit=1] Pos[0] 71 execute store result score #z cppValue run data get entity @e[type=marker,distance=..3,tag=cpp_locate_block,limit=1] Pos[2] 71 scoreboard players operation #tempx cppValue = #x cppValue execute if score #x cppValue matches ..-1 run scoreboard players operation #tempx cppValue *= #-1 cppValue scoreboard players operation #tempz cppValue = #z cppValue execute if score #z cppValue matches ..-1 run scoreboard players operation #tempz cppValue *= #-1 cppValue scoreboard players operation #tempx cppValue > #tempz cppValue execute if score #tempx cppValue matches 213000001.. run function cpp:locate/init1 execute if score #tempx cppValue matches 21300001..213000000 run function cpp:locate/init10 execute if score #tempx cppValue matches ..21300000 run function cpp:locate/init100 kill @e[type=marker,tag=cpp_locate_block_front]
进行计算的初始化。
cpp:locate/init100
execute store result score #x cppValue run data get entity @e[type=marker,distance=..3,tag=cpp_locate_block,limit=1] Pos[0] 7100 execute store result score #y cppValue run data get entity @e[type=marker,distance=..3,tag=cpp_locate_block,limit=1] Pos[1] 7100 execute store result score #z cppValue run data get entity @e[type=marker,distance=..3,tag=cpp_locate_block,limit=1] Pos[2] 7100 execute store result score #f cppValue run data get entity @e[type=marker,distance=..3,tag=cpp_locate_block_front,limit=1] Pos[0] 7100 execute store result score #g cppValue run data get entity @e[type=marker,distance=..3,tag=cpp_locate_block_front,limit=1] Pos[1] 7100 execute store result score #h cppValue run data get entity @e[type=marker,distance=..3,tag=cpp_locate_block_front,limit=1] Pos[2] 7100 scoreboard players operation #f cppValue -= #x cppValue scoreboard players operation #g cppValue -= #y cppValue scoreboard players operation #h cppValue -= #z cppValue scoreboard players operation #sf cppValue = #f cppValue scoreboard players operation #sg cppValue = #g cppValue scoreboard players operation #sh cppValue = #h cppValue execute if score #sf cppValue matches ..-1 run scoreboard players operation #x cppValue *= #-1 cppValue execute if score #sg cppValue matches ..-1 run scoreboard players operation #y cppValue *= #-1 cppValue execute if score #sh cppValue matches ..-1 run scoreboard players operation #z cppValue *= #-1 cppValue execute if score #sf cppValue matches ..-1 run scoreboard players operation #f cppValue *= #-1 cppValue execute if score #sg cppValue matches ..-1 run scoreboard players operation #g cppValue *= #-1 cppValue execute if score #sh cppValue matches ..-1 run scoreboard players operation #h cppValue *= #-1 cppValue function cpp:locate/loop100
递归计算视线上的每一个方块交点。
cpp:locate/loop100
tag @e[type=marker,distance=..8,tag=cpp_locate_block,tag=cpp_temp] remove cpp_temp scoreboard players operation #t1 cppValue = #x cppValue scoreboard players operation #t1 cppValue *= #-1 cppValue scoreboard players operation #t1 cppValue %= #7100 cppValue execute if score #t1 cppValue matches 0..1 run scoreboard players add #t1 cppValue 7100 scoreboard players operation #t1 cppValue *= #7100 cppValue scoreboard players operation #t1 cppValue /= #f cppValue scoreboard players operation #t2 cppValue = #y cppValue scoreboard players operation #t2 cppValue *= #-1 cppValue scoreboard players operation #t2 cppValue %= #7100 cppValue execute if score #t2 cppValue matches 0..1 run scoreboard players add #t2 cppValue 7100 scoreboard players operation #t2 cppValue *= #7100 cppValue scoreboard players operation #t2 cppValue /= #g cppValue scoreboard players operation #t3 cppValue = #z cppValue scoreboard players operation #t3 cppValue *= #-1 cppValue scoreboard players operation #t3 cppValue %= #7100 cppValue execute if score #t3 cppValue matches 0..1 run scoreboard players add #t3 cppValue 7100 scoreboard players operation #t3 cppValue *= #7100 cppValue scoreboard players operation #t3 cppValue /= #h cppValue scoreboard players operation #t1 cppValue < #t2 cppValue scoreboard players operation #t1 cppValue < #t3 cppValue scoreboard players operation #s1 cppValue = #f cppValue scoreboard players operation #s2 cppValue = #g cppValue scoreboard players operation #s3 cppValue = #h cppValue scoreboard players operation #s1 cppValue *= #t1 cppValue scoreboard players operation #s2 cppValue *= #t1 cppValue scoreboard players operation #s3 cppValue *= #t1 cppValue scoreboard players operation #s1 cppValue /= #7100 cppValue scoreboard players operation #s2 cppValue /= #7100 cppValue scoreboard players operation #s3 cppValue /= #7100 cppValue scoreboard players operation #x cppValue += #s1 cppValue scoreboard players operation #y cppValue += #s2 cppValue scoreboard players operation #z cppValue += #s3 cppValue summon marker ~ ~ ~ {Tags:["cpp_locate_block","cpp_temp"]} execute if score #sf cppValue matches 0.. store result entity @e[type=marker,distance=..6.5,tag=cpp_locate_block,tag=cpp_temp,limit=1] Pos[0] double 0.00014084507042254 run scoreboard players get #x cppValue execute if score #sg cppValue matches 0.. store result entity @e[type=marker,distance=..6.5,tag=cpp_locate_block,tag=cpp_temp,limit=1] Pos[1] double 0.00014084507042254 run scoreboard players get #y cppValue execute if score #sh cppValue matches 0.. store result entity @e[type=marker,distance=..6.5,tag=cpp_locate_block,tag=cpp_temp,limit=1] Pos[2] double 0.00014084507042254 run scoreboard players get #z cppValue execute if score #sf cppValue matches ..-1 store result entity @e[type=marker,distance=..6.5,tag=cpp_locate_block,tag=cpp_temp,limit=1] Pos[0] double -0.00014084507042254 run scoreboard players get #x cppValue execute if score #sg cppValue matches ..-1 store result entity @e[type=marker,distance=..6.5,tag=cpp_locate_block,tag=cpp_temp,limit=1] Pos[1] double -0.00014084507042254 run scoreboard players get #y cppValue execute if score #sh cppValue matches ..-1 store result entity @e[type=marker,distance=..6.5,tag=cpp_locate_block,tag=cpp_temp,limit=1] Pos[2] double -0.00014084507042254 run scoreboard players get #z cppValue execute at @e[type=marker,distance=..10,tag=cpp_locate_block,tag=cpp_temp] if entity @s[distance=..8] run function cpp:locate/loop100
当标记位置所有方块均不符合我们要求时,我们检测邻近6个位置。
cpp:locate/neighbor
execute at @e[type=marker,distance=..10,tag=cpp_locate_block] run function cpp:locate/neighbor_aec kill @e[type=marker,distance=..10,tag=cpp_locate_block,tag=!cpp_locate_block_neighbor]
cpp:locate/neighbor_aec
summon marker ~1 ~ ~ {Tags:["cpp_locate_block","cpp_locate_block_neighbor"]} summon marker ~-1 ~ ~ {Tags:["cpp_locate_block","cpp_locate_block_neighbor"]} summon marker ~ ~1 ~ {Tags:["cpp_locate_block","cpp_locate_block_neighbor"]} summon marker ~ ~-1 ~ {Tags:["cpp_locate_block","cpp_locate_block_neighbor"]} summon marker ~ ~ ~1 {Tags:["cpp_locate_block","cpp_locate_block_neighbor"]} summon marker ~ ~ ~-1 {Tags:["cpp_locate_block","cpp_locate_block_neighbor"]}
尽管看上去比较长,但是递归的次数很少,命令数比视线追踪法还是要少很多。设置为71/710/7100倍是为了保证记分板的计算过程中不会超过记分板的上下限而溢出。
§6.3 命令方块替换法
为了避免视线追踪不够精确,以及为了避免放置于不完整方块侧面等情形而导致方块不在视线上,我们可以考虑使用特殊方块来识别玩家放置的方块。一种是使用生存不存在的方块,例如石化橡木台阶,一种是使用具有特定NBT的实体方块,例如带有特定CustomName的容器。检测完成后应该将方块替换或修改掉,以保证后续放置的方块仍然具有独一性。该方式在服务中需要启用命令方块方可。
cpp/loot_tables/crafting_machine.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:petrified_oak_slab", "functions": [ { "function": "minecraft:set_nbt", "tag": "{id:\"cpp:crafting_machine\",CustomModelData:12970001}" } ] } ] } ] }
cpp:advancements/blocks/petrified_oak_slab.json
{ "criteria": { "petrified_oak_slab": { "trigger": "minecraft:placed_block", "conditions": { "item": { "items": ["minecraft:petrified_oak_slab"] } } } }, "rewards": { "function": "cpp:blocks/petrified_oak_slab/reset" } }
cpp:blocks/petrified_oak_slab/reset
advancement revoke @s only cpp:blocks/petrified_oak_slab fill ~-6 ~-6 ~-6 ~6 ~6 ~6 command_block{auto:1b,Command:"execute align xyz positioned ~0.5 ~ ~0.5 run function cpp:blocks/petrified_oak_slab/crafting_machine"} replace petrified_oak_slab execute positioned ~ -6 ~ as @s[dy=12] run fill ~-6 0 ~-6 ~6 12 ~6 command_block{auto:1b,Command:"execute align xyz positioned ~0.5 ~ ~0.5 run function cpp:blocks/petrified_oak_slab/crafting_machine"} replace petrified_oak_slab execute positioned ~ 249 ~ as @s[dy=12] run fill ~-6 243 ~-6 ~6 255 ~6 command_block{auto:1b,Command:"execute align xyz positioned ~0.5 ~ ~0.5 run function cpp:blocks/petrified_oak_slab/crafting_machine"} replace petrified_oak_slab
cpp:blocks/petrified_oak_slab/crafting_machine
setblock ~ ~ ~ barrel{CustomName:'{"translate":"block.minecraft.petrified_oak_slab"}'} summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Small:1b,Marker:1b,NoGravity:1b,DisabledSlots:7967,Tags:["cpp_blocks"]} execute as @e[type=armor_stand,tag=cpp_blocks,distance=..0.1,limit=1] run function cpp:blocks/petrified_oak_slab/tag
该方法也可以不借助命令方块。将玩家附近的方块复制到一定高度后,将所有方块破坏之,然后特定掉落物的相对位置就是相应方块位置。不过需要注意可能会影响原有方块或掉落物。也可以使用实体标记,通过修改其 Pos
来遍历玩家附近位置,找到特定方块时执行对应操作即可,不过这至多需要执行1000多次函数,开销并不小。
§6.4 破坏事件
高频检测标记所在位置的方块是否发生变化。若不符合要求,则表示该方块已被破坏(或被活塞推动),此时检测附近相应掉落物并修改之,然后US杀死标记。
cpp:tick
execute as @e[type=armor_stand,tag=cpp_leaves] at @s unless block ~ ~ ~ acacia_leaves run function cpp:blocks/acacia_leaves/break
cpp:blocks/acacia_leaves/break
# 树叶 execute as @e[type=item,sort=nearest,predicate=cpp:item/dropped_acacia_leaves,limit=1,distance=..3] unless data entity @s Item.tag.id run tag @s add cpp_block_drop data modify entity @e[type=item,sort=nearest,tag=cpp_block_drop,limit=1,distance=..3] Item set from entity @s ArmorItems[3] tag @e[type=item,distance=..3,tag=cpp_block_drop] remove cpp_block_drop # 树苗 execute as @e[type=item,sort=nearest,predicate=cpp:item/dropped_acacia_sapling,limit=1,distance=..3] run tag @s add cpp_block_drop execute as @s[tag=cpp_ore_leaves] at @e[type=item,sort=nearest,tag=cpp_block_drop,limit=1,distance=..3] run loot spawn ~ ~ ~ loot cpp:ore_sapling execute as @s[tag=cpp_wool_leaves] at @e[type=item,sort=nearest,tag=cpp_block_drop,limit=1,distance=..3] run loot spawn ~ ~ ~ loot cpp:wool_sapling execute as @s[tag=cpp_fruit_leaves] at @e[type=item,sort=nearest,tag=cpp_block_drop,limit=1,distance=..3] run loot spawn ~ ~ ~ loot cpp:fruit_sapling execute as @s[tag=cpp_sakura_leaves] at @e[type=item,sort=nearest,tag=cpp_block_drop,limit=1,distance=..3] run loot spawn ~ ~ ~ loot cpp:sakura_sapling kill @e[type=item,distance=..3,tag=cpp_block_drop] kill @s
cpp/predicates/item/dropped_acacia_leaves.json
{ "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "nbt": "{Age:0s,Item:{id:\"minecraft:acacia_leaves\",Count:1b}}" } }
cpp/predicates/item/dropped_acacia_sapling.json
{ "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "nbt": "{Age:0s,Item:{id:\"minecraft:acacia_sapling\",Count:1b}}" } }, { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "nbt": "{Age:1s,Item:{id:\"minecraft:acacia_sapling\",Count:1b}}" } } ] }
对于树苗我们需要添加 Age:0s
(挖掘)和 Age:1s
(自然腐烂)两种情形。
§6.5 模型设置
使用如下的 display
来放缩物品在头部的大小,可以保证其与坐标轴基本对齐。
assets/cpp/models/material/moon_stone.json
{ "parent": "block/cube_all", "display": { "head": { "rotation": [ 0, 0, 0 ], "translation": [ 0, -14.65, 0 ], "scale": [ 2.29, 2.29, 2.29 ] } }, "textures": { "all": "cpp:material/moon_stone" } }
§7 机器设计
本节我们将通过一个较为复杂的机器的例子,来了解如何设计一个机器。
首先参考 §5 物品设计和 §6 方块设计将机器方块设计好,我们这里选择木桶,木桶的优点是打开时没有动画,且栏位数比较多。如果对透明度有要求,使用箱子也可以,但要注意箱子有打开动画,以及避免出现大箱子的问题。
cpp/loot_tables/all_in_one_machine.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:barrel", "functions": [ { "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"translate\":\"item.mac.all_in_one_machine\"}'},id:\"cpp:all_in_one_machine\",CustomModelData:12970001}" } ] } ] } ] }
然后绘制 GUI。这个机器左侧包括三个按钮,分别表示温度、压强和输出方向。输出方向用于机器自动将输出栏物品输入到相应方向邻近的容器。
- GUI 整体是左上槽位的一个物品放大一定倍数并平移之后得到的。
- 按钮、进度条、能源条均为物品模型。我们将使用经验作为能源。
- 其余背景位置以及输出槽均为完全透明物品的模型。
§7.1 GUI纹理模型
物品的默认大小为 16×16
,所以如果我们想要将其放大 n
倍,就需要绘制大小为 16n×16n
的纹理,以对齐边缘和像素。
该模型用于机器的GUI,我们将其大小放大了 12
倍,也就是 192×192
大小。所以我们需要绘制这个大小的整数倍的纹理。通过修改z轴高度可调整纹理的覆盖次序。
assets/cpp/models/machine/gui/template.json
{ "elements": [ { "from": [ -16, 0, 0 ], "to": [ 32, 16, 1 ], "faces": { "south": { "uv":[0,0,16,16],"texture": "#layer0"} } } ], "gui_light": "front", "display": { "gui": { "scale": [ 3.375, 3.375, 1 ], "translation": [ 72, -18, -80] }, "ground": { "scale": [ 0, 0, 0 ] } } }
assets/cpp/models/machine/gui/all_in_one_machine.json
{ "parent": "cpp:machine/gui/template", "textures": { "layer0": "cpp:machine/gui/all_in_one_machine" } }
这个图片大小为 162×54
,即木桶所有栏位的大小。当该物品放置在木桶的第一格栏位时,经过上述放缩和平移,该物品的纹理正好和木桶的物品栏对齐。
若想要绘制各边缘超过这个距离限制的,可以重新调整 display.gui.translation
来对齐。更多注意事项请参阅§3.6 模型。
对于机器的按钮,为了保证我们点击时不会出现按钮乱跑的问题,我们可以在按钮的实际模型放置在点击处的右一格,但这并不能避免玩家点击右一格时按钮的闪烁问题。为简便起见,我们不采用该技巧。
assets/cpp/models/machine/option/temp.json
{ "parent": "item/generated", "display": { "gui": { "translation": [ -18, 0, 1 ] }, "ground": { "scale": [ 0, 0, 0 ] } } }
assets/cpp/models/machine/option/high_pressure.json
{ "parent": "cpp:machine/option/temp", "textures": { "layer0": "cpp:machine/option/high_pressure" } }
对于机器的能源条,我们使用三个模型来实现。这样的优点是如果当我们需要更换纹理时,只需要改变能源条的模型和纹理即可。
第一个是能源条,能源条为动态纹理,位于最底层
assets/cpp/models/machine/xp/xp.json
{ "parent": "item/generated", "textures": { "layer0": "cpp:machine/xp/xp" }, "display": { "gui": { "scale": [ 3.125, 3.125, 1 ], "translation": [17, -17, -65] }, "ground": { "scale": [ 0, 0, 0 ] } } }
assets/cpp/textures/machine/xp/xp.png.mcmeta
{ "animation": { "frametime": 2 } }
第二个是能源标尺框架,位于中间层。
assets/cpp/models/machine/xp/frame.json
{ "parent": "item/generated", "display": { "gui": { "scale": [ 1.125, 3.25, 0 ], "translation": [ 0, 19, -55] }, "ground": { "scale": [ 0, 0, 0 ] } }, "textures": { "layer0": "cpp:machine/xp/frame" } }
我们将能源条划分为0~100
共101种情形,第三层是用于遮挡能源条的101个与背景色相同的纯色方块。通过设置方块的高度放缩比例和平移,我们可以组合出只显示部分能源条的效果。公式为 scale[1]=0.03125×(100-n),translation[1]=1+0.25n
。
assets/cpp/models/machine/xp/88.json
{ "parent": "cpp:machine/xp/temp", "display": { "gui": { "scale": [1, 0.375, 1], "translation": [0, 23, -60] } } }
assets/cpp/models/machine/xp/temp.json
{ "parent": "item/generated", "textures": { "layer0": "cpp:pt" }, "display": { "ground": { "scale": [ 0, 0, 0 ] } } }
如果你擅长绘制UI的话,可以制作出很精美的UI。例如该视频中的GUI均是采用物品纹理绘制的。
§7.2 GUI背景处理
我们为所有的机器背景物品添加 exClear:1b
。我们假设这个物品都是 firework_star
。如果是多种物品的话,我们可以将这些物品 id
添加至一个物品标签内来统一清理。
清理玩家背包和地面的相应物品。
cpp:tick
clear @a firework_star{exClear:1b} kill @e[type=item,nbt={Item:{tag:{exClear:1b}}}] execute as @e[type=armor_stand,tag=machine_machine] at @s run function cpp:blocks/machine
禁用机器下方的漏斗矿车内,并将下方漏斗的冷却时间高频设置为2。
cpp:blocks/machine
execute positioned ~-1 ~-2 ~-1 as @e[type=hopper_minecart,dx=2,dy=2,dz=2] run data modify entity @s Enabled set value 0b execute if block ~ ~-1 ~ hopper run data modify block ~ ~-1 ~ TransferCooldown set value 2
§7.3 GUI命令
我们需要对机器高频固定住其GUI,同时对于玩家误放入的物品进行弹出处理,并操作机器选项和经验条。
cpp:all_in_one_machine/tick
function cpp:misc/xp execute unless predicate cpp:all_in_one_machine/gui run function cpp:all_in_one_machine/gui execute unless data block ~ ~ ~ Items[{Slot:21b}] run item block ~ ~ ~ container.21 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.output_slot"}'},CustomModelData:12970000} execute unless data block ~ ~ ~ Items[{Slot:22b}] run item block ~ ~ ~ container.22 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.output_slot"}'},CustomModelData:12970000} execute as @s[tag=!cpp_machine_work] run function cpp:all_in_one_machine/idle tag @s remove cpp_machine_work execute unless predicate cpp:power/strong as @s[scores={cppExp=1..}] if block ~ ~ ~ barrel{Items:[{Slot:3b},{Slot:4b},{Slot:21b,tag:{cppClear:1b}},{Slot:22b,tag:{cppClear:1b}}]} run function cpp:all_in_one_machine/type execute unless data block ~ ~ ~ Items[{Slot:21b,tag:{cppClear:1b}}] run function cpp:dist/dist21 execute unless data block ~ ~ ~ Items[{Slot:22b,tag:{cppClear:1b}}] run function cpp:dist/dist22
cpp/predicates/all_in_one_machine/gui.json
{ "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{cppClear:1b}},{Slot:1b,tag:{cppClear:1b}},{Slot:2b,tag:{cppClear:1b}},{Slot:5b,tag:{cppClear:1b}},{Slot:6b,tag:{cppClear:1b}},{Slot:8b,tag:{cppClear:1b}},{Slot:9b,tag:{cppClear:1b}},{Slot:10b,tag:{cppClear:1b}},{Slot:11b,tag:{cppClear:1b}},{Slot:12b,tag:{cppClear:1b}},{Slot:13b,tag:{cppClear:1b}},{Slot:14b,tag:{cppClear:1b}},{Slot:15b,tag:{cppClear:1b}},{Slot:16b,tag:{cppClear:1b}},{Slot:17b,tag:{cppClear:1b}},{Slot:18b,tag:{cppClear:1b}},{Slot:19b,tag:{cppClear:1b}},{Slot:20b,tag:{cppClear:1b}},{Slot:23b,tag:{cppClear:1b}},{Slot:24b,tag:{cppClear:1b}},{Slot:25b,tag:{cppClear:1b}},{Slot:26b,tag:{cppClear:1b}}]}" } } }
处理经验:当经验瓶栏放入了附魔之瓶时,进行清理和增加经验的操作,并刷新显示能源条。我们用战利品表来动态设置能源数值显示。
cpp:misc/xp
execute as @s[scores={cppExp=0..991}] if data block ~ ~ ~ Items[{Slot:7b,id:"minecraft:experience_bottle"}] run function cpp:misc/xp_add
cpp:misc/xp_add
item block ~ ~ ~ container.7 modify cpp:minus scoreboard players add @s cppExp 9 function cpp:misc/xp_show
cpp:misc/xp_show
loot replace block ~ ~ ~ container.8 1 loot cpp:misc/xp_show1 loot replace block ~ ~ ~ container.17 1 loot cpp:misc/xp_show1 loot replace block ~ ~ ~ container.26 1 loot cpp:misc/xp_show2 scoreboard players operation #t cppValue = @s cppExp execute unless score #t cppValue matches 0..1000 run scoreboard players set #t cppValue 1000 execute store result block ~ ~ ~ Items[{Slot:17b}].tag.CustomModelData int 0.1 run scoreboard players add #t cppValue 129720000
cpp/loot_tables/xp_show1.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cppClear:1b,display:{Name:'{\"italic\":false,\"translate\":\"item.cpp.xp_bar\"}'},CustomModelData:12972102}" }, { "function": "minecraft:set_lore", "lore": [ [{"score":{"name":"@s","objective":"cppExp"},"color":"green","italic":false},{"text":"/1000"}] ], "entity": "this", "replace": true } ] } ] } ] }
cpp/loot_tables/xp_show2.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cppClear:1b,display:{Name:'{\"italic\":false,\"translate\":\"item.cpp.xp_bar\"}'},CustomModelData:12972101}" }, { "function": "minecraft:set_lore", "lore": [ [{"score":{"name":"@s","objective":"cppExp"},"color":"green","italic":false},{"text":"/1000"}] ], "entity": "this", "replace": true } ] } ] } ] }
对于玩家误放入背景的物品,我们使用 §10.2 修改玩家背包中的潜影盒战利品表来将物品输出到容器中。
cpp:all_in_one_machine/gui
# 动态弹出非法放入的物品 setblock ~ 255 ~ shulker_box data modify block ~ 255 ~ Items set from block ~ ~ ~ Items data remove block ~ 255 ~ Items[{tag:{cppClear:1b}}] data remove block ~ 255 ~ Items[{Slot:3b}] data remove block ~ 255 ~ Items[{Slot:4b}] data remove block ~ 255 ~ Items[{Slot:7b}] data remove block ~ 255 ~ Items[{Slot:21b}] data remove block ~ 255 ~ Items[{Slot:22b}] loot give @p mine ~ 255 ~ tnt{drop_content:1b} setblock ~ 255 ~ air # 选项 execute unless data block ~ ~ ~ Items[{Slot:1b}] run function cpp:all_in_one_machine/option/pressure execute unless data block ~ ~ ~ Items[{Slot:10b}] run function cpp:all_in_one_machine/option/temperature execute unless data block ~ ~ ~ Items[{Slot:19b}] run function cpp:misc/output # 重置背景 item block ~ ~ ~ container.0 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.all_in_one_machine"}'},CustomModelData:12971003} loot replace block ~ ~ ~ container.1 1 loot cpp:all_in_one_machine/pressure item block ~ ~ ~ container.2 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.all_in_one_machine"}'},CustomModelData:12970000} item block ~ ~ ~ container.5 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.all_in_one_machine"}'},CustomModelData:12970000} item block ~ ~ ~ container.6 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.all_in_one_machine"}'},CustomModelData:12970000} item block ~ ~ ~ container.9 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.all_in_one_machine"}'},CustomModelData:12970000} loot replace block ~ ~ ~ container.10 1 loot cpp:all_in_one_machine/temperature item block ~ ~ ~ container.11 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.all_in_one_machine"}'},CustomModelData:12970000} item block ~ ~ ~ container.12 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.process_shower"}'},CustomModelData:12971080} item block ~ ~ ~ container.13 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.process_shower"}'},CustomModelData:12970000} item block ~ ~ ~ container.14 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.all_in_one_machine"}'},CustomModelData:12970000} item block ~ ~ ~ container.15 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.all_in_one_machine"}'},CustomModelData:12970000} item block ~ ~ ~ container.16 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.all_in_one_machine"}'},CustomModelData:12970000} item block ~ ~ ~ container.18 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.all_in_one_machine"}'},CustomModelData:12970000} loot replace block ~ ~ ~ container.19 1 loot cpp:misc/switch item block ~ ~ ~ container.20 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.all_in_one_machine"}'},CustomModelData:12970000} item block ~ ~ ~ container.23 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.all_in_one_machine"}'},CustomModelData:12970000} item block ~ ~ ~ container.24 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.all_in_one_machine"}'},CustomModelData:12970000} item block ~ ~ ~ container.25 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.xp_remove"}'},CustomModelData:12970000} function cpp:misc/xp_show
当玩家点击按钮时,调整选项。
cpp:all_in_one_machine/option/pressure
scoreboard players add @s cppAiomPres 1 scoreboard players set @s[scores={cppAiomPres=3..}] cppAiomPres 0 scoreboard players set @s[tag=!cpp_high_pressure,scores={cppAiomPres=2}] cppAiomPres 0 scoreboard players set @s[tag=!cpp_low_pressure,scores={cppAiomPres=0}] cppAiomPres 1
cpp/loot_tables/all_in_one_machine/pressure.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cppClear:1b,display:{Lore:['{\"translate\":\"lore.cpp.switch\"}']}}" }, { "conditions": [ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppAiomPres": 0 } } ], "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"italic\":false,\"translate\":\"item.cpp.low_pressure\"}'},CustomModelData:12971051}" }, { "conditions": [ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppAiomPres": 1 } } ], "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"italic\":false,\"translate\":\"item.cpp.normal_pressure\"}'},CustomModelData:12971052}" }, { "conditions": [ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppAiomPres": 2 } } ], "function": "minecraft:set_nbt", "tag": "{display:{Name:'{\"italic\":false,\"translate\":\"item.cpp.high_pressure\"}'},CustomModelData:12971053}" } ] } ] } ] }
机器空闲时进行一些重置操作。
cpp:all_in_one_machine/idle
data modify block ~ ~ ~ Items[{Slot:12b}].tag.CustomModelData set value 12971080 data modify entity @s ArmorItems[3].tag.CustomModelData set value 12970001 scoreboard players set @s cppTick 0
然后判断机器处是否有红石信号强充能,断言 cpp:power/strong
见 §10.3 红石信号。当机器未被强充能、有经验值、有输入物、输出栏空闲时,进入配方判断。这里我们根据要求的温度压强来分类。
cpp:all_in_one_machine/type
function #cpp:all_in_one_machine execute as @s[scores={cppAiomTemp=0,cppAiomPres=0}] run function cpp:all_in_one_machine/recipes/ll/ll_1_1 execute as @s[scores={cppAiomTemp=0,cppAiomPres=1}] run function cpp:all_in_one_machine/recipes/ln/ln_1_1 execute as @s[scores={cppAiomTemp=0,cppAiomPres=2}] run function cpp:all_in_one_machine/recipes/lh execute as @s[scores={cppAiomTemp=1,cppAiomPres=0}] run function cpp:all_in_one_machine/recipes/nl execute as @s[scores={cppAiomTemp=1,cppAiomPres=1}] run function cpp:all_in_one_machine/recipes/nn execute as @s[scores={cppAiomTemp=1,cppAiomPres=2}] run function cpp:all_in_one_machine/recipes/nh execute as @s[scores={cppAiomTemp=2,cppAiomPres=0}] run function cpp:all_in_one_machine/recipes/hl/hl_4_10 execute as @s[scores={cppAiomTemp=2,cppAiomPres=1}] run function cpp:all_in_one_machine/recipes/hn execute as @s[scores={cppAiomTemp=2,cppAiomPres=2}] run function cpp:all_in_one_machine/recipes/hh execute unless data block ~ ~ ~ Items[{Slot:21b,tag:{cppClear:1b}}] run function cpp:all_in_one_machine/clear execute as @s[scores={cppTick=1..}] run function cpp:all_in_one_machine/option/process data modify entity @s[tag=cpp_machine_work] ArmorItems[3].tag.CustomModelData set value 12970101
显示进度条。这里我们预先计算了半个进度条的刻数,然后加到当前刻数,计算后取整,这样得到进度条更加准确。
cpp:all_in_one_machine/option/process
scoreboard players set #t cppValue 16 scoreboard players operation #t cppValue *= @s cppTick scoreboard players operation #t cppValue += #process_pre cppValue scoreboard players operation #t cppValue /= $allInOneMachinePeriod cppConfig execute store result block ~ ~ ~ Items[{Slot:12b}].tag.CustomModelData int 1 run scoreboard players add #t cppValue 12971080
最后我们将输出栏的物品输入到邻近的容器内。
cpp:dist/dist21
data modify storage cpp:dist Item set from block ~ ~ ~ Items[{Slot:21b}] tag @s remove cpp_dist_success function cpp:dist/machine execute as @s[tag=cpp_dist_success] run item block ~ ~ ~ container.21 replace firework_star{cppClear:1b,display:{Name:'{"italic":false,"translate":"item.cpp.output_slot"}'},CustomModelData:12970000}
§7.4 物品输出
上一节我们调用了一个函数用于输出物品,我们来看一下它的算法。
根据机器的输出方向调整位置。
cpp:dist/machine
execute as @s[scores={cppMacOut=1}] positioned ~1 ~ ~ run function cpp:dist/pos execute as @s[scores={cppMacOut=2}] positioned ~ ~ ~1 run function cpp:dist/pos execute as @s[scores={cppMacOut=3}] positioned ~-1 ~ ~ run function cpp:dist/pos execute as @s[scores={cppMacOut=4}] positioned ~ ~ ~-1 run function cpp:dist/pos execute as @s[scores={cppMacOut=5}] positioned ~ ~-1 ~ run function cpp:dist/pos execute as @s[scores={cppMacOut=6}] positioned ~ ~1 ~ run function cpp:dist/pos
我们把大箱子添加至方块标签中。如果是大箱子的右半个,将其修改为左半个位置。
cpp/function/dist/pos
execute if block ~ ~ ~ #cpp:chests[type=left,facing=east] positioned ~ ~ ~1 run function cpp:dist/pos2 execute if block ~ ~ ~ #cpp:chests[type=left,facing=west] positioned ~ ~ ~-1 run function cpp:dist/pos2 execute if block ~ ~ ~ #cpp:chests[type=left,facing=south] positioned ~-1 ~ ~ run function cpp:dist/pos2 execute if block ~ ~ ~ #cpp:chests[type=left,facing=north] positioned ~1 ~ ~ run function cpp:dist/pos2 execute unless block ~ ~ ~ #cpp:chests[type=left] run function cpp:dist/pos2
我们通过插入包含一个不可堆叠的物品的战利品表,并比较有物品的栏位数有没有发生改变来判断其是否有空栏位。如果当前位置没有空栏位且为大箱子,调整输出的位置。
cpp/function/dist/pos2
clone ~ ~ ~ ~ ~ ~ ~ 255 ~ execute store result score #t cppValue run data get block ~ 255 ~ Items loot insert ~ 255 ~ loot cpp:misc/iron_axe execute store result score #s cppValue run data get block ~ 255 ~ Items setblock ~ 255 ~ air execute if score #t cppValue = #s cppValue run tag @s add cpp_container_full execute as @s[tag=!cpp_container_full] run function cpp:dist/dist execute as @s[tag=!cpp_dist_success,tag=cpp_container_full] if block ~ ~ ~ #cpp:chests[type=right,facing=east] positioned ~ ~ ~-1 run function cpp:dist/double execute as @s[tag=!cpp_dist_success,tag=cpp_container_full] if block ~ ~ ~ #cpp:chests[type=right,facing=west] positioned ~ ~ ~1 run function cpp:dist/double execute as @s[tag=!cpp_dist_success,tag=cpp_container_full] if block ~ ~ ~ #cpp:chests[type=right,facing=south] positioned ~1 ~ ~ run function cpp:dist/double execute as @s[tag=!cpp_dist_success,tag=cpp_container_full] if block ~ ~ ~ #cpp:chests[type=right,facing=north] positioned ~-1 ~ ~ run function cpp:dist/double tag @s remove cpp_container_full
cpp:dist/double
clone ~ ~ ~ ~ ~ ~ ~ 255 ~ execute store result score #t cppValue run data get block ~ 255 ~ Items loot insert ~ 255 ~ loot cpp:misc/iron_axe execute store result score #s cppValue run data get block ~ 255 ~ Items setblock ~ 255 ~ air execute unless score #t cppValue = #s cppValue run function cpp:dist/dist
最后使用 §10.2 修改玩家背包中的潜影盒战利品表来将物品输出到容器中。
cpp:dist/dist
tag @s add cpp_dist_success setblock ~ 255 ~ shulker_box data remove storage cpp:dist Item.Slot data modify block ~ 255 ~ Items[{Slot:0b}] merge from storage cpp:dist Item loot insert ~ ~ ~ mine ~ 255 ~ tnt{drop_content:1b} setblock ~ 255 ~ air
§7.5 配方处理
我们使用断言判断机器的物品是否满足相应配方的条件,然后进入计时。计时完成后进行产物生成,产物的生成通过使用战利品表判断当前位置的NBT得到。
cpp:all_in_one_machine/recipes/nn
execute as @s[scores={cppExp=2..}] if predicate cpp:all_in_one_machine/nn_2_2 run function cpp:all_in_one_machine/recipes/nn/nn_2_2
cpp/predicates/all_in_one_machine/nn_2_2.json
{ "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{tag:{id:\"cpp:fertilizer\"}},{id:\"minecraft:wheat_seeds\"}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{tag:{id:\"cpp:fertilizer\"}},{id:\"minecraft:beetroot_seeds\"}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{tag:{id:\"cpp:fertilizer\"}},{id:\"minecraft:carrot\"}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{tag:{id:\"cpp:fertilizer\"}},{id:\"minecraft:potato\"}]}" } } } ] }
这里需要注意一种极端情形,如果同一条件下的函数中,有多条判断原材料的命令,而且这些为无序配方(即不检测具体栏位),而且所有产物和原材料中,有部分物品可以进行其它合成,那么我们需要在这些判断中添加 as @s[tag=!mac_machine_work]
或者 if block ~ ~ ~ barrel{Items:[{Slot:16b,tag:{exClear:1b}}]}
来对机器是否已工作进行判断,否则有可能导致前一个配方的产物正常被后一个配方识别到,而导致配方出现混乱。
cpp:all_in_one_machine/recipes/nn/nn_2_2
tag @s add cpp_machine_work scoreboard players add @s cppTick 30 execute if score @s cppTick >= $allInOneMachinePeriod cppConfig run function cpp:all_in_one_machine/recipes/nn/nn_2_2_done
cpp:all_in_one_machine/recipes/nn/nn_2_2_done
scoreboard players remove @s cppExp 2 loot replace block ~ ~ ~ container.21 2 loot cpp:all_in_one_machine/nn_2_2
cpp/loot_tables/all_in_one_machine/nn_2_2.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:alternatives", "children": [ { "conditions": [ { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{tag:{id:\"cpp:fertilizer\"}},{id:\"minecraft:wheat_seeds\"}]}" } } } ], "type": "minecraft:loot_table", "name": "cpp:all_in_one_machine/items/wheats" }, { "conditions": [ { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{tag:{id:\"cpp:fertilizer\"}},{id:\"minecraft:beetroot_seeds\"}]}" } } } ], "type": "minecraft:loot_table", "name": "cpp:all_in_one_machine/items/beetroots" }, { "conditions": [ { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{tag:{id:\"cpp:fertilizer\"}},{id:\"minecraft:carrot\"}]}" } } } ], "type": "minecraft:item", "name": "minecraft:carrot", "functions":[ { "function": "minecraft:set_count", "count": 6 } ] }, { "conditions": [ { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{tag:{id:\"cpp:fertilizer\"}},{id:\"minecraft:potato\"}]}" } } } ], "type": "minecraft:item", "name": "minecraft:potato", "functions":[ { "function": "minecraft:set_count", "count": 6 } ] } ] } ] } ] }
这样,输出槽就产生了物品,然后通过上一节中相关函数命令,进入清理环节。如果你的配方并不是100%有产物的,则需要在计时完成中添加机器完成工作的标签,然后根据标签来判断是否进入清理环节。
cpp:all_in_one_machine/clear
scoreboard players set @s cppTick 0 item block ~ ~ ~ container.4 modify cpp:minus item block ~ ~ ~ container.3 modify cpp:minus function cpp:misc/xp_show
对于特殊的物品,例如药水、水桶等,我们可能希望能够返还玻璃瓶、桶等,此时我们需要进行额外的预处理。
execute if data block ~ ~ ~ Items[{id:"minecraft:water_bucket"}] run function cpp:crafting_machine/craft/clear/water_bucket
我们通过计算单个堆叠和多个堆叠的栏位数,来得到该有多少玻璃瓶进入输出的容器。如果容器满了,计算进入玩家背包的数量,处理应当留下的数量。尽管水桶等物品不可堆叠,但我们仍保留考虑其它模组使其可堆叠的情形。
cpp:crafting_machine/craft/clear/water_bucket
data modify storage cpp:_ Items set from block ~ ~ ~ Items execute store result score #t1 cppValue run data remove storage cpp:_ Items[{id:"minecraft:water_bucket",Count:1b}] execute store result score #t2 cppValue run data remove storage cpp:_ Items[{id:"minecraft:water_bucket"}] data modify storage cpp:dist Item set value {id:"minecraft:bucket",Count:1b} execute store result storage cpp:dist Item.Count byte 1 run scoreboard players operation #t1 cppValue += #t2 cppValue tag @s remove cpp_dist_success function cpp:dist/machine execute as @s[tag=!cpp_dist_success] run data remove block ~ ~ ~ Items[{id:"minecraft:water_bucket",Count:1b}].tag execute as @s[tag=!cpp_dist_success] if data block ~ ~ ~ Items[{id:"minecraft:water_bucket",Count:1b}] run data modify block ~ ~ ~ Items[{id:"minecraft:water_bucket",Count:1b}] merge value {id:"minecraft:bucket",Count:2b} execute as @s[tag=!cpp_dist_success] run loot give @p loot cpp:crafting_machine/items/bucket
§7.6 插件
前面我们提到机器有多种模式,包括温度和压强的调整。我们可以要求玩家使用特定物品插件shift右击机器来安装。插件原型为胡萝卜钓竿。
我们省略副手情形的处理。
cpp:tick
execute as @a[scores={cppUseCSt=1..}] run function cpp:use_cst/type
cpp:use_cst/type
scoreboard players reset @s cppUseCSt execute as @s[predicate=cpp:mainhand/carrot_on_a_stick] run function cpp:use_cst/mainhand execute as @s[predicate=!cpp:mainhand/carrot_on_a_stick] run function cpp:use_cst/offhand
cpp:use_cst/mainhand
execute as @s[predicate=cpp:mainhand/machine_plugin] at @s anchored eyes run function cpp:all_in_one_machine/plugin/ray
cpp:all_in_one_machine/ray
function cpp:locate/init execute as @e[type=marker,distance=..10,tag=cpp_locate_block] at @s if block ~ ~ ~ barrel align xyz positioned ~0.5 ~ ~0.5 if entity @e[type=armor_stand,distance=..0.5,tag=cpp_all_in_one_machine] run tag @s add cpp_locate_block_position execute at @e[type=marker,distance=..6.1,tag=cpp_locate_block_position,sort=nearest,limit=1] align xyz positioned ~0.5 ~ ~0.5 run function cpp:all_in_one_machine/plugin/type kill @e[type=marker,distance=..10,tag=cpp_locate_block]
cpp:all_in_one_machine/plugin/type
execute as @s[predicate=!cpp:mainhand/carrot_on_a_stick] run function cpp:all_in_one_machine/plugin/off execute as @s[predicate=cpp:mainhand/carrot_on_a_stick] run function cpp:all_in_one_machine/plugin/main
cpp:all_in_one_machine/plugin/main
execute as @s[predicate=cpp:mainhand/high_pressure_plugin] if entity @e[type=armor_stand,tag=cpp_all_in_one_machine,distance=..0.5,limit=1] if entity @e[type=armor_stand,tag=cpp_all_in_one_machine,distance=..0.5,limit=1,tag=!cpp_high_pressure] run function cpp:all_in_one_machine/plugin/hp execute as @s[predicate=cpp:mainhand/low_pressure_plugin] if entity @e[type=armor_stand,tag=cpp_all_in_one_machine,distance=..0.5,limit=1] if entity @e[type=armor_stand,tag=cpp_all_in_one_machine,distance=..0.5,limit=1,tag=!cpp_low_pressure] run function cpp:all_in_one_machine/plugin/lp execute as @s[predicate=cpp:mainhand/high_temperature_plugin] if entity @e[type=armor_stand,tag=cpp_all_in_one_machine,distance=..0.5,limit=1] if entity @e[type=armor_stand,tag=cpp_all_in_one_machine,distance=..0.5,limit=1,tag=!cpp_high_temperature] run function cpp:all_in_one_machine/plugin/ht execute as @s[predicate=cpp:mainhand/low_temperature_plugin] if entity @e[type=armor_stand,tag=cpp_all_in_one_machine,distance=..0.5,limit=1] if entity @e[type=armor_stand,tag=cpp_all_in_one_machine,distance=..0.5,limit=1,tag=!cpp_low_temperature] run function cpp:all_in_one_machine/plugin/lt
cpp:all_in_one_machine/plugin/hp
tellraw @s [{"translate":"info.cpp.high_pressure_plugin"}] tag @e[type=armor_stand,tag=cpp_all_in_one_machine,distance=..0.5,limit=1,tag=!cpp_high_pressure] add cpp_high_pressure function cpp:all_in_one_machine/plugin/clear
cpp:all_in_one_machine/plugin/clear
item entity @s[gamemode=!creative,predicate=!cpp:mainhand/carrot_on_a_stick] weapon.offhand replace air item entity @s[gamemode=!creative,predicate=cpp:mainhand/carrot_on_a_stick] weapon.mainhand replace air
我们也可以使用 §10.5 方块交互的方式来安装插件。
§7.7 容器扩展
在 §7.4 物品输出中,方块标签 #cpp:chests
记录了各种大箱子。如果添加更多可支持的容器,我们只需添加它们至该方块标签即可。
cpp/tags/blocks/chests.json
{ "replace": false, "values": [ "minecraft:chest", "minecraft:trapped_chest", { "id": "stonechest:chest_andesite", "required": false}, { "id": "stonechest:chest_cobblestone", "required": false}, { "id": "stonechest:chest_diorite", "required": false}, { "id": "stonechest:chest_granite", "required": false}, { "id": "stonechest:chest_stone", "required": false}, { "id": "quark:spruce_chest", "required": false}, { "id": "quark:birch_chest", "required": false}, { "id": "quark:jungle_chest", "required": false}, { "id": "quark:acacia_chest", "required": false}, { "id": "quark:dark_oak_chest", "required": false}, { "id": "quark:oak_chest", "required": false}, { "id": "quark:bamboo_chest", "required": false}, { "id": "quark:driftwood_chest", "required": false}, { "id": "quark:poise_chest", "required": false}, { "id": "quark:willow_chest", "required": false}, { "id": "quark:wisteria_chest", "required": false}, { "id": "quark:nether_brick_chest", "required": false}, { "id": "quark:purpur_chest", "required": false}, { "id": "quark:prismarine_chest", "required": false}, { "id": "quark:mushroom_chest", "required": false}, { "id": "quark:crimson_chest", "required": false}, { "id": "quark:warped_chest", "required": false}, { "id": "quark:wisteria_chest", "required": false}, { "id": "quark:wisteria_chest", "required": false}, { "id": "quark:wisteria_chest", "required": false} ] }
§7.8 接口
函数 cpp:all_in_one_machine/type
中包含一条命令 function #cpp:all_in_one_machine
。我们也创建了相应的函数标签。然后其他开发者便可向该函数标签写入内容来达到扩展或修改内容的目的,这也在 §4.3 前置与附属中提及。例如
foo:all_in_one_machine/type
execute as @s[scores={cppTemperature=0,cppPressure=0}] run function foo:all_in_one_machine/recipes/bar
然后类似创建后续的断言、战利品表、函数。
§7.9 管道
科技类模组人们需要添加管道来传输电力、流体、物品等。物品管道是类似的,我们将物品的信息存储在管道上并传输即可,最终将其输入到容器中。常见的几种做法有:
§7.9.1 无线传输
优点是简洁易操作,判断输出电力的机器附近是否有需要输入电力的机器并计算即可。
cpp:tick
execute as @e[type=armor_stand,tag=machine_machine,tag=cpp_power_source,scores={cppPower=1..}] at @s if entity @e[type=armor_stand,tag=machine_machine,tag=cpp_power_target,distance=..16] run function cpp:machine/power/translate
cpp:machine/power/translate
tag @s add cpp_power_out scoreboard players operation #t0 cppPower = @e[type=armor_stand,tag=machine_machine,tag=cpp_power_source,scores={cppPower=1..},distance=..1,limit=1] cppPower execute as @e[type=armor_stand,tag=machine_machine,tag=cpp_power_target] if score @s cppPower < @s cppPowerMax run function cpp:machine/power/translate1 scoreboard players operation @e[type=armor_stand,tag=machine_machine,tag=cpp_power_source,scores={cppPower=1..},distance=..1,limit=1] cppPower -= #t0 cppPower tag @s remove cpp_power_out
cpp:machine/power/translate1
scoreboard players operation #t cppPower = @s cppPowerMax scoreboard players operation #t cppPower -= @s cppPower execute if score #t cppPower > #t0 cppPower run scoreboard players operation #t cppPower = #t0 cppPower scoreboard players operation @s cppPower += #t cppPower scoreboard players operation #t0 cppPower -= #t cppPower
我们也可以在传输中添加类似投射物的特效来提高视觉效果。
§7.9.2 管道式
优点是具有传统科技模组的风格,缺点是每个管道都是一个实体,管道多的时候会较为卡顿。若不用实体仅用原版特定方块来表示导线,则限制颇多。
涌流式:能量从高处向周围比它低的管道/机器处流动,来最终实现能量传输。管道内会留有缓存。实现和无线传输类似,只是范围限定在1米内。
遍历式:从能量源出发,遇到过的管道做标记一直向下,如果遇到终点就回溯到分叉点,一直循环直到返回源头。
具体实现方式省略。
§7.9.3 激光式
为了降低管道式的管道数量,我们可以将管道替换为可以一次传输多格的激光式管道。这里我们使用展示框来表示,计算其与前方方块的距离并调整外观(长度)。
cpp:tick
execute as @e[type=item_frame,tag=cpp_wire] at @s run function cpp:wire/face execute as @e[type=armor_stand,tag=cpp_power_out,scores={cppPower=1..}] at @s run function cpp:power/face
cpp:wire/face
scoreboard players set @s cppValue 7500000 execute as @s[nbt={Facing:0b}] align xyz positioned ~0.5 ~-1 ~0.5 run function cpp:wire/down execute as @s[nbt={Facing:1b}] align xyz positioned ~0.5 ~1 ~0.5 run function cpp:wire/up execute as @s[nbt={Facing:2b}] align xyz positioned ~0.5 ~ ~-0.5 run function cpp:wire/north execute as @s[nbt={Facing:3b}] align xyz positioned ~0.5 ~ ~1.5 run function cpp:wire/south execute as @s[nbt={Facing:4b}] align xyz positioned ~-0.5 ~ ~0.5 run function cpp:wire/west execute as @s[nbt={Facing:5b}] align xyz positioned ~1.5 ~ ~0.5 run function cpp:wire/east execute store result entity @s Item.tag.CustomModelData int 1 run scoreboard players get @s cppValue
cpp:wire/east
scoreboard players add @s cppValue 1 execute if block ~ ~ ~ #cpp:fluid positioned ~1 ~ ~ run function cpp:wire/east
同样我们递归向前来获得接收能量的方块。
cpp:power/face
execute positioned ~0.5 ~0.5 ~ if entity @e[tag=cpp_wire,distance=..0.1] align xyz positioned ~1.5 ~ ~0.5 run function cpp:power/east execute positioned ~-0.5 ~0.5 ~ if entity @e[tag=cpp_wire,distance=..0.1] align xyz positioned ~-1.5 ~ ~0.5 run function cpp:power/west execute positioned ~ ~0.5 ~0.5 if entity @e[tag=cpp_wire,distance=..0.1] align xyz positioned ~0.5 ~ ~1.5 run function cpp:power/south execute positioned ~ ~0.5 ~-0.5 if entity @e[tag=cpp_wire,distance=..0.1] align xyz positioned ~0.5 ~ ~-1.5 run function cpp:power/north execute positioned ~ ~1 ~ if entity @e[tag=cpp_wire,distance=..0.1] align xyz positioned ~0.5 ~2 ~0.5 run function cpp:power/up execute if entity @e[tag=cpp_wire,distance=..0.1] align xyz positioned ~0.5 ~-2 ~0.5 run function cpp:power/down
cpp:power/east
execute if entity @s[distance=..16] if entity @e[tag=cpp_power_in,distance=..0.1] entity @e[tag=cpp_power_in,distance=..0.1,limit=1,scores={cppPower=..9999}] if score @e[tag=cpp_power_in,distance=..0.1,limit=1] cppPower < @e[tag=cpp_power_in,distance=..0.1,limit=1] cppPowerLimit run function cpp:power/in execute if entity @s[distance=..16] unless entity @e[tag=cpp_power_in,distance=..0.1] positioned ~1 ~ ~ run function cpp:power/east
cpp:power/in
scoreboard players remove @s cppPower 1 scoreboard players add @e[tag=cpp_power_in,distance=..0.1,limit=1] cppPower 1
§7.9.4 载体式
我们使用一个可移动的实体来传输能量,这个实体受方块控制。
cpp:golem/tick
data merge entity @s {PortalCooldown:900} execute if block ~ ~ ~ #cpp:golem_east run scoreboard players set @s cppGolemFace 0 execute if block ~ ~ ~ #cpp:golem_south run scoreboard players set @s cppGolemFace 1 execute if block ~ ~ ~ #cpp:golem_west run scoreboard players set @s cppGolemFace 2 execute if block ~ ~ ~ #cpp:golem_north run scoreboard players set @s cppGolemFace 3 execute if block ~ ~ ~ #cpp:golem_up run scoreboard players set @s cppGolemFace 4 execute if block ~ ~ ~ #cpp:golem_down run scoreboard players set @s cppGolemFace 5 execute as @s[scores={cppGolemFace=0}] run tp @s ~1 ~0 ~0 270 0 execute as @s[scores={cppGolemFace=1}] run tp @s ~0 ~0 ~1 0 0 execute as @s[scores={cppGolemFace=2}] run tp @s ~-1 ~0 ~0 90 0 execute as @s[scores={cppGolemFace=3}] run tp @s ~0 ~0 ~-1 180 0 execute as @s[scores={cppGolemFace=4}] run tp @s ~0 ~1 ~0 0 90 execute as @s[scores={cppGolemFace=5}] run tp @s ~0 ~-1 ~0 0 -90 execute as @s[scores={cppPower=1..}] if entity @e[type=armor_stand,tag=machine_machine,tag=cpp_power_in] run function cpp:power/translate
然后和其它传输方式一样,计算传输值即可。
§7.9.5 管道设计
管道模型通常是不完整方块,我们可以使用屏障方块来作为管道的本体方块,使用扳手(胡萝卜钓竿)来右键拆解。
管道需要高频判断是否与周边方块连接,以调整其自身显示。
cpp:pipe/tick
scoreboard players set @s cppModel 12970000 execute positioned ~1 ~ ~ if entity @e[type=armor_stand,tag=cpp_pipe,distance=..0.2] run scoreboard players add @s cppModel 1 execute positioned ~-1 ~ ~ if entity @e[type=armor_stand,tag=cpp_pipe,distance=..0.2] run scoreboard players add @s cppModel 2 execute positioned ~ ~ ~1 if entity @e[type=armor_stand,tag=cpp_pipe,distance=..0.2] run scoreboard players add @s cppModel 4 execute positioned ~ ~ ~-1 if entity @e[type=armor_stand,tag=cpp_pipe,distance=..0.2] run scoreboard players add @s cppModel 8 execute positioned ~ ~1 ~ if entity @e[type=armor_stand,tag=cpp_pipe,distance=..0.2] run scoreboard players add @s cppModel 16 execute positioned ~ ~-1 ~ if entity @e[type=armor_stand,tag=cpp_pipe,distance=..0.2] run scoreboard players add @s cppModel 32 execute store result entity @s ArmorItems[3].tag.CustomModelData int 1 run scoreboard players get @s cppModel
§8 NBT合成与烧炼
§8.1 地板合成
将物品扔在地面进行合成,是模组配方数量少时采用的一种便捷做法。优点是无需设计 GUI。
将磁铁(id:"cpp:magnet"
)和4个钻石合成为充能指南针(id:"cpp:powered_magnet"
)。
cpp:tick
execute as @e[type=item,nbt={Item:{tag:{id:"cpp:magnet"}}}] at @s if entity @e[type=item,distance=..1,nbt={Item:{id:"minecraft:diamond",Count:4b}}] run function cpp:powered_magnet
cpp:powered_magnet
kill @e[type=item,limit=1,distance=..1,nbt={Item:{id:"minecraft:diamond",Count:4b}}] kill @s summon item ~ ~ ~ {Item:{id:"minecraft:compass",Count:1b,tag:{id:"cpp:powered_magnet"}}}
§8.2 实体背包合成
利用玩家的背包、末影箱、箱子矿车、驴等生物的背包用于合成,也是一种较为便捷的方式,不受方块的限制。
由于玩家的NBT难以修改,玩家背包和末影箱并不便于合成处理,而且一般只能用于有序合成。其它情形可以通过不指定 Slot
来实现无序合成。
当合成配方较多时,我们应当考虑分类以降低每刻命令数。常见的做法是先按配方的原材料种类数、或者合成的形状分类。
利用玩家背包的右方3×3区域进行合成。
cpp:tick
scoreboard players set @a cppCraftSlot 0 scoreboard players add @a[nbt={Inventory:[{Slot:15b}]}] cppCraftSlot 1 scoreboard players add @a[nbt={Inventory:[{Slot:16b}]}] cppCraftSlot 1 scoreboard players add @a[nbt={Inventory:[{Slot:17b}]}] cppCraftSlot 1 scoreboard players add @a[nbt={Inventory:[{Slot:24b}]}] cppCraftSlot 1 scoreboard players add @a[nbt={Inventory:[{Slot:25b}]}] cppCraftSlot 1 scoreboard players add @a[nbt={Inventory:[{Slot:26b}]}] cppCraftSlot 1 scoreboard players add @a[nbt={Inventory:[{Slot:33b}]}] cppCraftSlot 1 scoreboard players add @a[nbt={Inventory:[{Slot:34b}]}] cppCraftSlot 1 scoreboard players add @a[nbt={Inventory:[{Slot:35b}]}] cppCraftSlot 1 execute as @a[scores={cppCraftSlot=1..}] run function cpp:craft
cpp:craft
execute if predicate cpp:craft/items run tag @s add cpp_craft execute if predicate cpp:craft/items run loot replace entity @s inventory.16 1 loot cpp:craft/items item entity @s[tag=cpp_craft] inventory.6 replace air item entity @s[tag=cpp_craft] inventory.7 replace air item entity @s[tag=cpp_craft] inventory.8 replace air item entity @s[tag=cpp_craft] inventory.15 replace air item entity @s[tag=cpp_craft] inventory.17 replace air item entity @s[tag=cpp_craft] inventory.24 replace air item entity @s[tag=cpp_craft] inventory.25 replace air item entity @s[tag=cpp_craft] inventory.26 replace air tag @s remove cpp_craft
cpp/predicates/craft/items
{ "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:reference", "name": "cpp:craft/items1" }, { "condition": "minecraft:reference", "name": "cpp:craft/items2" }, { "condition": "minecraft:reference", "name": "cpp:craft/items3" }, { "condition": "minecraft:reference", "name": "cpp:craft/items4" }, { "condition": "minecraft:reference", "name": "cpp:craft/items5" }, { "condition": "minecraft:reference", "name": "cpp:craft/items6" }, { "condition": "minecraft:reference", "name": "cpp:craft/items7" }, { "condition": "minecraft:reference", "name": "cpp:craft/items8" }, { "condition": "minecraft:reference", "name": "cpp:craft/items9" } ] }
cpp/predicates/craft/items5
[ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppCraftSlot": 5 } }, { "condition": "minecraft:alternative", "terms": [ { "_comment": "酸水", "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:6b,Count:1b,id:\"minecraft:sugar\"},{Slot:7b,Count:1b,id:\"minecraft:rotten_flesh\"},{Slot:8b,Count:1b,id:\"minecraft:glistering_melon_slice\"},{Slot:15b,Count:1b,id:\"minecraft:gunpowder\"},{Slot:16b,Count:1b,id:\"minecraft:potion\",tag:{Potion:\"minecraft:water\"}}]}" } } } ] } ]
cpp/loot_tables/craft/items
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:alternatives", "children": [ { "conditions": [ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppCraftSlot": 1 } } ], "type": "minecraft:loot_table", "name": "cpp:craft/items1" }, { "conditions": [ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppCraftSlot": 2 } } ], "type": "minecraft:loot_table", "name": "cpp:craft/items2" }, { "conditions": [ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppCraftSlot": 3 } } ], "type": "minecraft:loot_table", "name": "cpp:craft/items3" }, { "conditions": [ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppCraftSlot": 4 } } ], "type": "minecraft:loot_table", "name": "cpp:craft/items4" }, { "conditions": [ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppCraftSlot": 5 } } ], "type": "minecraft:loot_table", "name": "cpp:craft/items5" }, { "conditions": [ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppCraftSlot": 6 } } ], "type": "minecraft:loot_table", "name": "cpp:craft/items6" }, { "conditions": [ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppCraftSlot": 7 } } ], "type": "minecraft:loot_table", "name": "cpp:craft/items7" }, { "conditions": [ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppCraftSlot": 8 } } ], "type": "minecraft:loot_table", "name": "cpp:craft/items8" }, { "conditions": [ { "condition": "minecraft:entity_scores", "entity": "this", "scores": { "cppCraftSlot": 9 } } ], "type": "minecraft:loot_table", "name": "cpp:craft/items9" } ] } ] } ] }
cpp/loot_tables/craft/items5
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:alternatives", "children": [ { "_comment": "酸水", "conditions": [ { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:6b,id:\"minecraft:sugar\"},{Slot:7b,id:\"minecraft:rotten_flesh\"},{Slot:8b,id:\"minecraft:glistering_melon_slice\"},{Slot:15b,id:\"minecraft:gunpowder\"},{Slot:16b,id:\"minecraft:potion\",tag:{Potion:\"minecraft:water\"}}]}" } } } ], "type": "minecraft:loot_table", "name": "cpp:acid" } ] } ] } ] }
§8.3 容器合成
使用投掷器、箱子、木桶等容器可以更为便捷地实现合成。我们可以直接使用容器,或者使用 §7 机器设计的方法设计好自定义工作台的GUI。
输出方式我们可以选择输出到输出栏(木桶)、输出到自身(精准匹配+投掷器)、弹出。具体实现与 §7 机器设计并无二致。
和上一节类似,我们仍然使用战利品表谓词来进行判断并使用战利品表得到产物。最后做清理。对于需要返还物品的处理,见 §7.5 配方处理。
cpp:crafting_machine/type
execute if predicate cpp:crafting_machine/items run loot replace block ~ ~ ~ container.16 1 loot cpp:crafting_machine/items execute unless data block ~ ~ ~ Items[{Slot:16b,tag:{cppClear:1b}}] run function cpp:crafting_machine/clear/input
cpp:crafting_machine/clear/input
execute if data block ~ ~ ~ Items[{id:"minecraft:water_bucket"}] run function cpp:crafting_machine/clear/water_bucket execute if data block ~ ~ ~ Items[{id:"minecraft:lava_bucket"}] run function cpp:crafting_machine/clear/lava_bucket execute if data block ~ ~ ~ Items[{id:"minecraft:milk_bucket"}] run function cpp:crafting_machine/clear/milk_bucket execute if data block ~ ~ ~ Items[{id:"minecraft:cod_bucket"}] run function cpp:crafting_machine/clear/cod_bucket execute if data block ~ ~ ~ Items[{id:"minecraft:salmon_bucket"}] run function cpp:crafting_machine/clear/salmon_bucket execute if data block ~ ~ ~ Items[{id:"minecraft:tropical_fish_bucket"}] run function cpp:crafting_machine/clear/tropical_fish_bucket execute if data block ~ ~ ~ Items[{id:"minecraft:pufferfish_bucket"}] run function cpp:crafting_machine/clear/pufferfish_bucket execute if data block ~ ~ ~ Items[{id:"minecraft:axolot_bucket"}] run function cpp:crafting_machine/clear/axolot_bucket execute if data block ~ ~ ~ Items[{id:"minecraft:potion"}] run function cpp:crafting_machine/clear/potion execute if data block ~ ~ ~ Items[{id:"minecraft:honey_bottle"}] run function cpp:crafting_machine/clear/honey_bottle item block ~ ~ ~ container.1 modify cpp:minus item block ~ ~ ~ container.2 modify cpp:minus item block ~ ~ ~ container.3 modify cpp:minus item block ~ ~ ~ container.10 modify cpp:minus item block ~ ~ ~ container.11 modify cpp:minus item block ~ ~ ~ container.12 modify cpp:minus item block ~ ~ ~ container.19 modify cpp:minus item block ~ ~ ~ container.20 modify cpp:minus item block ~ ~ ~ container.21 modify cpp:minus
§8.4 NBT烧炼
§8.4.1 替换产物法
放置熔炉、高炉、烟熏炉、营火的过程已省略。我们假设相应方块位置已有标签为 cpp_furnace
的盔甲架。
将蛋烧炼为战利品表物品 cpp:egg_stew
。创建烧炼配方以激活烧炼蛋。
cpp/recipes/egg.json
{ "type": "smelting", "ingredient": { "item": "minecraft:egg" }, "result": "minecraft:mushroom_stew", "experience": 0.1, "cookingtime": 200 }
然后在烧炼时间达到 199s
时,替换熔炉内的物品。
cpp:tick
execute as @e[type=armor_stand,tag=cpp_furnace] at @s unless block ~ ~ ~ furnace run kill @s execute as @e[type=armor_stand,tag=cpp_furnace] at @s if block ~ ~ ~ furnace{CookTime:199s} run function cpp:foods/cook/check
cpp:foods/cook/check
execute if block ~ ~ ~ furnace{Items:[{Slot:0b,id:"minecraft:egg"}]} run function cpp:foods/cook/egg_stew
cpp:foods/cook/egg_stew
loot replace entity @s weapon.mainhand 1 loot cpp:egg_stew data modify block ~ ~ ~ Items[{Slot:2b}] set from entity @s HandItems[0] item entity @s weapon.mainhand replace minecraft:air execute store result block ~ ~ ~ Items[0].Count byte 0.999 run data get block ~ ~ ~ Items[0].Count data merge block ~ ~ ~ {CookTime:0s}
注意这里不能通过战利品表将产物loot replace到熔炉的输出栏。
这种做法的局限性一是若要求待烧炼物含指定NBT,则必须添加该物品的的烧炼配方,这就导致即使没有相应的NBT也会烧炼,个人建议为原物品也添加合理的烧炼配方,或者烧炼配方的产物和待烧炼物相同,同时不奖励经验值;二是即使产物应当可堆叠也无法正常堆叠,这时需配合漏斗等设备传输方可持续烧炼。
情形 | 添加配方 | 注意事项 |
---|---|---|
原版物品A->NBT物品B | A->B | 在烧炼即将完成时生成产物。 |
NBT物品A->原版物品B | A->B | 同上一条。对于原版物品A,还需要清理产物或添加其它合适的烧炼效果。 |
A->C | 同上一条。产物无法堆叠,需配合漏斗等设备传输方可持续烧炼。 | |
NBT物品A->NBT物品B | A->B | 同上上条。若有其它烧炼配方可以得到原版物品B,则进行相应配方烧炼时的产物会直接变为NBT物品B,因此要排除这种情形。 |
A->C | 同前两条。 |
§8.4.2 记分板模拟
另一种做法就是并不添加相应的烧炼配方,而是通过记分板计时来达到控制烧炼时间的目的。当物品进入熔炉后,判断其是否需要烧炼以及是否有燃料,然后判断输出栏是否为空或其产物,如果确实可以烧炼,那么临时存储输入输出栏物品至存储区,然后放入任意可烧炼物品以触发燃料的消耗和烧炼动画。下一刻再将存储区的物品返回熔炉。反复操作直到烧炼时间达到了烧炼该物品所需的时间,此时产出产物即可。(by chaoren019)
该方法在计算时会导致熔炉内物品闪烁一下(这可以通过自定义空白模型的铁矿石来解决),更换不同燃烧值物品时,燃烧火焰条也会有所变化,这和通常烧炼配方有差异。
cpp:furnace/tick
execute unless block ~ ~ ~ furnace run kill @s execute as @s[tag=cpp_check_fuel] run function cpp:furnace/fuel_get execute unless block ~ ~ ~ furnace{Items:[{Slot:2b,Count:64b}]} unless block ~ ~ ~ furnace{Items:[{Slot:2b,Count:16b,id:"minecraft:ender_pearl"}]} if predicate cpp:furnace/furnace run function cpp:furnace/type
当检测到燃料改变时,重新计算燃烧值。注意这需要在检测到改变的下一刻才能获得,因此该部分在最前面。
cpp:furnace/fuel_get
tag @s remove cpp_check_fuel execute store result score @s cppValue run data get block ~ ~ ~ BurnTime item block ~ ~ ~ container.0 replace air data modify block ~ ~ ~ {} merge from entity @s HandItems[0].tag.storage
使用断言和战利品表来记录配方。这里断言需要分输出栏空和输出栏为相应产物两种情形。注意我们需要排除输出栏已满的情形,而这项判断又需要对部分不是64堆叠数的物品单独判断。
cpp/predicates/furnace/furnace.json
{ "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:inverted", "term": { "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:inverted", "term": { "condition": "minecraft:reference", "name": "cpp:furnace/furnace1" } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:2b}]}" } } } ] } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{id:'cpp:clay_bucket'}},{Slot:2b,id:'minecraft:bucket'}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{id:'cpp:lycoris_radiata'}},{Slot:2b,id:'minecraft:red_dye'}]}" } } } ] }
cpp/predicates/furnace/furnace1.json
{ "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{id:'cpp:clay_bucket'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{id:'cpp:lycoris_radiata'}}]}" } } } ] }
cpp:furnace/type
# 燃料改变 execute if block ~ ~ ~ furnace{BurnTime:0s,Items:[{Slot:1b}]} run function cpp:furnace/fuel_change_check # 消耗燃料 execute as @s[tag=!cpp_check_fuel] if block ~ ~ ~ furnace{BurnTime:0s,Items:[{Slot:1b}]} run function cpp:furnace/fuel_use # 进度 execute unless block ~ ~ ~ furnace{BurnTime:0s} store result block ~ ~ ~ CookTime short 1 run scoreboard players add @s cppTick 1 execute if score @s cppTick matches 199.. run function cpp:furnace/done # 停止 execute if block ~ ~ ~ furnace{BurnTime:0s} run scoreboard players reset @s cppTick # 发光 execute if predicate cpp:furnace/lit run function cpp:furnace/lit_true execute if predicate cpp:furnace/unlit run function cpp:furnace/lit_false
在熔炉的盔甲架添加手持物来记录燃料的变化。
cpp:furnace/fuel_change_check
execute store result score #change cppValue run data modify entity @s HandItems[0].tag.id set from block ~ ~ ~ Items[{Slot:1b}].id execute if score #change cppValue matches 1 run function cpp:furnace/fuel_change
cpp:furnace/fuel_change
data modify entity @s HandItems[0].tag.storage set from block ~ ~ ~ item block ~ ~ ~ container.0 replace iron_ore item block ~ ~ ~ container.2 replace air tag @s add cpp_check_fuel
cpp:furnace/fuel_use
execute store result block ~ ~ ~ BurnTime short 1 run scoreboard players get @s cppValue execute if data block ~ ~ ~ Items[{Slot:1b,id:"minecraft:lava_bucket",Count:1b}] run item block ~ ~ ~ container.1 replace bucket 2 item block ~ ~ ~ container.1 modify cpp:minus
烧炼完成时添加获取经验的配方记录,这里我们统一设定为每次0.15点,可以根据需要实现不同配方不同经验值。
cpp:furnace/done
execute if data block ~ ~ ~ {Items:[{Slot:2b}]} run item block ~ ~ ~ container.2 modify cpp:plus execute unless data block ~ ~ ~ {Items:[{Slot:2b}]} run function cpp:smelt/furnace/done_loot item block ~ ~ ~ container.0 modify cpp:minus scoreboard players reset @s cppTick data modify block ~ ~ ~ CookTime set value 0s execute store result score #t cppValue run data get block ~ ~ ~ RecipesUsed."minecraft:charcoal" execute store result block ~ ~ ~ RecipesUsed."minecraft:charcoal" int 1 run scoreboard players add #t cppValue 1
烧炼完毕时,通过战利品表生成产物,注意不能直接 loot replace 进熔炉的输出槽。
cpp:furnace/done_loot
data remove block ~ ~ ~ RecipesUsed clone ~ ~ ~ ~ ~ ~ ~ 255 ~ loot replace block ~ 255 ~ container.0 loot cpp:furnace/furnace data modify block ~ 255 ~ Items[{Slot:0b}].Slot set value 2b data modify block ~ ~ ~ Items append from block ~ 255 ~ Items[{Slot:2b}] setblock ~ 255 ~ air
cpp/loot_tables/furnace/furnace.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:alternatives", "children": [ { "conditions": [ { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{id:'cpp:clay_bucket'}}]}" } } } ], "type": "minecraft:item", "name": "minecraft:bucket" }, { "conditions": [ { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{id:'cpp:lycoris_radiata'}}]}" } } } ], "type": "minecraft:item", "name": "minecraft:red_dye" } ] } ] } ] }
修改熔炉的火焰外观。
cpp:furnace/lit_true
data modify storage cpp:furnace {} set from block ~ ~ ~ {} execute if block ~ ~ ~ furnace[facing=east] run setblock ~ ~ ~ furnace[lit=true,facing=east] execute if block ~ ~ ~ furnace[facing=west] run setblock ~ ~ ~ furnace[lit=true,facing=west] execute if block ~ ~ ~ furnace[facing=south] run setblock ~ ~ ~ furnace[lit=true,facing=south] execute if block ~ ~ ~ furnace[facing=north] run setblock ~ ~ ~ furnace[lit=true,facing=north] data modify block ~ ~ ~ {} set from storage cpp:furnace {}
cpp:furnace/lit_false
data modify storage cpp:furnace {} set from block ~ ~ ~ {} execute if block ~ ~ ~ furnace[facing=east] run setblock ~ ~ ~ furnace[lit=false,facing=east] execute if block ~ ~ ~ furnace[facing=west] run setblock ~ ~ ~ furnace[lit=false,facing=west] execute if block ~ ~ ~ furnace[facing=south] run setblock ~ ~ ~ furnace[lit=false,facing=south] execute if block ~ ~ ~ furnace[facing=north] run setblock ~ ~ ~ furnace[lit=false,facing=north] data modify block ~ ~ ~ {} set from storage cpp:furnace {}
对于烟熏炉和高炉,操作是类似的,但是需要每次增加cppTick 2。营火由于会判断材料是否合法,因此无法用该方法。
§8.5 NBT酿造
类似于自定义烧炼的方法,我们可以自定义酿造。但是由于酿造台的限制,酿造材料必须为原版酿造材料物品,待酿造的物品只能为药水或玻璃瓶。除了红石、萤石、火药、龙息外,建议使用兔子腿,因为兔子腿只有酿造功能。
如果酿造材料和待酿造的物品本身就属于原版可酿造的配方,我们只需在酿造即将完成时,将药水替换掉即可。例如:使用大地之证 (兔子腿 cpp:certification_of_earth
) 将粗制的药水酿造成有多重药水效果的大地药水(potion_of_earth
),同时支持延长、加强、喷溅、滞留版本。
放置和探测酿造台的部分省略。
cpp:tick
execute as @e[type=armor_stand,tag=cpp_brewing_stand] at @s run function cpp:brewing/tick
cpp:brewing/tick
execute unless block ~ ~ ~ brewing_stand run kill @s execute if block ~ ~ ~ brewing_stand{BrewTime:1s,Items:[{Slot:3b,tag:{id:"cpp:certification_of_earth"}}]} run function cpp:brewing/earth execute if block ~ ~ ~ brewing_stand{BrewTime:1s,Items:[{Slot:3b,tag:{id:"cpp:redstone"}}]} if predicate cpp:brewing/long run function cpp:brewing/long execute if block ~ ~ ~ brewing_stand{BrewTime:1s,Items:[{Slot:3b,tag:{id:"cpp:glowstone_dust"}}]} if predicate cpp:brewing/strong run function cpp:brewing/strong execute if block ~ ~ ~ brewing_stand{BrewTime:1s,Items:[{Slot:3b,tag:{id:"cpp:gunpowder"}}]} if predicate cpp:brewing/splash run function cpp:brewing/splash execute if block ~ ~ ~ brewing_stand{BrewTime:1s,Items:[{Slot:3b,tag:{id:"cpp:dragon_breath"}}]} if predicate cpp:brewing/lingering run function cpp:brewing/lingering
我们在大地药水中添加 Potion
,这样后续可以实现延长、加强、喷溅、投掷版本的酿造。
cpp/loot_tables/potion_of_dirt.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "item", "name": "minecraft:potion", "functions": [ { "function": "set_nbt", "tag": "{Potion:'minecraft:leaping',CustomPotionColor:14137114,display:{Name:'{\"translate\":\"item.ex.potion_of_earth\"}'},CustomPotionEffects:[{Id:3b,Duration:3600}],id:'cpp:potion_of_earth'}" } ] } ] } ] }
cpp:brewing/earth
loot replace entity @s weapon.mainhand 1 loot cpp:potion_of_earth data modify block ~ ~ ~ Items[{tag:{Potion:"minecraft:awkward"}}].tag set from entity @s HandItems[0].tag loot replace entity @s weapon.mainhand 1 loot minecraft: execute store result block ~ ~ ~ Items[{Slot:3b}].Count byte 0.999 run data get block ~ ~ ~ Items[{Slot:3b}].Count
cpp/predicates/brewing/long
{ "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{Potion:'minecraft:night_vision'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{Potion:'minecraft:invisibility'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{Potion:'minecraft:leaping'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{Potion:'minecraft:fire_resistance'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{Potion:'minecraft:swiftness'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{Potion:'minecraft:slowness'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{Potion:'minecraft:turtle_master'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{Potion:'minecraft:water_breathing'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{Potion:'minecraft:poison'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{Potion:'minecraft:regeneration'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{Potion:'minecraft:strength'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{Potion:'minecraft:weakness'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:0b,tag:{Potion:'minecraft:slow_falling'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:1b,tag:{Potion:'minecraft:night_vision'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:1b,tag:{Potion:'minecraft:invisibility'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:1b,tag:{Potion:'minecraft:leaping'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:1b,tag:{Potion:'minecraft:fire_resistance'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:1b,tag:{Potion:'minecraft:swiftness'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:1b,tag:{Potion:'minecraft:slowness'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:1b,tag:{Potion:'minecraft:turtle_master'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:1b,tag:{Potion:'minecraft:water_breathing'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:1b,tag:{Potion:'minecraft:poison'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:1b,tag:{Potion:'minecraft:regeneration'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:1b,tag:{Potion:'minecraft:strength'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:1b,tag:{Potion:'minecraft:weakness'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:1b,tag:{Potion:'minecraft:slow_falling'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:2b,tag:{Potion:'minecraft:night_vision'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:2b,tag:{Potion:'minecraft:invisibility'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:2b,tag:{Potion:'minecraft:leaping'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:2b,tag:{Potion:'minecraft:fire_resistance'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:2b,tag:{Potion:'minecraft:swiftness'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:2b,tag:{Potion:'minecraft:slowness'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:2b,tag:{Potion:'minecraft:turtle_master'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:2b,tag:{Potion:'minecraft:water_breathing'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:2b,tag:{Potion:'minecraft:poison'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:2b,tag:{Potion:'minecraft:regeneration'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:2b,tag:{Potion:'minecraft:strength'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:2b,tag:{Potion:'minecraft:weakness'}}]}" } } }, { "condition": "minecraft:location_check", "predicate": { "block": { "nbt": "{Items:[{Slot:2b,tag:{Potion:'minecraft:slow_falling'}}]}" } } } ] }
处理延长版药水时,还需要一并处理原版酿造的药水。加强、喷溅、投掷情形类似处理即可。
cpp:brewing/long
loot replace entity @s weapon.mainhand 1 loot cpp:potion_of_long_earth data modify block ~ ~ ~ Items[{tag:{id:"cpp:potion_of_earth}}].tag set from entity @s HandItems[0].tag loot replace entity @s weapon.mainhand 1 loot minecraft:air data modify block ~ ~ ~ Items:[{tag:{Potion:"minecraft:night_vision"}}]}.tag.Potion set value "minecraft:long_night_vision" data modify block ~ ~ ~ Items:[{tag:{Potion:"minecraft:invisibility"}}]}.tag.Potion set value "minecraft:long_invisibility" data modify block ~ ~ ~ Items:[{tag:{Potion:"minecraft:leaping"}}]}.tag.Potion set value "minecraft:long_leaping" data modify block ~ ~ ~ Items:[{tag:{Potion:"minecraft:fire_resistance"}}]}.tag.Potion set value "minecraft:long_fire_resistance" data modify block ~ ~ ~ Items:[{tag:{Potion:"minecraft:swiftness"}}]}.tag.Potion set value "minecraft:long_swiftness" data modify block ~ ~ ~ Items:[{tag:{Potion:"minecraft:slowness"}}]}.tag.Potion set value "minecraft:long_slowness" data modify block ~ ~ ~ Items:[{tag:{Potion:"minecraft:turtle_master"}}]}.tag.Potion set value "minecraft:long_turtle_master" data modify block ~ ~ ~ Items:[{tag:{Potion:"minecraft:water_breathing"}}]}.tag.Potion set value "minecraft:long_water_breathing" data modify block ~ ~ ~ Items:[{tag:{Potion:"minecraft:poison"}}]}.tag.Potion set value "minecraft:long_poison" data modify block ~ ~ ~ Items:[{tag:{Potion:"minecraft:regeneration"}}]}.tag.Potion set value "minecraft:long_regeneration" data modify block ~ ~ ~ Items:[{tag:{Potion:"minecraft:strength"}}]}.tag.Potion set value "minecraft:long_strength" data modify block ~ ~ ~ Items:[{tag:{Potion:"minecraft:weakness"}}]}.tag.Potion set value "minecraft:long_weakness" data modify block ~ ~ ~ Items:[{tag:{Potion:"minecraft:slow_falling"}}]}.tag.Potion set value "minecraft:long_slow_falling"} execute store result block ~ ~ ~ Items[{Slot:3b}].Count byte 0.999 run data get block ~ ~ ~ Items[{Slot:3b}].Count item entity @s weapon.mainhand replace air
如果酿造材料和待酿造的物品不属于原版可酿造的配方,我们需要在二者均进入酿造台时,将其物品修改为具有相同纹理名称的、可以实现酿造的物品。当物品进入背包时,将其改回原物品。酿造即将完成时,检测相应NBT是否来自指定的物品,并修改产物。
§9 植物
我们使用寻常作物来实现自定义作物。在相应的方块处放置带有自定义物品模型的盔甲架来实现其外观的不同,并高频判断和重置其生长状态来确定生长。模型可以选择盖住原方块模型,或删除原方块模型并将原作物当做自定义作物实现(会生成盔甲架实体)。如果自定义作物的纹理没有覆盖原来的方块,就会导致自定义的作物处,既有自定义作物的纹理,又有原本的作物纹理。解决方法之一是限定自定义作物模型包含原有的作物模型,另一种是删除原有方块纹理并在原方块处放置对应纹理的盔甲架解决。
作物 | 方块需求 | 可使用骨粉 | 模型处理 | 特点 | 用途 |
---|---|---|---|---|---|
小麦、胡萝卜、马铃薯、甜菜根、瓜梗 | 耕地 | 是 | 瓜梗模型最小,自定义模型只要比其大即可。 | 瓜梗模型太小了很难破坏。 | 通常作物、瓜类作物 |
树苗 | 任意土 | 是 | 模型较大,需要删除原模型,由于金合欢树苗出现场合最少,因此以金合欢为宜。深色橡木由于其需要2×2才可生长也有一定优势。 | 有几率在一刻内直接生长为大树 | 花卉、树 |
甘蔗、仙人掌 | 沙子 | 否 | 可以用较粗的类似甘蔗的模型,或完整方块模型来覆盖。 | 需要处理上方一格方块,无法避免玩家手动叠方块 | 甘蔗类 |
草、蕨 | 任意土 | 是 | 可以用较粗的类似草的模型,或完整方块模型来覆盖。 | 需要处理上方一格方块,无法避免玩家手动叠方块 | 甘蔗类 |
可可豆 | 丛林木 | 是 | 可以用略大的方形模型来覆盖 | 可以添加标签来支持种植在其它方块侧 | 树生类 |
竹子、浆果丛、海草、海带 | - | 部分 | 几乎无可能 | 模型随机、性质特殊、水生难使用 | - |
屏障 | 任意 | 否 | 任意 | 无法直接破坏,需要借助其它方式;需要手动实现生长过程 | 任意 |
任意 | 任意 | 否 | 完整方块模型 | 需要手动实现生长过程,一般无法瞬间破坏 | 任意 |
可以看出,较便捷的只有瓜梗+模型覆盖、金合欢/深色橡木树苗+删除原模型、任意方块+完整模型三种做法。对于第二种做法,我们还需要修改村庄等结构中相应的模型,而且在玩家大规模种植时会生成大量实体。
另一种实现方式即使用§6.3 命令方块替换法来放置,只需在放置完成后检测下方不是土时将其破坏掉即可。这种方法较为简单,缺点是物品缺少树苗的诸如可作为燃料、可堆肥的特性。
§9.1 作物
种植铁种子,收获种子和铁锭,可由骨粉催熟。种下后,探测位置放置盔甲架。破坏时,修改附近掉落物。
cpp:advancements/plants/iron_seeds
{ "parent": "cpp:plants/root", "criteria": { "iron_seeds": { "trigger": "minecraft:placed_block", "conditions": { "item": { "nbt":"{id:'cpp:iron_seeds'}" } } } }, "rewards": { "function": "cpp:plants/plants/iron_seeds" } }
cpp:tick
execute as @e[type=armor_stand,tag=cpp_beetroots_plants] at @s unless block ~ ~ ~ beetroots run function cpp:plants/break/beetroots
cpp:plants/break/beetroots
execute as @s[tag=cpp_iron_seeds] as @e[type=item,nbt={Item:{id:"minecraft:beetroot_seeds"},Age:0s},distance=..1] run data merge entity @s {Item:{tag:{HideFlags:63,Enchantments:[{}],display:{Name:'{"translate":"item.ex.iron_seeds"}'},id:"cpp:iron_seeds"}}} execute as @s[tag=cpp_iron_seeds] as @e[type=item,nbt={Item:{id:"minecraft:beetroot"},Age:0s},distance=..1] run data merge entity @s {Item:{id:"minecraft:iron_ingot"}}
实际上,我们也可以通过盔甲架手持物品来实现自定义纹理的作物。
§9.2 花草
我们使用金合欢树苗作为花草原型。方块的放置部分我们省略,只需要注意花草的模型通常和物品模型是不同的,因此在放置后需要修改CustomModelData,破坏情形也是同理。
放置花草种子,长大后收获成熟的花草。
cpp:tick
execute as @e[type=armor_stand,tag=cpp_plants] at @s run function cpp:plants/tick
cpp:plants/tick
# 破坏 execute unless block ~ ~ ~ acacia_sapling run function cpp:block/break/acacia_sapling # 树生长 execute as @s[tag=!cpp_acacia_sapling] if block ~ ~ ~ acacia_sapling[stage=1] run function cpp:plants/type
cpp:plants/type
setblock ~ ~ ~ acacia_sapling execute as @s[tag=cpp_crops] run function cpp:plants/crops/grow execute as @s[tag=cpp_trees] run function cpp:plants/trees/grow
树木的处理见下一节,我们这里只处理花草部分。当花草长到最后一个阶段时,我们将其物品改成成熟的花草,以替代原有的花草种子。
cpp:plants/crops/grow
execute store result score #t cppValue run data get entity @s ArmorItems[3].tag.CustomModelData execute if score #t cppValue matches 12975001..12975060 run scoreboard players add #t cppValue 20 execute if score #t cppValue matches 12975061 run loot replace entity @s armor.head 1 loot cpp:lycoris_radiata execute if score #t cppValue matches 12975062 run loot replace entity @s armor.head 1 loot cpp:trifolium execute if score #t cppValue matches 12975063 run loot replace entity @s armor.head 1 loot cpp:blackthorn execute if score #t cppValue matches 12975064 run loot replace entity @s armor.head 1 loot cpp:cattail execute if score #t cppValue matches 12975065 run loot replace entity @s armor.head 1 loot cpp:marigold execute if score #t cppValue matches 12975066 run loot replace entity @s armor.head 1 loot cpp:hibiscus execute if score #t cppValue matches 12975067 run loot replace entity @s armor.head 1 loot cpp:hyacinth execute if score #t cppValue matches 12975068 run loot replace entity @s armor.head 1 loot cpp:calamus execute if score #t cppValue matches 12975069 run loot replace entity @s armor.head 1 loot cpp:wild_lilium execute if score #t cppValue matches 12975070 run loot replace entity @s armor.head 1 loot cpp:bauhinia execute if score #t cppValue matches 12975071 run loot replace entity @s armor.head 1 loot cpp:fluffy_grass execute if score #t cppValue matches 12975072 run loot replace entity @s armor.head 1 loot cpp:gerbera execute if score #t cppValue matches 12975073 run loot replace entity @s armor.head 1 loot cpp:esparto execute if score #t cppValue matches 12975074 run loot replace entity @s armor.head 1 loot cpp:glow_forsythia execute if score #t cppValue matches 12975075 run loot replace entity @s armor.head 1 loot cpp:glazed_shade execute if score #t cppValue matches 12975076 run loot replace entity @s armor.head 1 loot cpp:stelera execute if score #t cppValue matches 12975077 run loot replace entity @s armor.head 1 loot cpp:forage_crystal execute if score #t cppValue matches 12975078 run loot replace entity @s armor.head 1 loot cpp:isorchid execute if score #t cppValue matches 12975079 run loot replace entity @s armor.head 1 loot cpp:burning_chrysanthe execute if score #t cppValue matches 12975080 run loot replace entity @s armor.head 1 loot cpp:oxalis execute if score #t cppValue matches 12975021..12975080 store result entity @s ArmorItems[3].tag.CustomModelData int 1 run scoreboard players get #t cppValue
§9.3 树
种下矿石树树苗,然后会长大。长大后的树叶会逐渐变为矿石。
cpp:plants/trees/grow
setblock ~ ~ ~ acacia_sapling execute store result score #t cppValue run data get entity @s ArmorItems[3].tag.CustomModelData scoreboard players add @s cppValue 1 execute if score @s cppValue >= $modTreeAgeMax exConfig if score #t cppValue matches 12976002 run function cpp:plants/trees/ore
cpp:plants/trees/ore
execute positioned ~-2 ~3 ~-2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-2 ~3 ~-1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-2 ~3 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-2 ~3 ~1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-2 ~3 ~2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-1 ~3 ~-2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-1 ~3 ~-1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-1 ~3 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-1 ~3 ~1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-1 ~3 ~2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~3 ~-2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~3 ~-1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~3 ~1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~3 ~2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~1 ~3 ~-2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~1 ~3 ~-1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~1 ~3 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~1 ~3 ~1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~1 ~3 ~2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~2 ~3 ~-2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~2 ~3 ~-1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~2 ~3 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~2 ~3 ~1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~2 ~3 ~2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-2 ~4 ~-2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-2 ~4 ~-1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-2 ~4 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-2 ~4 ~1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-2 ~4 ~2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-1 ~4 ~-2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-1 ~4 ~-1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-1 ~4 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-1 ~4 ~1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-1 ~4 ~2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~4 ~-2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~4 ~-1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~4 ~1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~4 ~2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~1 ~4 ~-2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~1 ~4 ~-1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~1 ~4 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~1 ~4 ~1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~1 ~4 ~2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~2 ~4 ~-2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~2 ~4 ~-1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~2 ~4 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~2 ~4 ~1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~2 ~4 ~2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-2 ~5 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-1 ~5 ~-1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-1 ~5 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-1 ~5 ~1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~5 ~-2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~5 ~-1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~5 ~1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~5 ~2 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~1 ~5 ~-1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~1 ~5 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~1 ~5 ~1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~2 ~5 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~-1 ~6 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~6 ~-1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~6 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~ ~6 ~1 run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} execute positioned ~1 ~6 ~ run summon armor_stand ~ ~ ~ {Invulnerable:1b,Invisible:1b,Marker:1b,NoGravity:1b,Small:1b,DisabledSlots:7967,Tags:["cpp_block","cpp_leaves","cpp_need_fire","cpp_leaves_on_tree","cpp_ore_leaves"],Fire:32767s} loot replace entity @e[type=armor_stand,tag=cpp_ore_leaves,distance=..20] armor.head 1 loot cpp:ore_leaves execute at @e[type=armor_stand,tag=cpp_ore_leaves,distance=..20] if block ~ ~ ~ #minecraft:leaves run setblock ~ ~ ~ acacia_leaves replace execute at @e[type=armor_stand,tag=cpp_ore_leaves,distance=..20] run setblock ~ ~ ~ acacia_leaves keep kill @s setblock ~ ~ ~ air fill ~ ~ ~ ~ ~5 ~ spruce_log replace #minecraft:leaves fill ~ ~ ~ ~ ~5 ~ spruce_log keep
然后我们处理树叶转化。我们使用20刻一次的函数。
cpp:tick20
execute store result score #rts cppValue run gamerule randomTickSpeed execute store result score #rtsno cppValue if entity @e[type=armor_stand,tag=cpp_leaves_on_tree] scoreboard players operation #rtsno cppValue *= #rts cppValue scoreboard players operation #leaves_rts cppValue += #rtsno cppValue execute if score #leaves_rts cppValue >= $modLeavesThreshold exConfig as @e[type=armor_stand,tag=cpp_leaves_on_tree,sort=random,limit=1] at @s run function cpp:plants/leaves/type schedule function cpp:tick20 20t
cpp:plants/leaves/type
execute as @s[tag=cpp_ore_leaves] run function cpp:plants/leaves/ore execute as @s[tag=cpp_fruit_leaves] if block ~ ~-1 ~ #cpp:air run function cpp:plants/leaves/fruit scoreboard players set #leaves_rts cppValue 0
cpp:plants/leaves/ore
loot replace entity @s weapon.mainhand 1 loot cpp:misc/ore_leaves execute as @s[nbt={HandItems:[{id:"minecraft:ancient_debris"},{}]}] run setblock ~ ~ ~ ancient_debris execute as @s[nbt={HandItems:[{id:"minecraft:emerald_ore"},{}]}] run setblock ~ ~ ~ emerald_ore execute as @s[nbt={HandItems:[{id:"minecraft:diamond_ore"},{}]}] run setblock ~ ~ ~ diamond_ore execute as @s[nbt={HandItems:[{id:"minecraft:lapis_ore"},{}]}] run setblock ~ ~ ~ lapis_ore execute as @s[nbt={HandItems:[{id:"minecraft:redstone_ore"},{}]}] run setblock ~ ~ ~ redstone_ore execute as @s[nbt={HandItems:[{id:"minecraft:gold_ore"},{}]}] run setblock ~ ~ ~ gold_ore execute as @s[nbt={HandItems:[{id:"minecraft:nether_quartz_ore"},{}]}] run setblock ~ ~ ~ nether_quartz_ore execute as @s[nbt={HandItems:[{id:"minecraft:iron_ore"},{}]}] run setblock ~ ~ ~ iron_ore execute as @s[nbt={HandItems:[{id:"minecraft:coal_ore"},{}]}] run setblock ~ ~ ~ coal_ore kill @s
§10 物品与实体处理
本节内容较杂,主要包括常见的一些物品与实体处理方式。
§10.1 修改玩家背包
在1.17的 item
命令出现之前,我们无法直接对玩家的背包进行修改,想要修改玩家背包就必须借助其它方式。
一种方式是将修改后的物品信息复制到生物头部,并修改生物的死亡战利品表NBT。在战利品表中穷举获得该物品 id
和数量,并使用 copy_nbt
函数来将头部物品的NBT复制到战利品的NBT中。见 关于如何绕过moj不让改玩家的限制。
cpp/loot_tables/misc/loot
{ "type": "entity", "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:alternatives", "children": [ { "type": "item", "name": "minecraft:andesite", "conditions": [ { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "nbt": "{ArmorItems:[{},{},{}{id:'minecraft:andesite'}}" } } ] }, [中间省略其它物品的穷举过程] { "type": "item", "name": "minecraft:zombie_head", "conditions": [ { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "nbt": "{ArmorItems:[{},{},{}{id:'minecraft:zombie_head'}}" } } ] } ], functions": [ { "function": "minecraft:set_count", "count": 2, "conditions": [ { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "nbt": "{ArmorItems:[{},{},{},{Count:2b}]}" } } ] }, [中间省略其它数量的穷举过程] { "function": "minecraft:set_count", "count": 64, "conditions": [ { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "nbt": "{ArmorItems:[{},{},{},{Count:64b}}" } } ] }, { "function": "minecraft:copy_nbt", "source": "this", "ops": [ { "source": "ArmorItems[3].tag", "target": "{}", "op": "merge" } ] } ] } ] } ] }
最后我们使用 loot
函数来将战利品表物品输出到玩家背包中。可以看出,该方法使用的战利品表非常长,而且需要根据版本更新或模组内容添加新的物品 id
。这里我们提及一个可能的误区,一般生物的装备和手持物并不会出现在生物的战利品表中,想要通过直接 loot ... kill
获取生物的装备或手持物是不可能的。
另一种方式是对潜影盒战利品表使用项目 dynamic
,该项目配合 "name": "minecraft:contents"
可以将潜影盒内物品作为战利品表输出。
minecraft/loot_tables/blocks/shulker_box.json
{ "type": "minecraft:block", "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:alternatives", "children": [ { "type": "minecraft:dynamic", "name": "minecraft:contents", "conditions": [ { "condition": "minecraft:match_tool", "predicate": { "nbt":"{drop_content:1b}" } } ] }, { "type": "minecraft:item", "functions": [ { "function": "minecraft:copy_name", "source": "block_entity" }, { "function": "minecraft:copy_nbt", "source": "block_entity", "ops": [ { "source": "Lock", "target": "BlockEntityTag.Lock", "op": "replace" }, { "source": "LootTable", "target": "BlockEntityTag.LootTable", "op": "replace" }, { "source": "LootTableSeed", "target": "BlockEntityTag.LootTableSeed", "op": "replace" } ] }, { "function": "minecraft:set_contents", "entries": [ { "type": "minecraft:dynamic", "name": "minecraft:contents" } ] } ], "name": "minecraft:shulker_box" } ] } ] } ] }
我们将物品复制到潜影盒内,然后使用命令 loot ... mine ~ 255 ~ tnt{drop_content:1b}
。通常我们将这种辅助用的方块放置在 255
高度以免破坏地形和建筑。
除了用于修改玩家背包,该命令还可配合 loot insert
来将物品输出到容器内,并使物品尽量堆叠。这常用于机器的输出,见 物品输出。
§10.2 耐久处理
有时候我们需要模拟使用工具的过程,但工具并没有真正被使用,耐久也没有被消耗。此时我们需要手动对其耐久进行处理。
我们将物品信息复制到存储区 cpp:damage
的 Item
中,然后使用断言判断是否减少耐久。最后判断其是否已经耐久耗尽,若耗尽则清除之。
cpp:damage
execute store result score @s cppValue run data get entity @s SelectItem.tag.Enchantments[{id:"minecraft:unbreaking"}].lvl execute store result score #damage cppValue run data get entity @s SelectedItem.tag.Damage execute if predicate cpp:damage store result storage cpp:storage Damage int 1 run scoreboard players add #damage cppValue 1 item entity @s[nbt=!{SelectedItem:{tag:{Unbreakable:1b}}}] weapon.mainhand modify cpp:set_damage_score]
cpp/predicate/damage.json
{ "condition": "minecraft:value_check", "value": { "type": "minecraft:uniform", "min": 0, "max": { "type": "score", "target": { "type": "context", "target": "this" }, "score": "cppValue" } }, "range": 0 }
cpp/item_modifiers/set_damage_score.json
{ "function": "minecraft:copy_nbt", "source": { "type": "storage", "source": "cpp:storage" }, "ops": [ { "op": "replace", "source": "Damage", "target": "Damage" } ] }
对于1.17之前的版本,我们可以通过生成0~耐久附魔等级
的随机数,并判断是否为0来确定是否消耗耐久,参考§10.1 随机数。
§10.3 红石信号
为了检测当前方块是否被强充能了,我们将所有能激活当前方块的红石元件和方块状态记录在断言文件中来判断。注意我们没有对当前方块是否完整进行检测。陷阱箱触发的红石信号也是无法直接探测到的。
cpp/predicates/power/strong.json
{ "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:location_check", "offsetY": -1, "predicate": { "block": { "blocks": ["minecraft:lever"], "state": { "powered": true, "face": "ceiling" } } } }, { "condition": "minecraft:location_check", "offsetY": 1, "predicate": { "block": { "blocks": ["minecraft:lever"], "state": { "powered": true, "face": "floor" } } } }, { "condition": "minecraft:location_check", "offsetX": 1, "predicate": { "block": { "blocks": ["minecraft:lever"], "state": { "powered": true, "face": "wall", "facing": "east" } } } }, { "condition": "minecraft:location_check", "offsetX": -1, "predicate": { "block": { "blocks": ["minecraft:lever"], "state": { "powered": true, "face": "wall", "facing": "west" } } } }, { "condition": "minecraft:location_check", "offsetZ": 1, "predicate": { "block": { "blocks": ["minecraft:lever"], "state": { "powered": true, "face": "wall", "facing": "south" } } } }, { "condition": "minecraft:location_check", "offsetZ": -1, "predicate": { "block": { "blocks": ["minecraft:lever"], "state": { "powered": true, "face": "wall", "facing": "north" } } } }, { "condition": "minecraft:location_check", "offsetY": -1, "predicate": { "block": { "tag": "minecraft:buttons", "state": { "powered": true, "face": "ceiling" } } } }, { "condition": "minecraft:location_check", "offsetY": 1, "predicate": { "block": { "tag": "minecraft:buttons", "state": { "powered": true, "face": "floor" } } } }, { "condition": "minecraft:location_check", "offsetX": 1, "predicate": { "block": { "tag": "minecraft:buttons", "state": { "powered": true, "face": "wall", "facing": "east" } } } }, { "condition": "minecraft:location_check", "offsetX": -1, "predicate": { "block": { "tag": "minecraft:buttons", "state": { "powered": true, "face": "wall", "facing": "west" } } } }, { "condition": "minecraft:location_check", "offsetZ": 1, "predicate": { "block": { "tag": "minecraft:buttons", "state": { "powered": true, "face": "wall", "facing": "south" } } } }, { "condition": "minecraft:location_check", "offsetZ": -1, "predicate": { "block": { "tag": "minecraft:buttons", "state": { "powered": true, "face": "wall", "facing": "north" } } } }, { "condition": "minecraft:location_check", "offsetY": -1, "predicate": { "block": { "blocks": ["minecraft:redstone_torch","minecraft:redstone_wall_torch"], "state": { "lit": true } } } }, { "condition": "minecraft:location_check", "offsetY": -1, "predicate": { "block": { "blocks": ["minecraft:observer"], "state": { "powered": true, "facing": "down" } } } }, { "condition": "minecraft:location_check", "offsetY": 1, "predicate": { "block": { "blocks": ["minecraft:observer"], "state": { "powered": true, "facing": "up" } } } }, { "condition": "minecraft:location_check", "offsetX": 1, "predicate": { "block": { "blocks": ["minecraft:observer","minecraft:repeater","minecraft:comparator","minecraft:tripwire_hook"], "state": { "powered": true, "facing": "east" } } } }, { "condition": "minecraft:location_check", "offsetX": -1, "predicate": { "block": { "blocks": ["minecraft:observer","minecraft:repeater","minecraft:comparator","minecraft:tripwire_hook"], "state": { "powered": true, "facing": "west" } } } }, { "condition": "minecraft:location_check", "offsetZ": 1, "predicate": { "block": { "blocks": ["minecraft:observer","minecraft:repeater","minecraft:comparator","minecraft:tripwire_hook"], "state": { "powered": true, "facing": "south" } } } }, { "condition": "minecraft:location_check", "offsetZ": -1, "predicate": { "block": { "blocks": ["minecraft:observer","minecraft:repeater","minecraft:comparator","minecraft:tripwire_hook"], "state": { "powered": true, "facing": "north" } } } }, { "condition": "minecraft:location_check", "offsetY": 1, "predicate": { "block": { "tag": "minecraft:wooden_pressure_plates", "state": { "powered": true } } } }, { "condition": "minecraft:location_check", "offsetY": 1, "predicate": { "block": { "blocks": ["minecraft:stone_pressure_plate","minecraft:light_weighted_pressure_plate","minecraft:heavy_weighted_pressure_plate"], "state": { "powered": true } } } }, { "condition": "minecraft:reference", "name": "cpp:power/has_power_block" } ] }
cpp/predicates/power/has_power_block.json
[ { "condition": "minecraft:location_check", "predicate": { "block": { "tag": "cpp:has_power" } } }, { "condition": "minecraft:inverted", "term": { "condition": "minecraft:location_check", "predicate": { "block": { "state": { "power": "0" } } } } } ]
cpp/tags/blocks/has_power.json
{ "replace": false, "values": [ "minecraft:target", "minecraft:sculk_sensor" ] }
弱充能情形更为繁琐。
想要产生一个红石信号,可以通过在当前位置放置红石块再放置空气来产生。注意保存当前方块的信息。这样的红石信号可以用于激活结构方块,因此可用于 §11 世界生成。
§10.4 方块交互
与某些特定方块交互,可通过相应的交互判据的计分板来探测。如果仅需要右击而不打开,可对容器进行上锁,但玩家快捷栏上方会提示容器已上锁,需要使用资源包来消除;或者使用传送来实现强制关闭GUI,但画面会有闪烁感。
右键方块破坏器将其上方的石头变为砂砾,圆石变为沙子。方块破坏器为上锁的熔炉,且相应位置已有盔甲架标记。这里我们直接使用 视线追踪法来获取方块破坏器位置。
cpp:load
scoreboard objectives add cppBsdIntFur minecraft.custom:minecraft.interact_with_furnace
cpp:tick
execute as @a[scores={cppBsdIntFur=1..}] at @s anchored eyes run function cpp:block_breaker/ray
cpp:block_breaker/ray
execute if entity @s[distance=..7] unless block ~ ~ ~ furnace positioned ^ ^ ^0.005 anchored feet run function cpp:block_breaker/ray execute if entity @s[distance=..7] if block ~ ~ ~ furnace{Lock:"zsx<3wtt"} if block ~ ~1 ~ #cpp:block_breaker run function cpp:block_breaker/done scoreboard players reset @s cppBsdIntFur
cpp/tags/blocks/block_breaker
{ "replace": false, "values": [ "minecraft:stone", "minecraft:cobblestone", ] }
cpp:block_breaker/done
execute if block ~ ~1 ~ stone run setblock ~ ~1 ~ gravel execute if block ~ ~1 ~ cobblestone run setblock ~ ~1 ~ sand
同理,插件中的插件安装也可以通过打开木桶的判据来实现。我们使用传送来实现强制关闭GUI。
cpp:load
scoreboard objectives add cppOpenFrame minecraft.custom:minecraft.open_barrel
cpp:tick
execute as @a[tag=machine_close_gui] at @s positioned ~ ~-256 ~ run function cpp:misc/close_gui_back execute as @a[scores={cppOpenFrame=1..}] if predicate cpp:all_in_one_machine/hand_machine_plugin at @s anchored eyes run function cpp:all_in_one_machine/plugin/ray
cpp/predicates/all_in_one_machine/hand_machine_plugin.json
{ "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "mainhand":{ "nbt": "{macMachinePlugin:1b}" } } } }, { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "offhand":{ "nbt": "{macMachinePlugin:1b}" } } } } ] }
cpp:all_in_one_machine/plugin/ray
execute as @s[distance=..6] if block ~ ~ ~ barrel align xyz positioned ~0.5 ~ ~0.5 run function cpp:all_in_one_machine/init execute as @s[distance=..6] unless block ~ ~ ~ barrel positioned ^ ^ ^0.005 anchored feet run function cpp:all_in_one_machine/plugin/ray
cpp:all_in_one_machine/init
execute as @s[nbt=!{SelectedItem:{tag:{macMachinePlugin:1b}}}] run data modify storage cpp:plugin Item set from entity @s Inventory[{Slot:-106b}] execute as @s[nbt={SelectedItem:{tag:{macMachinePlugin:1b}}}] run data modify storage cpp:plugin Item set from entity @s SelectedItem execute if data storage cpp:plugin Item.tag{id:"cpp:high_pressure_plugin"} if entity @e[tag=machine_all_in_one_machine,distance=..0.5,limit=1,tag=!machine_high_pressure] run function cpp:all_in_one_machine/init/high_pressure execute if data storage cpp:plugin Item.tag{id:"cpp:low_pressure_plugin"} if entity @e[tag=machine_all_in_one_machine,distance=..0.5,limit=1,tag=!machine_low_pressure] run function cpp:all_in_one_machine/init/low_pressure execute if data storage cpp:plugin Item.tag{id:"cpp:high_temperature_plugin"} if entity @e[tag=machine_all_in_one_machine,distance=..0.5,limit=1,tag=!machine_high_temperature] run function cpp:all_in_one_machine/init/high_temperature execute if data storage cpp:plugin Item.tag{id:"cpp:low_temperature_plugin"} if entity @e[tag=machine_all_in_one_machine,distance=..0.5,limit=1,tag=!machine_low_temperature] run function cpp:all_in_one_machine/init/low_temperature execute as @s[tag=machine_plugin_used] at @s run function cpp:close_gui tag @s[tag=machine_plugin_used] remove machine_plugin_used
cpp:all_in_one_machine/init/high_pressure
tellraw @s [{"translate":"info.machine.high_pressure_plugin"}] tag @s add machine_plugin_used tag @e[tag=machine_all_in_one_machine,distance=..0.5,limit=1,tag=!machine_high_pressure] add machine_high_pressure clear @s[tag=machine_plugin_used] minecraft:firework_star{id:"cpp:high_pressure_plugin"} 1
cpp:misc/close_gui
summon minecraft:marker ~ ~ ~ {Tags:["machine_close_gui_pos"],Duration:2} summon minecraft:marker ^ ^ ^3 {Tags:["machine_close_gui_facing"],Duration:2} tp @s ~ ~256 ~ tag @s add machine_close_gui
cpp:misc/close_gui_back
tp @s @e[type=marker,tag=machine_close_gui_pos,distance=..10,sort=nearest,limit=1] tag @s remove machine_close_gui execute at @s facing entity @e[type=marker,tag=machine_close_gui_facing,distance=..10,sort=nearest,limit=1] feet run tp ~ ~ ~
§10.5 生物移动
手持绿宝石块的玩家吸引 16
米内的村民到自身位置。
cpp:tick
tag @a remove cpp_player_hand_emerald_block execute as @e[type=villager] at @s if entity @a[distance=1..16,predicate=cpp:hand_emerald_block] run function cpp:misc/attract_villager
cpp/predicates/hand_emerald_block.json
{ "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "mainhand":{ "items": ["minecraft:emerald_block"] } } } }, { "condition": "minecraft:entity_properties", "entity": "this", "predicate": { "equipment": { "offhand":{ "items": ["minecraft:emerald_block"] } } } } ] }
cpp:misc/attract_villager
tag @p[distance=1..16,predicate=cpp:hand_emerald_block] add cpp_player_hand_emerald_block tp @s ~ ~ ~ facing entity @p[tag=cpp_player_hand_emerald_block] data merge entity @s {Motion:[0.0d,-1.0d,0.0d]} execute facing entity @p[tag=cpp_player_hand_emerald_block] feet positioned ^ ^ ^0.75 unless block ~ ~ ~ #cpp:attract_through if block ~ ~1 ~ #cpp:air run data merge entity @s {Motion:[0.0d,1.0d,0.0d]} execute store result score #temp0 cppValue run data get entity @p[tag=cpp_player_hand_emerald_block] Pos[0] 100 execute store result score #temp1 cppValue run data get entity @s Pos[0] 100 execute store result entity @s Motion[0] double 0.0005 run scoreboard players operation #temp0 cppValue -= #temp1 cppValue execute store result score #temp0 cppValue run data get entity @p[tag=cpp_player_hand_emerald_block] Pos[2] 100 execute store result score #temp1 cppValue run data get entity @s Pos[2] 100 execute store result entity @s Motion[2] double 0.0005 run scoreboard players operation #temp0 cppValue -= #temp1 cppValue
cpp/tags/blocks/attract_through.json
{ "replace": false, "values": [ "#cpp:fluid", "#minecraft:saplings", "minecraft:grass", "minecraft:fern", "minecraft:dead_bush", "minecraft:seagrass", "minecraft:sea_pickle", "#minecraft:small_flowers", "minecraft:brown_mushroom", "minecraft:red_mushroom", "minecraft:torch", "minecraft:end_rod", "minecraft:ladder", "minecraft:snow", "#minecraft:carpets", "minecraft:sunflower", "minecraft:lilac", "minecraft:rose_bush", "minecraft:peony", "minecraft:tall_grass", "minecraft:large_fern", "#minecraft:corals", "#minecraft:signs", "minecraft:vine", "minecraft:dead_tube_coral", "minecraft:dead_horn_coral", "minecraft:dead_fire_coral", "minecraft:dead_bubble_coral", "minecraft:dead_brain_coral", "minecraft:dead_tube_coral_fan", "minecraft:dead_horn_coral_fan", "minecraft:dead_fire_coral_fan", "minecraft:dead_bubble_coral_fan", "minecraft:dead_brain_coral_fan", "minecraft:lever", "minecraft:repeater", "minecraft:comparator", "#minecraft:wooden_pressure_plates", "minecraft:light_weighted_pressure_plate", "minecraft:heavy_weighted_pressure_plate", "#minecraft:buttons", "minecraft:redstone_torch", "minecraft:tripwire_hook", "minecraft:tripwire", "#minecraft:doors", "#minecraft:rails", "minecraft:redstone_wire", "#minecraft:walls", "#minecraft:fences", "minecraft:oak_fence_gate", "minecraft:spruce_fence_gate", "minecraft:birch_fence_gate", "minecraft:jungle_fence_gate", "minecraft:acacia_fence_gate", "minecraft:dark_oak_fence_gate" ] }
这里我们进行了繁复的判断是为了让村民能够爬坡,但不能爬过栅栏类方块。如果是想要实现吸引物品掉落物的效果,则很多内容可以省略。
§10.6 交易
使用村民或流浪商人交易是一种常见的特殊物品获得方式。对于村民,我们需要将村民的经验设定为 250
或更高以保证村民是大师,以避免其因工作方块而进行变化,导致设定的交易被覆盖或出现不想要的交易。对于流浪商人,我们需要设置 DespawnDelay
充分大以避免其消失。如果不希望交易产生经验值,需将 rewardExp
设置为 0b
。通过让村民佩戴物品,可以让村民头部实现不同的外观,但是其它部位不能如此修改,使用佩戴在头部全身模型也会因村民头部移动而发生错乱。
建议使用战利品来随机生成村民的交易项。
trade:load
function trade:tick20 20t
trade:tick20
execute as @e[type=wandering_trader,tag=!trade_trade_added] run function trade:check schedule function trade:tick20 20t
trade:check
data merge entity @s {ArmorItems:[{},{},{},{id:"minecraft:carved_pumpkin",Count:1b,tag:{CustomModelData:12970051}}],ArmorDropChances:[-1.0f,-1.0f,-1.0f,-1.0f]} data modify entity @s Offers.Recipes prepend value {buy:{id:"minecraft:stone",Count:64b},sell:{id:"minecraft:emerald",Count:1b},maxUses:12} data modify entity @s Offers.Recipes append value {buy:{id:"minecraft:emerald",Count:64b},sell:{id:"minecraft:stone",Count:1b},maxUses:12} loot replace entity @s weapon.mainhand 1 loot trade:trade/buy data modify entity @s Offers.Recipes[0].buy.id set from entity @s HandItems[0].id data modify entity @s Offers.Recipes[0].buy.Count set from entity @s HandItems[0].Count loot replace entity @s weapon.mainhand 1 loot trade:trade/sell data modify entity @s Offers.Recipes[9].sell.id set from entity @s HandItems[0].id data modify entity @s Offers.Recipes[9].buy.Count set from entity @s HandItems[0].Count item entity @s weapon.mainhand replace air tag @s add trade_trade_added
trade/loot_tables/trade/buy.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:oak_log", "functions": [ { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 36, "max": 40 } } ] }, { "type": "minecraft:item", "name": "minecraft:spruce_log", "functions": [ { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 36, "max": 40 } } ] }, { "type": "minecraft:item", "name": "minecraft:birch_log", "functions": [ { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 36, "max": 40 } } ] }, { "type": "minecraft:item", "name": "minecraft:jungle_log", "functions": [ { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 36, "max": 40 } } ] }, { "type": "minecraft:item", "name": "minecraft:acacia_log", "functions": [ { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 36, "max": 40 } } ] }, { "type": "minecraft:item", "name": "minecraft:dark_oak_log", "functions": [ { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 36, "max": 40 } } ] } ] } ] }
trade/loot_tables/trade/sell.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:piston", "functions": [ { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 4, "max": 6 } } ] }, { "type": "minecraft:item", "name": "minecraft:redstone_lamp", "functions": [ { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 4, "max": 6 } } ] }, { "type": "minecraft:item", "name": "minecraft:tnt", "functions": [ { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 4, "max": 6 } } ] }, { "type": "minecraft:item", "name": "minecraft:observer", "functions": [ { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 4, "max": 6 } } ] } ] } ] }
这样,我们便为每个流浪商人的最前面添加了交易 36-40
个原木换 1
个绿宝石,最后面添加了交易 4-6
个绿宝石换活塞、红石灯、TNT、观察者。
1.13版本可将村民的NBT修改为 {VillagerData:{profession:"minecraft:none"}
以确保村民不含交易。
§10.7 清理特定物品
在自动种植、自动繁殖或放置方块等情形,我们需要对容器内的特定物品进行消耗。例如:当含有 cpp:toughen_hand
的物品展示框下方容器内有胡萝卜、金胡萝卜、蒲公英时,令附近的兔子进入繁殖状态并清除一个相应物品。
cpp:tick
execute as @e[type=item_frame,nbt={Item:{tag:{id:"cpp:toughen_hand"}}}] at @s if entity @e[type=rabbit,distance=..9,nbt={Age:0,InLove:0}] run function cpp:toughen_hand/rabbit_check
cpp:toughen_hand/rabbit_check
scoreboard players set @s cppValue 0 execute if data block ~ ~-1 ~ Items[{id:"minecraft:carrot"}] run scoreboard players set @s cppValue 1 execute if data block ~ ~-1 ~ Items[{id:"minecraft:golden_carrot"}] run scoreboard players set @s cppValue 2 execute if data block ~ ~-1 ~ Items[{id:"minecraft:dandelion"}] run scoreboard players set @s cppValue 3 execute if score @s cppValue matches 1.. run function cpp:toughen_hand/rabbit
cpp:toughen_hand/rabbit
execute if score @s cppValue matches 1 run data modify storage cpp:toughen_hand Item set from block ~ ~-1 ~ Items[{id:"minecraft:carrot"}] execute if score @s cppValue matches 2 run data modify storage cpp:toughen_hand Item set from block ~ ~-1 ~ Items[{id:"minecraft:golden_carrot"}] execute if score @s cppValue matches 3 run data modify storage cpp:toughen_hand Item set from block ~ ~-1 ~ Items[{id:"minecraft:dandelion"}] execute store result storage cpp:toughen_hand Item.Count byte 0.999 run data get storage cpp:toughen_hand Item.Count data modify block ~ ~-1 ~ Items append from storage cpp:toughen_hand Item data merge entity @e[type=item_framerabbit,distance=..9,nbt={Age:0,InLove:0},limit=1] {InLove:600}
注意到一点,Items[{id:"minecraft:carrot"}]
只会选择容器内的最后一个拥有胡萝卜的栏位。如果 cpp:toughen_hand
的物品被消耗完,则追加到容器的 Items
时,仍然会先覆盖已有的同 Slot
项,然后由于 Count
变为 0
而清除该栏位。
§10.8 连锁
当玩家使用原版镐时,破坏所有相连的矿石。
我们将允许使用连锁的工具添加至cpp:mainhand/axe
等物品标签中,其中镐根据方块所需挖掘等级不同而划分为不同标签。相应可挖掘方块被记录在cpp:chain/axe
等方块标签中。
cpp:tick
execute as @a at @s at @e[type=item,nbt={Age:0s,PickupDelay:10s},distance=..6.5,limit=1] run function cpp:chain/init
cpp:chain/init
execute store result score @s cppValue run data get entity @s SelectedItem.tag.Enchantments[{id:"minecraft:unbreaking"}].lvl execute store result score #damage cppValue run data get entity @s SelectedItem.tag.Damage function cpp:get_durality execute if entity @s[nbt={SelectedItem:{tag:{Unbreakable:1b}}}] run scoreboard players set #max_durality cppValue 2147483647 scoreboard players set #mined cppValue 0 execute as @s[predicate=cpp:mainhand/axe] run function cpp:chain/axe/mark execute if score #damage cppValue < #max_durality cppValue as @s[predicate=cpp:mainhand/shovel] run function cpp:chain/shovel/mark execute if score #damage cppValue < #max_durality cppValue as @s[predicate=cpp:mainhand/hoe] run function cpp:chain/hoe/mark execute if score #damage cppValue < #max_durality cppValue as @s[predicate=cpp:mainhand/pickaxe3] run function cpp:chain/pickaxe3/mark execute if score #damage cppValue < #max_durality cppValue as @s[predicate=cpp:mainhand/pickaxe2] run function cpp:chain/pickaxe2/mark execute if score #damage cppValue < #max_durality cppValue as @s[predicate=cpp:mainhand/pickaxe1] run function cpp:chain/pickaxe1/mark execute if score #damage cppValue < #max_durality cppValue as @s[predicate=cpp:mainhand/pickaxe] run function cpp:chain/pickaxe/mark execute store result storage cpp:_ Damage int 1 run scoreboard players get #damage cppValue item entity @s[nbt=!{SelectedItem:{tag:{Unbreakable:1b}}}] weapon.mainhand modify cpp:chain_tool
cpp:chain/pickaxe1/mark
execute positioned ~1 ~ ~ if block ~ ~ ~ #cpp:chain/pickaxe1 run function cpp:chain/pickaxe1/damage execute if score #damage cppValue < #max_durality cppValue positioned ~-1 ~ ~ if block ~ ~ ~ #cpp:chain/pickaxe1 run function cpp:chain/pickaxe1/damage execute if score #damage cppValue < #max_durality cppValue positioned ~ ~1 ~ if block ~ ~ ~ #cpp:chain/pickaxe1 run function cpp:chain/pickaxe1/damage execute if score #damage cppValue < #max_durality cppValue positioned ~ ~-1 ~ if block ~ ~ ~ #cpp:chain/pickaxe1 run function cpp:chain/pickaxe1/damage execute if score #damage cppValue < #max_durality cppValue positioned ~ ~ ~1 if block ~ ~ ~ #cpp:chain/pickaxe1 run function cpp:chain/pickaxe1/damage execute if score #damage cppValue < #max_durality cppValue positioned ~ ~ ~-1 if block ~ ~ ~ #cpp:chain/pickaxe1 run function cpp:chain/pickaxe1/damage
cpp/tags/blocks/pickaxe1.json
{ "replace": false, "values": [ "#cpp:chain/pickaxe", "minecraft:copper_ore", "minecraft:iron_ore", "minecraft:lapis_ore", {"id":"#forge:ores/copper","required":false} ] }
cpp:chain/pickaxe1/damage
# 挖矿经验 execute as @s[predicate=!cpp:mainhand/silk_touch] run function cpp:chain/xp function cpp:chain/mine execute if predicate cpp:damage run scoreboard players add #damage cppValue 1 scoreboard players add #mined cppValue 1 execute if score #mined cppValue < $maxChainBlocks cppConfig if score #damage cppValue < #max_durality cppValue run function cpp:chain/pickaxe1/mark
cpp:chain/xp
execute if block ~ ~ ~ #coal_ores run xp add @s 1 execute if block ~ ~ ~ #redstone_ores run xp add @s 3 execute if block ~ ~ ~ nether_quartz_ore run xp add @s 3 execute if block ~ ~ ~ #lapis_ores run xp add @s 4 execute if block ~ ~ ~ #diamond_ores run xp add @s 5 execute if block ~ ~ ~ #emerald_ores run xp add @s 5
cpp:chain/mine
loot spawn ~ ~ ~ mine ~ ~ ~ mainhand tp @e[type=item,distance=..1] @s setblock ~ ~ ~ air
cpp/item_modifiers/chain_tool.json
{ "function": "minecraft:copy_nbt", "source": { "type": "storage", "source": "cpp:_" }, "ops": [ { "op": "replace", "source": "Damage", "target": "Damage" } ] }
§11 算法
§11.1 随机数
§11.1.1 不定长均匀分布
我们使用战利品的 set_attribute
函数可以便捷地得到 0
至 2048
之间的一个随机浮点数。由此可得到下述算法。
cpp/loot_tables/misc/random.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:tnt", "functions": [ { "function": "minecraft:set_attributes", "modifiers": [ { "slot": "mainhand", "name": "1", "attribute": "generic.luck", "operation": "addition", "amount": { "type": "minecraft:uniform", "min": 0, "max": 2048 } } ] } ] } ] } ] }
cpp:misc/random
setblock ~ 255 ~ chest loot insert ~ 255 ~ loot cpp:misc/random execute store result score #rand cppValue run data get block ~ 255 ~ Items[0].tag.AttributeModifiers[0].Amount 1048575 setblock ~ 255 ~ air scoreboard players operation #random_interval cppValue = #random_max cppValue scoreboard players operation #random_interval cppValue -= #random_min cppValue scoreboard players add #random_interval cppValue 1 scoreboard players operation #rand cppValue %= #random_interval cppValue scoreboard players operation #rand cppValue += #random_min cppValue
先设置 #random_min
和 #random_max
的 cppValue
为随机数的上下界,然后执行该函数,则 #rand
的 cppValue
即为该区间的一个随机数。
另一种做法更为简单,生成一个标记体并将其 UUID[0]
作为随机数来源即可。
cpp:random
summon marker ~ 0 ~ {Tags:["cpp_random"]} execute store result score #rand cppValue run data get entity @e[tag=cpp_random,limit=1,type=marker] UUID[0] kill @e[tag=cpp_random,type=marker] scoreboard players operation #random_interval cppValue = #random_max cppValue scoreboard players operation #random_interval cppValue -= #random_min cppValue scoreboard players add #random_interval cppValue 1 scoreboard players operation #rand cppValue %= #random_interval cppValue scoreboard players operation #rand cppValue += #random_min cppValue
对于1.17,我们可以直接将记分板分数作为战利品表的数值使用。
cpp/loot_tables/loot_tablesrandom
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:tnt", "functions": [ { "function": "minecraft:set_attributes", "modifiers": [ { "slot": "legs", "name": "1", "attribute": "generic.luck", "operation": "addition", "amount": { "type": "minecraft:uniform", "min": { "type": "minecraft:score", "target": { "type": "minecraft:fixed", "name": "#random_min" }, "score": "cppValue" }, "max": { "type": "minecraft:score", "target": { "type": "minecraft:fixed", "name": "#random_max" }, "score": "cppValue" } } } ] } ] } ] } ] }
§11.1.2 定长均匀分布
对于固定范围的随机数,我们可以直接使用命令random
得到。
execute store result score #rand value run random 1..100
§11.1.3 非均匀分布
对于只有若干个值的非均匀分布,我们可以使用战利品表来实现。
设 2, 3, 5, 7, 11 的概率分别为 0.2, 0.2, 0.36, 0.12, 0.12。
cpp/loot_tables/misc/rand_prime.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:stone", "weight": 20, "functions": [ { "function": "minecraft:set_count", "count": 2 } ] }, { "type": "minecraft:item", "name": "minecraft:stone", "weight": 20, "functions": [ { "function": "minecraft:set_count", "count": 3 } ] }, { "type": "minecraft:item", "name": "minecraft:stone", "weight": 36, "functions": [ { "function": "minecraft:set_count", "count": 5 } ] }, { "type": "minecraft:item", "name": "minecraft:stone", "weight": 12, "functions": [ { "function": "minecraft:set_count", "count": 7 } ] }, { "type": "minecraft:item", "name": "minecraft:stone", "weight": 12, "functions": [ { "function": "minecraft:set_count", "count": 11 } ] } ] } ] }
cpp:misc/rand_prime
setblock ~ 255 ~ chest loot insert ~ 255 ~ loot cpp:misc/rand_prime execute store result score #rand cppValue run data get block ~ 255 ~ Items[0].Count setblock ~ 255 ~ air
设 1, 2, 3 的概率分别为 0.35, 0.35, 0.3。
cpp/predicates/misc/rand_30.json
{ "condition": "random_chance", "chance": 0.3 }
cpp/predicates/misc/rand_50.json
{ "condition": "random_chance", "chance": 0.5 }
cpp:misc/rand_123
scoreboard players set #rand cppValue 1 execute if predicate cpp:misc/rand_50 run scoreboard players set #rand cppValue 2 execute if predicate cpp:misc/rand_30 run scoreboard players set #rand cppValue 3
§11.1.4 二项分布
cpp/loot_tables/misc/binomial.json
{ "pools": [ { "rolls": 1, "entries": [ { "type": "minecraft:item", "name": "minecraft:stone", "functions": [ { "function": "minecraft:set_attributes", "modifiers": [ { "slot": "mainhand", "name": "random_luck", "attribute": "generic.luck", "operation": "addition", "amount": { "type": "minecraft:binomial" "n": 100, "p": 0.3 } } ] } ] } ] } ] }
cpp:misc/binomial
setblock ~ 255 ~ chest loot insert ~ 255 ~ loot cpp:misc/binomial execute store result score #rand cppValue run data get block ~ 255 ~ Items[0].tag.AttributeModifiers[0].Amount setblock ~ 255 ~ air
§11.2 世界生成结构
在维度和维度类型中,我们介绍了如何通过数据包修改原有维度和添加新的维度。如果想要添加更多的生物群系和各式各样的结构,也可以通过自定义世界生成来实现。本节我们来介绍添加结构的几种方法。
§11.2.1 自定义地物
通过自定义地物,我们可以添加形状和生成类似树、花、矿石、紫晶洞等、而方块和尺寸有差异的小型结构。
我们在原版的一个生物群系文件基础上做修改来创建一个新的生物群系,并将它添加到我们自定义的维度中。
cpp/worldgen/biome/test.json
{ "effects": { "mood_sound": { "sound": "minecraft:ambient.cave", "tick_delay": 6000, "block_search_extent": 8, "offset": 2.0 }, "sky_color": 7972607, "fog_color": 12638463, "water_color": 4159204, "water_fog_color": 329011 }, "surface_builder": "minecraft:grass", "carvers": { "air": [ "minecraft:cave", "minecraft:canyon" ] }, "features": [ [], [ "minecraft:lake_water", "minecraft:lake_lava" ], [], [ "minecraft:monster_room" ], [], [], [ "minecraft:ore_dirt", "minecraft:ore_gravel", "minecraft:ore_granite", "minecraft:ore_diorite", "minecraft:ore_andesite", "minecraft:ore_coal", "minecraft:ore_iron", "minecraft:ore_gold", "minecraft:ore_redstone", "minecraft:ore_diamond", "minecraft:ore_lapis", "minecraft:disk_sand", "minecraft:disk_clay", "minecraft:disk_gravel" ], [], [ "cpp:small_bush", "minecraft:patch_grass_badlands", "minecraft:brown_mushroom_normal", "minecraft:red_mushroom_normal", "minecraft:patch_sugar_cane", "minecraft:patch_pumpkin", "minecraft:spring_water", "minecraft:spring_lava" ], [ "minecraft:freeze_top_layer" ] ], "starts": [ "minecraft:mineshaft", "minecraft:stronghold", "minecraft:ruined_portal" ], "spawners": { "monster": [ { "type": "minecraft:spider", "weight": 100, "minCount": 4, "maxCount": 4 }, { "type": "minecraft:zombie", "weight": 95, "minCount": 4, "maxCount": 4 }, { "type": "minecraft:zombie_villager", "weight": 5, "minCount": 1, "maxCount": 1 }, { "type": "minecraft:skeleton", "weight": 100, "minCount": 4, "maxCount": 4 }, { "type": "minecraft:creeper", "weight": 100, "minCount": 4, "maxCount": 4 }, { "type": "minecraft:slime", "weight": 100, "minCount": 4, "maxCount": 4 }, { "type": "minecraft:enderman", "weight": 10, "minCount": 1, "maxCount": 4 }, { "type": "minecraft:witch", "weight": 5, "minCount": 1, "maxCount": 1 } ], "creature": [ { "type": "minecraft:sheep", "weight": 12, "minCount": 4, "maxCount": 4 }, { "type": "minecraft:pig", "weight": 10, "minCount": 4, "maxCount": 4 }, { "type": "minecraft:chicken", "weight": 10, "minCount": 4, "maxCount": 4 }, { "type": "minecraft:cow", "weight": 8, "minCount": 4, "maxCount": 4 }, { "type": "minecraft:rabbit", "weight": 4, "minCount": 2, "maxCount": 3 } ], "ambient": [ { "type": "minecraft:bat", "weight": 10, "minCount": 8, "maxCount": 8 } ], "water_creature": [], "water_ambient": [], "misc": [] }, "spawn_costs": {}, "player_spawn_friendly": false, "precipitation": "rain", "temperature": 0.7, "downfall": 0.8, "category": "forest", "depth": 0.1, "scale": 0.4 }
然后创建一个地物文件,里面指定了一个灌木丛。将它添加到生物群系的特征列表中类型为 VEGETAL_DECORATION
的项。
cpp/worldgen/configured_feature/small_bush.json
{ "config": { "max_water_depth": 0, "ignore_vines": true, "heightmap": "OCEAN_FLOOR", "minimum_size": { "limit": 1, "lower_size": 0, "upper_size": 1, "type": "minecraft:two_layers_feature_size" }, "decorators": [], "trunk_provider": { "state": { "Properties": { "axis": "y" }, "Name": "minecraft:oak_log" }, "type": "minecraft:simple_state_provider" }, "leaves_provider": { "state": { "Properties": { "persistent": "false", "distance": "7" }, "Name": "minecraft:oak_leaves" }, "type": "minecraft:simple_state_provider" }, "foliage_placer": { "radius": 2, "offset": 0, "height": 3, "type": "minecraft:pine_foliage_placer" }, "trunk_placer": { "base_height": 1, "height_rand_a": 0, "height_rand_b": 0, "type": "minecraft:straight_trunk_placer" } }, "type": "minecraft:tree" }
注意由于 small_bush
的 "type":"minecraft:tree"
,因此它会生成在原有的树的位置。想要二者均可生成,请使用随机选择的地物。
§11.2.2 任意纯方块结构
利用结构地物和模板池,我们可以实现自定义的结构的自然生成。可自定义的结构的结构地物可选的类型有堡垒遗迹、掠夺者前哨站或村庄。注意这样生成的会被视为相应结构(可用于完成进度和 locate
命令)。掠夺者前哨站附近会生成灾厄村民,堡垒遗迹会触发原版进度“光辉岁月”,因此村庄是最合适的,堡垒遗迹次之。注意村庄类型的结构从地表的一层方块起生成,因此为了避免结构陷下去应当将结构的最底一层设置为空,而堡垒遗迹类型的结构生成在地下而非地表。通过使用拼图方块、或直接修改结构文件将方块放置在结构外,无论是村庄还是堡垒遗迹我们都可以使结构任意部分生成在地表、任意部分生成在地下。
添加结构文件 cpp/structures/tp_overworld.nbt
,然后利用类型的结构地物来自定义模板池。
cpp/worldgen/configured_structure_feature/totem_pillar.json
{ "config": { "start_pool": "cpp:totem_pillar", "size": 1 }, "type": "minecraft:pillager_outpost" }
cpp/worldgen/template_pool/totem_pillar.json
{ "name": "cpp:tp_overworld", "fallback": "minecraft:empty", "elements": [ { "weight": 1, "element": { "location": "cpp:tp_overworld", "processors": { "processors": [] }, "projection": "rigid", "element_type": "minecraft:legacy_single_pool_element" } } ] }
注意这样生成的结构几率非常低,因为原版村庄几率很低,我们可以修改该维度中村庄的生成几率。为了避免在原版可生成村庄的生物群系大量生成村庄,我们可以将其结构设为村庄和自定义结构均有权重的模板池。
§11.2.3 含实体结构
上一节生成的结构中带上实体标记,然后在函数中检测该标记并执行任意命令并清除标记。
§11.2.4 区块标记法
世界生成时,随机生成结构。判断玩家东南32×32范围内是否有标记,如无,添加相应标记在32整数倍坐标处,然后生成战利品表、分散,通过战利品表掉落物来生成结构。也可以使用方块来标记,例如普通生存下y=0使用屏障替换基岩或超平坦生存y=255处使用air替换void_air来实现,这在某些对实体加载有修改的服务端会很有效。
cpp:tick
execute as @a at @s unless block ~ 0 ~ bedrock run function cpp:generate/check
cpp:generate/check
execute positioned ~-64 -1 ~-64 unless entity @e[type=marker,tag=cpp_chunk,dx=32,dy=2,dz=32] run function cpp:generate/mark execute positioned ~-64 -1 ~-32 unless entity @e[type=marker,tag=cpp_chunk,dx=32,dy=2,dz=32] run function cpp:generate/mark execute positioned ~-64 -1 ~ unless entity @e[type=marker,tag=cpp_chunk,dx=32,dy=2,dz=32] run function cpp:generate/mark execute positioned ~-32 -1 ~-64 unless entity @e[type=marker,tag=cpp_chunk,dx=32,dy=2,dz=32] run function cpp:generate/mark execute positioned ~-32 -1 ~-32 unless entity @e[type=marker,tag=cpp_chunk,dx=32,dy=2,dz=32] run function cpp:generate/mark execute positioned ~-32 -1 ~ unless entity @e[type=marker,tag=cpp_chunk,dx=32,dy=2,dz=32] run function cpp:generate/mark execute positioned ~ -1 ~-64 unless entity @e[type=marker,tag=cpp_chunk,dx=32,dy=2,dz=32] run function cpp:generate/mark execute positioned ~ -1 ~-32 unless entity @e[type=marker,tag=cpp_chunk,dx=32,dy=2,dz=32] run function cpp:generate/mark execute positioned ~ -1 ~ unless entity @e[type=marker,tag=cpp_chunk,dx=32,dy=2,dz=32] run function cpp:generate/mark
cpp:generate/mark
summon marker ~32 0 ~32 {Tags:["cpp_aec_marker","cpp_chunk","cpp_temp"]} execute as @e[type=marker,tag=cpp_temp] run function cpp:generate/aec loot spawn ~ ~ ~ loot cpp:misc/generate execute store result score #t cppValue run spreadplayers ~ ~ 0 15 false @e[type=item,nbt={Item:{tag:{cpp_generate_marker:1b}}}] execute as @e[type=item,nbt={Item:{tag:{cpp_generate_marker:1b}}}] at @s if block ~ ~-1 ~ #cpp:replacable run tp ~ ~-1 ~ execute as @e[type=item,nbt={Item:{tag:{cpp_generate_marker:1b}}}] at @s if block ~ ~-1 ~ #cpp:replacable run tp ~ ~-1 ~ execute as @e[type=item,nbt={Item:{tag:{cpp_generate_marker:1b}}}] at @s run function cpp:generate/build
cpp:generate/aec
execute store result entity @s Pos[0] double 32 run data get entity @s Pos[0] 0.03125 execute store result entity @s Pos[2] double 32 run data get entity @s Pos[2] 0.03125 tag @s remove cpp_temp
cpp:generate/build
# 附魔室 execute as @s[nbt={Item:{tag:{cpp_generate_type:"enchanting_room"}}}] if score #t cppValue matches 1.. run function cpp:generate/structures/enchanting_room # 图腾柱 execute as @s[nbt={Item:{tag:{cpp_generate_type:"tp_overworld"}}}] run function cpp:generate/structures/tp_overworld execute as @s[nbt={Item:{tag:{cpp_generate_type:"tp_the_nether"}}}] run function cpp:generate/structures/tp_the_nether execute as @s[nbt={Item:{tag:{cpp_generate_type:"tp_flower"}}}] run function cpp:generate/structures/tp_flower # 树 execute as @s[nbt={Item:{tag:{cpp_generate_type:"fruit_tree"}}}] if block ~ ~-1 ~ #cpp:sapling_plantable_on align xyz positioned ~0.5 ~ ~0.5 run function cpp:plants/trees/fruit0 execute as @s[nbt={Item:{tag:{cpp_generate_type:"ore_tree"}}}] if block ~ ~-1 ~ #cpp:sapling_plantable_on align xyz positioned ~0.5 ~ ~0.5 run function cpp:plants/trees/ore0 execute as @s[nbt={Item:{tag:{cpp_generate_type:"wool_tree"}}}] if block ~ ~-1 ~ #cpp:sapling_plantable_on align xyz positioned ~0.5 ~ ~0.5 run function cpp:plants/trees/wool0 execute as @s[nbt={Item:{tag:{cpp_generate_type:"sakura_tree"}}}] if block ~ ~-1 ~ #cpp:sapling_plantable_on align xyz positioned ~0.5 ~ ~0.5 run function cpp:plants/trees/sakura0 # 农作物 execute as @s[nbt={Item:{tag:{cpp_generate_type:"crops"}}}] if block ~ ~-1 ~ minecraft:grass_block run function cpp:generate/structures/crops # 花草 execute as @s[nbt={Item:{tag:{cpp_generate_type:"modcrops"}}}] run function cpp:generate/structures/modcrops kill @s
cpp:generate/structures/enchanting_room
setblock ~ ~20 ~ structure_block{posX:-1,posY:-20,posZ:-1,name:"cpp:build/enchanting_room",mode:"LOAD"} setblock ~ ~21 ~ redstone_block fill ~ ~20 ~ ~ ~21 ~ air loot insert ~1 ~5 ~ loot cpp:chests/enchanting_room loot insert ~2 ~5 ~ loot cpp:chests/enchanting_room loot insert ~3 ~5 ~ loot cpp:chests/enchanting_room loot insert ~ ~5 ~1 loot cpp:chests/enchanting_room loot insert ~ ~5 ~2 loot cpp:chests/enchanting_room loot insert ~ ~5 ~3 loot cpp:chests/enchanting_room loot insert ~1 ~5 ~4 loot cpp:chests/enchanting_room loot insert ~2 ~5 ~4 loot cpp:chests/enchanting_room loot insert ~3 ~5 ~4 loot cpp:chests/enchanting_room loot insert ~4 ~5 ~1 loot cpp:chests/enchanting_room loot insert ~4 ~5 ~2 loot cpp:chests/enchanting_room loot insert ~4 ~5 ~3 loot cpp:chests/enchanting_room
cpp/loot_tables/misc/generate.json
{ "pools": [ { "rolls": 1, "conditions":[ { "condition": "minecraft:random_chance", "chance": 0.006 }, { "condition": "minecraft:location_check", "predicate": { "dimension": "minecraft:overworld" } } ], "entries": [ { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cpp_generate_marker:1b,cpp_clear:1b,CustomModelData:12970000,cpp_generate_type:'enchanting_room'}" } ] } ] }, { "rolls": 1, "conditions":[ { "condition": "minecraft:random_chance", "chance": 0.006 }, { "condition": "minecraft:location_check", "predicate": { "dimension": "minecraft:overworld" } } ], "entries": [ { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cpp_generate_marker:1b,cpp_clear:1b,CustomModelData:12970000,cpp_generate_type:'tp_overworld'}" } ] } ] }, { "rolls": 1, "conditions":[ { "condition": "minecraft:random_chance", "chance": 0.006 }, { "condition": "minecraft:location_check", "predicate": { "dimension": "minecraft:the_nether" } } ], "entries": [ { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cpp_generate_marker:1b,cpp_clear:1b,CustomModelData:12970000,cpp_generate_type:'tp_the_nether'}" } ] } ] }, { "rolls": 1, "conditions":[ { "condition": "minecraft:random_chance", "chance": 0.006 }, { "condition": "minecraft:location_check", "predicate": { "dimension": "cpp:flower" } } ], "entries": [ { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cpp_generate_marker:1b,cpp_clear:1b,CustomModelData:12970000,cpp_generate_type:'tp_flower'}" } ] } ] }, { "rolls": 1, "conditions":[ { "condition": "minecraft:random_chance", "chance": 0.09 }, { "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:location_check", "predicate": { "dimension": "minecraft:overworld" } }, { "condition": "minecraft:location_check", "predicate": { "dimension": "cpp:flower" } } ] } ], "entries": [ { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cpp_generate_marker:1b,cpp_clear:1b,CustomModelData:12970000,cpp_generate_type:'fruit_tree'}" } ] }, { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cpp_generate_marker:1b,cpp_clear:1b,CustomModelData:12970000,cpp_generate_type:'ore_tree'}" } ] }, { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cpp_generate_marker:1b,cpp_clear:1b,CustomModelData:12970000,cpp_generate_type:'wool_tree'}" } ] }, { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cpp_generate_marker:1b,cpp_clear:1b,CustomModelData:12970000,cpp_generate_type:'sakura_tree'}" } ] } ] }, { "rolls": 1, "conditions":[ { "condition": "minecraft:random_chance", "chance": 0.15 }, { "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:location_check", "predicate": { "biome": "minecraft:desert" } }, { "condition": "minecraft:location_check", "predicate": { "biome": "minecraft:desert_hills" } }, { "condition": "minecraft:location_check", "predicate": { "biome": "minecraft:desert_lakes" } }, { "condition": "minecraft:location_check", "predicate": { "biome": "minecraft:badlands" } }, { "condition": "minecraft:location_check", "predicate": { "biome": "minecraft:badlands_plateau" } }, { "condition": "minecraft:location_check", "predicate": { "biome": "minecraft:modified_badlands_plateau" } }, { "condition": "minecraft:location_check", "predicate": { "biome": "minecraft:wooded_badlands_plateau" } }, { "condition": "minecraft:location_check", "predicate": { "biome": "minecraft:modified_wooded_badlands_plateau" } }, { "condition": "minecraft:location_check", "predicate": { "biome": "minecraft:eroded_badlands" } } ] } ], "entries": [ { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cpp_generate_marker:1b,cpp_clear:1b,CustomModelData:12970000,cpp_generate_type:'dead_coral_fan'}" }, { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 1, "max": 5 } } ] } ] }, { "rolls": 1, "conditions":[ { "condition": "minecraft:random_chance", "chance": 0.1 }, { "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:location_check", "predicate": { "dimension": "minecraft:overworld" } }, { "condition": "minecraft:location_check", "predicate": { "dimension": "cpp:flower" } } ] } ], "entries": [ { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cpp_generate_marker:1b,cpp_clear:1b,CustomModelData:12970000,cpp_generate_type:'small_bush'}" } ] } ] }, { "rolls": 1, "conditions":[ { "condition": "minecraft:random_chance", "chance": 0.08 }, { "condition": "minecraft:alternative", "terms": [ { "condition": "minecraft:location_check", "predicate": { "biome": "minecraft:taiga" } }, { "condition": "minecraft:location_check", "predicate": { "biome": "minecraft:taiga_hills" } }, { "condition": "minecraft:location_check", "predicate": { "biome": "minecraft:taiga_mountains" } } ] } ], "entries": [ { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cpp_generate_marker:1b,cpp_clear:1b,CustomModelData:12970000,cpp_generate_type:'dead_spruce'}" }, { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 1, "max": 4 } } ] } ] }, { "rolls": 1, "conditions":[ { "condition": "minecraft:random_chance", "chance": 0.04 }, { "condition": "minecraft:location_check", "predicate": { "dimension": "minecraft:overworld" } } ], "entries": [ { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cpp_generate_marker:1b,cpp_clear:1b,CustomModelData:12970000,cpp_generate_type:'crops'}" }, { "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 1, "max": 4 } } ] } ] }, { "rolls": 1, "conditions":[ { "condition": "minecraft:random_chance", "chance": 0.5 }, { "condition": "minecraft:location_check", "predicate": { "dimension": "cpp:flower" } } ], "entries": [ { "type": "minecraft:item", "name": "minecraft:firework_star", "functions": [ { "function": "minecraft:set_nbt", "tag": "{cpp_generate_marker:1b,cpp_clear:1b,CustomModelData:12970000,cpp_generate_type:'modcrops'}" } ] } ] } ] }
注意在扩散标记物时,当下方为草等方块时,需要将其向下移动以避免结构悬空生成。
我们可以将所有维度的结构标记物均放置在同一战利品表内。
另一种生成为全局替换,例如将主世界的地牢刷怪笼一部分替换为苦力怕刷怪笼,那么在每次检测时,使用 fill
命令强制替换即可。但是该命令开销很大,会导致较大的卡顿。
§11.3 维度探测
由于我们不能预先知道会有哪些维度,因此我们需要在每个维度放置标记来存储该维度的位置,然后记录维度ID。这些操作均需要玩家来主动探测,因此我们使用维度旅行进度来探测新维度。
我们将维度标记存储在区块 (1000000,1000000)
并常加载该区块。
cpp:load
advancement grant @a only cpp:misc/new_dim
cpp/advancements/new_dim.json
{ "criteria": { "entered_dim": { "trigger": "minecraft:changed_dimension" } }, "rewards": { "function": "cpp:adv/new_dim" } }
cpp:adv/new_dim
advancement revoke @s only cpp:new_dim execute positioned 16000000 0 16000000 unless entity @e[type=marker,tag=cpp_dim_marker,distance=..1] run function cpp:init/dim_marker
cpp:init/dim_marker
forceload add ~ ~ scoreboard players add #dim_number cppValue 1 summon marker ~ ~ ~ {Tags:["cpp_dim_marker"]} data modify storage cpp:_ dim append from entity @s Dimension
cpp:tick
execute as @e[type=marker,tag=cpp_dim_marker] unless score @s cppValue matches -2147483648..2147483647 run scoreboard players operation @s cppValue = #dim_number cppValue
使用时,我们递归探测玩家所在的维度在列表 cpp:_ dim
中的序号。
cpp:dim_get
scoreboard players set #t cppValue 0 data modify storage cpp:_ dim1 set from storage cpp:_ dim execute as @p run function cpp:dim_loop execute as @e[type=marker,tag=cpp_dim_marker] if score @s cppValue = #t cppValue at @s run spreadplayers 0 0 1 10 false @p
cpp:dim_loop
scoreboard players add #t cppValue 1 execute store result score #s cppValue run data modify storage cpp:_ dim1[0] set from entity @s Dimension data remove storage cpp:_ dim1[0] execute if score #s cppValue matches 1 if data storage cpp:_ dim1[] run function cpp:dim_loop