最后更新于 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 命令基础

本文不会介绍命令的基础知识,相关内容请读者通过如下链接自行了解。

若你已对 1.18 之前版本的命令较为熟悉,可在Java版版本记录中查看各版本间差异。

§1.2 工具准备

§1.2.1 文本编辑器

原版模组涉及的文本文件,包括文本文档(.txt)、函数文件(.mcfunction)、JSON文件(.json, .mcmeta)、顶点着色器(.vsh)和片段着色器(.fsh),均需使用 UTF-8 编码格式。注意不要误选了 UTF-8 with BOM 编码格式。文本编辑器多如瀚海,读者可自行选择一种。我仅列出本人常用的两个文本编辑器。

编辑器右下角可以看到 LFCRLF,分别表示两种换行符,二者均可正常使用,建议使用 LF

为便于查看文件后缀,请将文件->文件夹选项->查看->隐藏已知文件类型的扩展名去掉勾选。我们可以在编辑器中新建文本文件,或者在文件夹中右键->新建->文本文档,除 txt 以外的文本文件可通过修改文件后缀得到。

§1.2.2 压缩软件

数据包和资源包均可以为文件夹或 zip 文件,zip 文件需要压缩软件来打开和制作。游戏本体和模组本体的 jar 文件也需要使用压缩软件来打开。常见的压缩软件有:

§1.2.3 NBT 编辑器

我们可以使用 NBTStudio 来打开 dat 文件和其它 NBT 格式文件。

§1.2.4 绘图工具

我们可以使用 Adobe Photoshop 或其它绘图工具来绘制纹理。Windows 自带的画图由于无法生成透明背景,因此不建议使用。

§1.3 游戏文件夹

本节中我们将对游戏文件夹的结构做简单的介绍,我们只介绍原版模组开发中涉及的内容。.minecraft 文件夹是 Minecraft 创建的并用于游戏运行的文件夹,它包含了游戏的所有内容。在第一次启动启动器时,会自动创建 .minecraft 文件夹。参考 .minecraft

.minecraft 文件夹通常位于你的启动器目录下。如果启动器中设置为各版本独立,则位于 versions/版本号下。另一种方式是在游戏内点击选项->资源包->打开压缩包文件夹并返回上级目录,或者点击单人游戏->选中世界->编辑->打开世界文件夹并返回上上级目录。

游戏本体位于 versions/版本号/版本号.jar。该文件包含了对应版本的游戏资源和数据文件,使用压缩软件打开后,可以看到

游戏本体文件结构
图1.1 游戏本体文件结构

资源包文件夹位于 resourcepacks,其下方子文件夹或 zip 文件即为一个资源包,具体结构见资源包。服务器下载的资源包位于 server-resource-packs/服务器,可使用压缩软件打开。

资源文件 部分资源文件不被包含在原版资源包内,而是位于资源文件夹下,这主要包括各种语言文本和音效文件。资源文件索引位于 assets/indexes/版本号.json,打开后通过键值可知相应资源的 hash 值,对应的资源位于 assets/objects/hash前2位/hash。例如打开 assets/indexes/1.17.json,找到键 minecraft/lang/zh_cn.jsonhash8fb4f6725d8317a37e7f823ff424e66a46b9ef75,因此简体中文的语言文本位于文件夹 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 即可使用。

§1.4 JSON文件

参考JSON。数据包的进度、战利品表、战利品表谓词、物品修饰器、配方、标签、维度、维度类型、自定义世界生成,资源包的语言文件、模型、音效、字体、credits.json 等文件均为JSON文件。.mcmeta 文件也是JSON文件,因此格式也是相同的。

JSON文件中用于分割的空格、制表符(Tab)、回车和换行符都是可去的,它们仅用于提高可读性。编写时,使用空格或制表符缩进,以便于查看括号匹配和层次。JSON文件通常包含用于封装文件数据的一对大括号{},即它是一个JSON对象,但战利品表谓词物品修饰器文件的根数据类型还可以为JSON数组。它包含类似 "abc": "def" 这样的 "键": 值 对,一般使用单引号也可以。同一个文件中如果允许有相同的键,则后者会覆盖前者。

