本篇教程由作者设定使用 CC BY-NC 协议。
(之前的教程写的太随性了,一点也不专业,现在终于忙完了毕设,重新一下之前的教程并给出更多实例)
(2024.06.25 删除之前的所有内容,重新施工开始,预计不知道什么时候完工)
这作者绝对是写论文写傻了
1 综述
1.1 研究内容
本文旨在将作者自身的编写实践与wiki内容进行结合,撰写一篇相比较于wiki更通俗且更完善的教程。文章将尽可能准确描述本人在编写过程中遇到的问题和解决方案,并提供尽可能多的图例进行辅助描述。
1.2 注意事项
本文的重点将放在以下几个方面:
如何自定义新的食物组
如何完全重新分配食物的食物组及其注意事项
如何自定义增益/减益
对于wiki中给出的修改原版食物组分配的方法,本文仅对wiki上的内容进行简单转述,因为本文作者直接手动重写了所有分配文件。因此如果真出现问题了本文作者也真不知道该怎么办。对于英文的翻译问题,我尽量使用自己的英语功底与AI相配合的方式翻译,不一定能做到完全准确的表达出相应的含义。
2 基础知识与初步认识
2.1 食物组,食物与增益
2.1.1 食物组
食物组(Food Groups)是wiki中相应词汇的直接翻译。考虑到实际应用中其可以灵活变通,因此本文直接翻译而不是意译。毕竟这玩意叫做“营养成分”或者其他什么只要吃食物就能补充的玩意都行。在具体的游戏中,其所指代的为一个唯一的类别名。该名称用于渲染GUI,构建diet命令行,创建“diet:类型名”的tags指明食物所属的类别并给出信息提示,以及在增益的触发条件中指明需要那些类别的数据。下图展示了本人创建的食物组类别在GUI,命令行以及食物的介绍中的情况。其中命令行中展示的类型名为实际编辑时的类型名,食物的介绍中展示了tag和翻译过后的提示,GUI中展示了翻译过的食物组名称。
注意到附魔金苹果中明明没有“diet:类型名”的tag,但是其仍然被传递了相关参数,那是因为diet本身可以做到自动推断,及给出原材料的类别后就能自动推断合成物的类别,具体推断方法将在后续介绍。
2.1.2 食物
食物指能提供饱食度的物品,包括直接右键吃的物品与放下后点击食用的物品,对应MC原版中的两种吃食物的方法。注意有些mod可能会添加类似于饱食度设定的功能(比如 thirst was taken 的口渴度/水分值),这些物品即使绑定了diet定义的食物组也不能添加相应的类别的值。因为该值的计算依赖的是食物的恢复值与饱和度,这玩意直接钉死在代码里面了,因此根本没法修改。
食物能增加某个类别多少值取决于食物的恢复值,饱和度,食物组的gain_multiplier,serverconfig中的gainPenaltyPerGroup以及食物组的数量(存疑)。根据代码中的描述,其计算步骤大致为:
计算quality=(healing+healing*saturation)/groups.size();healing为恢复值,saturation为饱和度,groups.size()可能为食物组的数量(存疑)
计算初步的gain=(quality*0.25f)/(quality+15.0f)
对gain进行初步修正gain*=Math.pow(1.0f-gainPenaltyPerGroup/100f,groups.size()-1);其中Math.pow(a,b)等价于计算a^b(a的b次方)。这里是先根据惩罚项计算出百分比,随后次方,由于底数属于0-1,次方越高值越接近0。
获取所有的食物组信息,value=gain*gain_multiplier;gain_multiplier在食物组中定义,是个无限制的float,因此这里可放大可缩小
计算出最终的value=Math.max(0.005f,Math.round(value*200)/200.0f);即相当于存在一个保底0.5%的增加值,Math.round是四舍五入。
需要说明的是,一个食物可以属于多个不同的食物组,一个食物组内部可以有多个不同的食物,两者是多对多的关系。同时diet允许进行自动推断,及根据原材料的diet tag自动推断合成物的diet tag,并且生成合成物的相关信息和数据。该自动推断支持合成树推断和类似于蛋糕的右键食用食物的推断(即能够将块状食物的类型自动映射并计算)。如2.1.1中的附魔金苹果的奶类就来自于之前设定的potion的类别,而水果则来自于苹果的类别,金属则来自于金粒的类别。但是对于农夫乐事中需要使用碗右键生成碗装食物的块状食物则无法自动推断过去,毕竟这个推断是根据合成树进行推断的,而这种互动方式并不属于合成树(JEI可以看到这种方式生成的食物没有合成树)。
diet的自动推断功能需要在配置文件中开启,关于配置文件的翻译与解析将在后续章节中介绍。
2.1.3 增益
增益指当玩家的食物组的值满足一定条件时会给予玩家的buff/debuff(负增益也是增益)。每个增益分为三个部分,分别是attributes,effects和conditions。attributes对应玩家的attribute,effects对应玩家的effect,conditions则用来规定满足什么条件时将该增益施加给玩家。注意这里面所涉及的attribute和effect都是针对玩家的,在使用大量第三方mod的情况下请妥善区分。attributes,effects和conditions均为数组形式,其内部包含多个attribute修改,effect修改和逻辑判断条件。condition内部的这些逻辑判断条件之间的关系为“短路AND”,即当有多个condition时按照文件定义顺序依次判断,全部通过后才会施加增益。
需要额外说明的是,虽然增益只有三个部分,但是实际的增益文件中却还有一个部分,称之为groups,该部分的主要功能为注册groups中定义的食物组使其正常工作。
2.2 配置文件
diet的配置文件位于config文件下,分别为diet-client.toml和diet-server.toml
diet-client.toml的内容翻译如下(括号部分为注释的翻译,这些注释写的还蛮清楚的,比wiki好多了)
#If enabled, a button to the Diet GUI appears in player inventories.(是否开启玩家背包栏中的diet的GUI按钮)
addButton = true
#The x-position of the Diet GUI button in player inventories.(调整该按钮的x坐标)
#Range: -10000 ~ 10000(x坐标范围)
buttonX = 126
#The y-position of the Diet GUI button in player inventories.(调整该按钮的y坐标)
#Range: -10000 ~ 10000(y坐标范围)
buttonY = -22
#The primary text color of the Diet GUI, as an integer or hexadecimal.(调整GUI的文字颜色,int或者十六进制颜色表示)
textColor = "4210752"
diet-server.toml的内容翻译如下(括号部分为注释的翻译,这些注释写的还蛮清楚的,比wiki好多了):
#The minimum percentage that diet groups can be reduced to upon death.
(玩家复活后会扣除食物组的值,这里设定的是保底留下多少值,前提是你死亡前的值要超过所设定的值。)
(有趣的是,游戏中和命令行中展示的都是百分数即0-1,而设置中却是整数)
#Range: 0 ~ 100
deathPenaltyMin = 0
#The reduction in percentage applied to all diet groups upon death.
(每次死亡时对每个食物组扣除多少值/扣除多少百分比)
#Range: 0 ~ 100
deathPenaltyLoss = 100
#The method to apply for losses due to death penalties.
#AMOUNT = Reduce by a flat percentage amount
#PERCENT = Reduce by a percent of the current value
#RESET = Reset value to defaults
#Allowed Values: AMOUNT, PERCENT, RESET
(如何扣除食物组的值,AMOUNT就是扣数,PERCENT就是扣百分比,RESET就是直接重新设置为多少)
deathPenaltyMethod = "AMOUNT"
/*
这里我必须用C++风格做一个超级长的注释来详细解释一下上面的这三个配置。
案例1:
假设deathPenaltyMethod = “RESET”
此时每次玩家死亡时,食物组的值都会回到初始值,这里的初始值是指定义食物组时食物组里面的属性default_value,该属性仅在玩家第一次进入世界时用于初始化食物组数据。
案例2:
假设deathPenaltyMethod =“AMOUNT”
此时需要上述两个参数进行调整,假设deathPenealtyLoss = 50,deathPenaltyMin = 20
使用命令行 /diet set 玩家名 食物组名 1,假设食物组名为A,此时A的值为100%
/kill,则A变为50,100>20,100-50=50;
/kill,则A变为20,50>20,50-50=20;这就是deathPenaltyMin的保底作用,前提是你必须先超过才行。
如果你的A值小于20,即使kill,A也不会回到20而是直接扣50,扣到0为止
案例3:
假设deathPenaltyMethod =“PERCENT”
此时需要上述两个参数进行调整,假设deathPenealtyLoss = 50,deathPenaltyMin = 20
使用命令行 /diet set 玩家名 食物组名 1,假设食物组名为A,此时A的值为100%
/kill,则A变为50,100>20,100-100*50%=50;
/kill,则A变为25,50>20,50-50*50%=25;
/kill,则A变为20,25>20,25-25*50%=20;
如果你的A值小于20,即使kill,A也不会回到20而是直接扣掉50%,扣到0为止
*/
#The percentage reduction in total gain for each diet group consumed at once.
(从描述上看貌似是每次增加食物组的值的总惩罚,经翻阅代码查看发现是对已经计算出来的食物的增加量进行一个加权惩罚,后续还有0.5%的保底,因此建议保持不动)
#Range: 0 ~ 100
gainPenaltyPerGroup = 15
#The percentage reduction in total decay for each diet group decayed at once.
(从描述上看貌似是每次自然消耗食物组值的总惩罚,经翻阅代码查看发现是对食物组设定的的自然消耗量进行一个加权惩罚后作为实际的消耗量,因此建议保持不动)
#Range: 0 ~ 100
decayPenaltyPerGroup = 15
#List of food quality overrides for diet gain values.
#Format: "modid:name;quality"
(极其不确定这是个什么玩意,看代码的话貌似是可以人为定义某个食物的恢复值,而饱和度会被自动设置为0)
foodOverrides = []
#If enabled, food groups are assigned to unclassified items based on ingredients.
(是否开启自动推断,开启的话那些未定义食物组的item如果可以食用的话则会根据其合成树对类型进行归纳推断,而增加的值仍然根据当前item决定)
generateGroupsForEmptyItems = true
#If enabled, food group tooltips are hidden until player has eaten that type of item.
(是否在未吃过该食物时隐藏其成分信息)
hideTooltipsUntilEaten = false
2.3 命令介绍
diet的命令有很多,在控制台输入/diet即可根据提示补充后续的输入。其中大部分命令不仅在字面上比较好理解(连这英文都看不懂的话多少有点扯了),需要着重介绍的是export命令,其中可以导出那些没有被食物组定义的食物ID到csv中,虽然你仍然需要一个个找具体的食物(不然你不看合成表用脚填数据啊),但是这可以帮助你快速定位具体的mod,相对而言还是比较实用的。
2.4 数据包文件组成及其介绍
diet数据包的数据包的目录大致如下:
作者本人正在使用的数据包目录树如下:
----data_pack_for_diet\
|----pack.mcmeta
|----data\
| |----diet\
| | |----diet\
| | | |----groups\
| | | | |----eggs.json
| | | | |----fishes.json
| | | | |----fruits.json
| | | | |----grains.json
| | | | |----honeys.json
| | | | |----milks.json
| | | | |----mushrooms.json
| | | | |----nuts.json
| | | | |----ores.json
| | | | |----proteins.json
| | | | |----salt.json
| | | | |----sugars.json
| | | | |----vegetables.json
| | | |----suites\
| | | | |----builtin.json
| | |----tags\
| | | |----items\
| | | | |----eggs.json
| | | | |----fishes.json
| | | | |----fruits.json
| | | | |----grains.json
| | | | |----honeys.json
| | | | |----ingredients.json
| | | | |----milks.json
| | | | |----mushrooms.json
| | | | |----nuts.json
| | | | |----ores.json
| | | | |----proteins.json
| | | | |----salt.json
| | | | |----special_food.json
| | | | |----sugars.json
| | | | |----vegetables.json
其中data_pack_for_diet是自定义的数据包名称,下面包括data目录和pack.mcmeta文件,mcmeta文件的写法自行参考mc的wiki,data目录下仅包含diet目录,注意当前描述的这个diet目录的名称相当于namespace,你改成别的也是可以的,但其内部目录的名字就必须严格按照规定。diet目录下又包含diet目录和tags目录,tags目录存放的是食物的tag,diet目录下存放的是食物组和增益,这两个目录及其内部目录的名称均不可更改。首先介绍diet目录下的groups目录,其内部存放着食物组的JSON定义文件。JSON文件名就是该食物组的唯一ID名,也是食物组在命令行和后续的资源包翻译文件中所唯一确定的翻译字段,注意是全局唯一!!!因此尽量不要随意修改文件名称。diet目录下还有一个suites目录,其内部存放的是增益信息,作者虽然在wiki中说明可以将增益写在多个文件中,每个文件名不重复就行,但是考虑到其绑定了GUI页面显示,因此在实际使用过程中更推荐将增益全部写入到builtin.json文件中,并且加上replace=true的字段,整体替换到内置的数据包中的文件。但对于groups下面的文件倒是不用替换,即使你的文件名跟内置数据包中的文件名重复,默认也是用你自己的数据包定义的食物组信息进行覆盖。最后再看tags目录下items目录下的文件,此处的文件定义了每个食物组包含哪些食物,因此其文件名要求和食物组中的文件名保持完全一致,否则将绑定失败或者加载失败。在此处极其推荐在文件中添加replace=true进行覆盖,防止内置数据包对该处可能出现的重名文件造成影响。极其不推荐对内置数据包进行修修补补的操作,直接扬了最好,这样在检查文件时只要发现那个食物组的信息不见了那就是该文件中的内容错了。
2.5 资源包文件组成及其介绍
资源包主要是为了将自定义的食物组进行翻译,便于阅读理解。
资源包大致结构如下:
assets\diet\lang文件下存放的就是对自定义食物组的翻译。其代码格式如下所示:
{
"groups.diet.drinks.name": "饮料",
"groups.diet.eggs.name": "蛋类",
"groups.diet.fishes.name": "鱼类",
"groups.diet.fruits.name": "水果",
"groups.diet.grains.name": "谷物",
"groups.diet.honeys.name": "蜂蜜",
"groups.diet.milks.name": "奶类",
"groups.diet.mushrooms.name": "蘑菇",
"groups.diet.nuts.name": "坚果",
"groups.diet.proteins.name": "肉类",
"groups.diet.sugars.name": "糖类",
"groups.diet.salt.name": "盐类",
"groups.diet.vegetables.name": "蔬菜",
"groups.diet.wines.name": "酒类",
"groups.diet.ores.name":"金属"
}
即groups.diet.食物组名.name:食物组翻译的格式,相比较于数据包已经算简单清晰得多得多得多了。
2.6 总结
本章节对diet模组的主要内容及相关名词,配置文件,数据包和资源包格式进行了说明和阐述,了解该部分内容将有助于后续对食物组,食物分配和增益的编辑于修改。
3 食物组编辑与修改
3.1 食物组定义
2.4节中已经阐明了食物组文件应该存放的位置和食物组文件的命名问题,这里不再重复阐述。本节将重点介绍食物组的JSON格式以及各字段的含义。下面展示一个样例:
{
"icon": "minecraft:diamond",
"color": "#ffffff",
"order": 5,
"default_value": 0.25,
"gain_multiplier": 2.0,
"decay_multiplier": 0.5,
"beneficial": true
}
各字段含义如下所示:
参数名 | 参数类型 | 参数格式 | 默认值 | 字段是否必须 | 含义与描述 |
icon | string | id格式 | minecraft:air | 否 | 在GUI界面中用于控制食物组的图标 |
color | string | 16进制颜色字符串 | #ffffff | 否 | 在GUI界面中控制进度条的颜色 |
order | integer | 整数 | 1 | 否 | 在GUI界面中控制其排列顺序,数字越小越靠上 |
default_value | decimal | 0-1的浮点数 | 0.0 | 否 | 设定食物组的初始值 |
gain_multiplier | decimal | 浮点数 | 1.0 | 否 | 参与吃下食物时增加食物组的值的修正 |
decay_multiplier | decimal | 浮点数 | 1.0 | 否 | 参与到饥饿时减少食物组的值的修正 |
beneficial | boolean | true/false | false | 否 | 食物组是否有益,true时tooltips中的文字颜色为绿色,反之为红色,是用来配合增益使用的友好提示 |
3.2 添加/修改食物组
在2.4节中已经阐明,diet的食物组定义文件放在groups目录下,且文件名即为食物组的唯一ID和组名,如果与原版的食物组名称重复则玩家的文件内容将覆盖内置数据包的文件内容。因此创建新的食物组/修改食物组的方法如下:
构建数据包,保证数据包格式正确
在groups目录下创建文件xxx.json,使用vscode编辑该JSON文件。请务必保证每个字段都有定义,虽然mod中提供了默认值使得该字段看起来都是非必须的,但是极其不推荐这种缺斤少两的行为。
以上工作完成后将数据包导入游戏(推荐使用paxi等第三方mod管理数据包)创建世界并进入
假设创建了test.json食物组信息:
打开游戏,此时应该出现的效果如下:
GUI中没有任何有关test的食物组信息
/diet set命令 add命令 get命令中能查询到存在test这个groups选项,但只有get命令执行后会出现提示信息,set/add执行时没有任何提示出现。
注意,这是正常的现象,之所以会这样是因为你没有绑定该食物组,现在我们需要移步到增益文件了。
3.3 让食物组正常工作
让食物组正常工作的方法很简单,需要移步到suits目录中。在2.4节已经阐明了suits目录最推荐的使用方法为仍然创建builtin.json,并使用replace=true对内置数据包文件内容进行整体替换,且将所有的增益都写到该文件中。当然目前并不需要具体的书写增益部分,只需要书写replace部分和groups部分即可。
在suits目录下创建builtin.json
设置字段replace=true
在groups字段中填入所需要的食物组名称(即食物组文件名,必须完全一致!)
设置effects字段为[](空数组)
保存文件
此时重新进入世界,应会出现以下结果:
GUI中出现了你的食物组,但其名称为groups.diet.test.name
/diet 命令行能正常执行命令并给出反馈
此时便意味着你的食物组已经可以正常工作了,只是还差一个本地化翻译,按照2.5节中的资源包结构照猫画虎即可。
3.4 如何修复bug
对于diet的数据包而言,出现bug的直接原因往往是因为你的JSON文件中有字段名称或者字段内容书写错误,或者数据包结构不正确导致无法识别,或者干脆就是没有编写完整而导致的“错误”。因此请务必严格按照wiki和给出的上述建议进行操作。严格对照字段内容,如果实在不放心可以直接去wiki或者github上复制粘贴一份。
4 食物分类
4.1 特殊食物组
当设计好食物组并确定可以使用后,就需要给食物打上tags用来标记食物所属的食物组。在diet中,除了我们自定义的食物组tags外,还有另外两种特殊的食物组tags:ingredients和special_food。
(1)ingredients 阻止自动推断的tag
属于ingredients的食物将在自动推断开启时不会把自身的tags按照合成树向后传递。
(2)special_food 特殊食物。
属于special_food的食物往往是诸如蛋糕等虽然没有饱食度,但是通过互动的方式可以获得饱食度的食物。有了该标签后这些食物的tooltips中将会显示他们所属的食物组,只不过不能像正常食物一样显示出具体增加多少值。
4.2 添加食物到食物组中
添加食物到特定食物组中的方法较为简单,假设我们要创建eggs的食物分配组:
(1)在tags/items目录下创建eggs.json。(文件名仍然是食物组名,和groups下的保持一致)
(2)json内部文件格式为:
{
"replace": true,
"values":[
{
"id": item的id,
"required": false
}
]
}
其中 replace的目的是排除内置数据包的影响,建议在此处直接使用replace:true整体屏蔽掉内置数据包。values内部包含一个或多个组合,尽管wiki上给出的写法是直接写id就行,但是我在翻阅源代码时发现了更安全的写法,也就是文件格式中所展示的{id,required}组合,这样即使id出错或者不存在于游戏中,由于required=false因此会直接忽略该数据,否则会因为找不到id而爆炸。
上图展示了作者正在使用和调整和数据包中的eggs.json的一部分内容。
5 增益撰写
5.1 增益参数
回到suites/builtin.json文件中,现在我们要正式撰写增益了。增益内容写在effects数组中,我们提取一个数组值对其参数进行分析和详解。
{
"attributes": [
{
"name": "minecraft:generic.movement_speed",
"operation": "multiply_base",
"amount": 0.25
}
],
"status_effects": [
{
"name": "minecraft:hunger",
"power": 3
}
],
"conditions": [
{
"groups": ["sugars"],
"match": "all",
"above": 0.8,
"below": 1.0
}
]
}
attributes是用来描述如何修改player的attribute,其内部字段为:
字段名 | 字段类型 | 参数格式 | 描述 | 可选值 |
name | string | attributeId (minecraft:generic.max_health) | 要修改的玩家attribute | 参考参数格式问wiki和mod |
operation | string | 指定字符串 | 如何对attribute进行操作 | add(加amount) multiply_total(value*amount) multiply_base(base*amount) |
amount | decimal | 浮点数 | 配合operation进行操作 | 浮点数 |
status_effects是用来描述施加给player的effect,其内部字段为:
字段名 | 字段类型 | 参数格式 | 描述 | 可选值 |
name | string | effectId (minecraft:hunger) | 要施加的效果 | 问wiki和mod |
power | integer | 整数 | 等级几的效果 | 问你的效果 |
conditions为上述effects和attributes的触发条件,其内部字段为:
groups:string[],一个数组,描述需要获取那些食物组的数据
above:decimal,一个0-1的浮点数,描述的是食物组数据的下确限
below:decimal,一个0-1的浮点数,描述的是食物组数据的上确限,既食物组数据在[above,below]内部才行
match:可选字符串,描述判断方法,有以下几种判断方法:
all(groups中所有的食物组数据全部在范围内才为true)
any(groups中有一个食物组的数据在范围内即为true)
average(groups中所有食物组数据的平均值在范围内才为true)
none(groups中的所有食物组都不在范围内才为true)
every(对于groups中的食物组,每有一个食物组满足条件,对应的effects中的power+1,换句话说如果定义的是力量1,那么一个满足就是力量1,两个就是力量2,三个就是力量3)
具体书写方法参看下图中作者正在使用的数据包中的样例
需要提醒的是,对于每个effect中,condition必须存在,但attributes和status_effects可以不同时出现,只使用其中一个也是可以的。