JSON数据类型有下述几种。Wiki 上有关页面使用了 NBT 的数据类型标注,但其实并不适用于JSON文件,我们应当将其视为相应内容的可取值范围。

布尔型,值为 truefalse

{
  "is_on_fire": false,
  "is_baby": true,
  "noise_caves_enabled": false
}

数值,值为任何数字。22.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 命令的参数以给予玩家特定战利品表物品等。

战利品表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"
        }
      ]
    }
  ]
}

该例子战利品表表示牛死亡时

战利品表除上述用途外,还可用于生成随机数,见随机数;指定自定义机器的配方,见配方处理等诸多场景。

原版战利品表用于决定方块、实体的默认掉落物以及游戏的一些默认内容。如果需要修改原版的战利品表,只需要在数据包中添加相同路径的相应名称文件并修改之即可。原版的战利品表被划分为实体掉落物 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结构为:

通过直接编辑结构文件,我们可以绕过使用结构方块保存的一些限制。在游戏内保存好大致的结构后,使用NBT编辑器打开:

这些技巧的使用请参考任意纯方块结构

如果结构用于结构地物中,当结构只有部分在已加载区块中时,系统会根据结构的size来确定还有哪些部分没有被加载。blockspos不在长方体区域内,这可能会导致生成的结构不完整。

§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 命名空间中的标签

本文中我们有时会不加注明地使用命名空间: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:waterminecraft: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对象或一个非负整数。

均匀分布的整数
{会从 basebase+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) 使用结构文件来生成结构,其中结构包含的拼图方块会工作以生成更复杂的结构。拼图方块是一个允许游戏使用较小模板生成结构的拼接方块:

拼图方块目前仅用于生成掠夺者前哨站、村庄和堡垒遗迹类型的结构地物。这些结构的生成开始于包含拼图方块的模板。每个拼图方块都使用下列流程来生成另一个模板:

初始模板和附加模板里的拼图方块都会重复上述流程,直到没有剩余的拼图方块或者结构足够大为止。使用结构方块加载结构不能复现上述过程。

模板池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 已经定义了很多常见模型,我们可以直接调用而无需自己编写。对于自定义的多个类似的模型,我们也可以写好一个模板后继承之。常见的默认模型有
  • minecraft:item/generated 普通手持物,指定了手持物在不同位置的显示效果。
  • minecraft:item/handheld 手持工具物,与上一条有不同的旋转效果。
  • minecraft:block/block 方块,指定了方块在不同位置的显示效果。
  • minecraft:block/cube 完整方块,指定了一个1×1×1大小的方块模型。
光照 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,最详细的官方我的世界百科

方向示意图
图3.1 方向示意图

如果我们循着模型 minecraft:item/handheld 往上,最终会得到模型 builtin/generated。这种模型是内建的,不能通过资源包来修改。

由于 scale 至多为4,而 from to 至多为-1632,因此模型最多可以放大至12倍。想要更大的模型可以通过分段来旋转拼接而成,参阅

模型在不同位置仅有放缩旋转平移的差别,想要实现视觉上的明显不同,可参阅【1.14】物品头部/背包/手持显示不同纹理/模型

方块状态我们一般不会用到,而且语法比较简单,可直接参阅 wiki,这里不做赘述。

§3.6 纹理

命名空间ID命名空间:路径/文件名
文件路径命名空间/textures/路径/文件名.json

纹理 (Texture) 的绘制需要用到诸如 Photoshop 之类的画图软件,具体请参考纹理版或网络上的相关教程。

当模型调用的纹理不存在时,会显示紫黑色的纹理。如果模型是正确的,你仍然可以看出正确的模型形状。

纹理可以是动态的。动态纹理需要高为宽的整数倍,除非指定了 widthheight。当纹理的高度为宽度的 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 该字符向上移动的像素高度,这个值会使得字符的显示结果出现垂直偏移
    }
  ]
}

如果 typespace,则表示该字符表示空格,此时格式为

  {
    "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 下的下述文件来修改:

在后处理着色器渲染管线中,除了玩家自定义的缓冲层,系统还预设了

当屏幕渲染完成时,若玩家处于上述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 ] }
  ]
}

这里全局变量的 countvalues 数组的长度,当为浮点数 (float) 时,实际为 count 长度的浮点数数组 float, vec2, vec3, vec4int 类似,为 int, ivec3, ivec3, ivec4;矩阵为 matrix4x4, matrix3x3, matrix2x2

顶点着色器和片段着色器程序可参考原版着色器的 vsh, fsh 文件。着色器中的数据类型包括标量 bool, int, uint, float, double,向量 bven, iven, uven, ven, dvenn=2,3,4 为向量长度;矩阵 matn, matnxm 为列优先的矩阵,n,m=2,3,4。构建变量时,可以使用其它变量作为其部分。全局变量可以被声明为

着色器程序中的 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 规划

当我们有了一定的命令基础和数据包与资源包的相关知识之后,我们可以开始考虑做一个模组了。模组的目的是在原版的基础上做出一定的修改,模组的核心是内容,命令和技巧都只是为实现这一目的的手段。通常的设计路线如下图所示:

设计路线
图4.1 设计路线

平衡性可以从合成难度和触发条件等方面来调整。

原版模组不比基于 Forge/Liteloader 等 API 的模组,在实现效率上一般会有所欠缺,因此我们在制作和测试过程中,应当优先保证流畅度,再考虑内容的充实性。

模组的整个生命周期中,所有内容都应当有完整的文档记录,以便于随时查看和修改。

为你的模组设定合理的版本管理方式,例如使用 Git 托管,有利于保存模组的所有历史版本。这可以减少因误操作导致的损失。

§4.1 名称设计

模组的所有内容都应当被合理地命名,尽量采用简洁且有意义的命名,同时便于他人处理数据包冲突。这些内容包括数据包下所有的文件和文件夹名以及记分板、标签、组的名称等。建议各种名称均使用大驼峰、小驼峰或下划线记法。如果模组包含多个模块,可以在不同数据包下使用相同的命名空间、不同的文件夹,来分门别类。

类型示例说明
数据包名称 craftingpp
更多的合成v1.10
CraftingPlusPlus
建议使用模组全名,空格和特殊字符使用下划线代替或忽略,也可使用中文名称。
资源包名称 craftingpp_resourcepack
更多的合成v1.10资源包
CraftingPlusPlus_Resources
建议使用数据包名称+资源包等。
命名空间 craftingpp
ex
fisma
mek
过短的命名空间容易冲突,可使用模组名称的全程或缩写来表示。命名空间必须由小写或下划线组成。建议整个模组使用一个命名空间,或若干相同前缀的命名空间。
物品名称 红色火之力
Red Force of Fire
cpp:red_force_of_fire
建议使用物品英文名称对应的小写+下划线写法作为物品id,具体设计见下一节。
记分板和组 cppValue
exCraft
macTicks
cpp_health
建议使用命名空间为前缀的驼峰记法,因为记分板名称长度不可超过16。长度允许的话,使用下划线记法也可以。
实体标签 cpp_entities_checker
cpp_generate_dead
cpp_machines_marker
exDarkAnimals
建议使用命名空间为前缀。
自定义NBT键 exGenratorMarker
exLeavesDecayMarker
exSkyislandDrop
建议使用命名空间为前缀。
记分板假名 #temp
$machineWorkPeriod
$mod_leaves_speed
临时变量建议使用#开头,配置参数建议使用$开头,因为#开头的计分板不会在记分板列表中显示而$开头的可以。因记分板已使用前缀,此处可以不使用前缀。
tick函数 _main.mcfunction
tick.mcfunction
loop.mcfunction
即函数标签 minecraft:tick 下的函数,见 §2.10.1 预设标签
load函数 load.mcfunction
_init.mcfunction
root.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.mcmetapack.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.png1×1 的透明文件,assets/cpp/textures/font/chain.png182×182 的文件,右上角为需要显示的状态效果图案。我们使用自定义的字体以避免占据原版字符。

空格纹理
图5.1 空格纹理
状态效果纹理
图5.2 状态效果纹理
cpp:chain/showtime
title @s actionbar {"font":"cpp:default","text":"12"}
自定义状态效果
图5.3 自定义状态效果

状态效果即将结束时的闪烁效果也可以类似实现,这里不再赘述。

§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.4 花环物品纹理

在头部时,我们将其在背包的显示旋转使得只能看到方块底部,而底部纹理为对应的普通物品纹理。其它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" }
      }
    }
  ]
}
花环方块纹理
图5.5 花环方块纹理
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 画

我们使用指定内含物的物品展示框来实现自定义的画。例如:放置经典画作后显示一幅画,右键可以切换。

我们为烟火之星 CustomModelData12974001-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的模型后,在方块位置生成用于显示的实体。参考

实体一般使用头戴相应物品的盔甲架或者存储有相应物品的物品展示框或荧光物品展示框。相比于盔甲架,物品展示框的优点是:

缺点是:

在这些缺点不影响时,我们更建议使用物品展示框。本节中我们以盔甲架为例。首先我们根据 §5 物品设计来将方块对应的物品设计好,然后我们需要定位到方块所在位置。我们使用进度判断玩家放置了某物品,然后剥夺进度,获取该方块位置。由于玩家可以紧靠其它不完整方块来放置,这会导致该方块位置不一定在玩家视线上。一种做法是记录所有方块的碰撞箱,然后判断玩家视线与何方块的何面相交,从而得到方块位置,见超精准的射线追踪碰撞检测器。另一种在放弃精准度的前提下,我们可以将视线向相邻6个位置移动一格检测。为了简便,我们采用第二种做法。

最后,我们在方块位置放置盔甲架。我们需要判断玩家是主手还是副手放置的,并将相应的物品信息复制到盔甲架头部。不直接使用 loot 命令复制到盔甲架头部是为了在之后的破坏事件中保留原物品的额外信息,例如玩家重命名的名称等。反之,若我们不希望保留这些额外信息,则可以直接使用 loot 命令输入到盔甲架头部。

对于特殊的方块物品,我们需要对盔甲架进行预处理或调整,例如

§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

§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,即木桶所有栏位的大小。当该物品放置在木桶的第一格栏位时,经过上述放缩和平移,该物品的纹理正好和木桶的物品栏对齐。

机器纹理
图7.2 cpp/textures/machine/gui/all_in_one_machine.png

若想要绘制各边缘超过这个距离限制的,可以重新调整 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
  }
}
能源条
图8.3 assets/cpp/textures/machine/xp/xp.png

第二个是能源标尺框架,位于中间层。

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"
    }
}
能源标尺框架
图7.4 assets/cpp/models/machine/xp/frame.png

我们将能源条划分为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均是采用物品纹理绘制的。

机器GUI示例1
图7.5 机器GUI示例1
机器GUI示例2
图7.6 机器GUI示例2

§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:damageItem 中,然后使用断言判断是否减少耐久。最后判断其是否已经耐久耗尽,若耗尽则清除之。

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 函数可以便捷地得到 02048 之间的一个随机浮点数。由此可得到下述算法。

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_maxcppValue 为随机数的上下界,然后执行该函数,则 #randcppValue 即为该区间的一个随机数。

另一种做法更为简单,生成一个标记体并将其 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

§11.4 绘制图案

TBD

§11.5 循环和递归

TBD

§11.6 字符操作

TBD

§11.7 位运算与种子

TBD