本篇教程由作者设定未经允许禁止转载。

引言

目前制作匠魂3附属模组的主要方式有:纯Java代码、使用Json Things和使用MCreator。Json Things上手简单,无门槛,不包含Java代码,逻辑简单,但存在不小的局限性,只能用来编写较为简单的模组。

MCreator代码可视化,无门槛,局限性小,但容易出现低创模组和差评模组。

此教程主要针对如何使用纯Java代码,从安装IDE开始,创建一个1.18.2匠魂3的附属模组。

准备工作

安装Intellij IDEA

常用的Java IDE有Intellij IDEA、Eclipse等。从UI友好程度、直观性和美观性上来说,前者都是要优于后者的。

故此教程使用Intellij IDEA。

下载传送门->Download IntelliJ IDEA – The Leading Java and Kotlin IDE (jetbrains.com)

免费版即可,安装过程不再赘述,需要注意的是IDEA的系统资源消耗远大于Eclipse,电脑性能不佳的读者慎入。

准备Forge MDK

Forge MDK是一个项目模板,它是你的项目的基本框架。

下载传送门->Downloads for Minecraft Forge for Minecraft 1.18.2

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第1张图片点击下载MDK

注意:由于我们会安装多个非同一作者开发的模组,受作者更新频率等因素影响,其要求的Forge版本可能各不相同,如在启动时发现深红色的错误信息:cannot find …… in classpath,请按照“……”内的Forge版本更换MDK。(截至教程编写日期,所需版本为Forge 40.2.1),可在上图的“All Versions”中找到,如图:

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第2张图片

点击后,进入如下页面

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第3张图片下载完成后,解压到一文件夹,文件夹名可以是你的项目名。需要注意的是文件夹名中不能包含中文或特殊字符。

更好的使用体验

汉化

用IDEA打开解压目录下的“build.gradle”文件,进入到如下页面

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第4张图片

IDEA的默认界面语言是英语,为照顾部分英语水平欠佳的读者,这里提供安装简体中文语言包的教程。

点击左上角的“File”,在展开的页面中点击“Settings(图标为扳手)”

接下来,在设置页面中点击“Plugins”,按照下图操作:

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第5张图片检查正上方的“Marketplace”和“Installed”两个选项卡,确保目前选中的是“Marketplace”

安装完成后,第三步中的Install图标会变为绿底白字的“Restart IDE”。点击并按照提示重启,此时界面语言已变为中文,如图:

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第6张图片变为简体中文的IDEA页面

JDK

确保你指定了正确的Java SDK。点击“文件”选项卡下的“项目结构”,进入如下页面:

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第7张图片

在左侧的选项卡下点击“项目”,为项目指定Java SDK。

实测Java 17.0.1可用,若构建时出现问题,请尝试使用JDK 8等版本。

更快地构建

为了在测试时拥有更快的构建速度你的时间非常宝贵,请按下图操作:

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第8张图片将这两项由“Gradle(默认)”改为“Intellij IDEA”,点击“应用”保留更改。

开发环境配置

build.gradle

打开文件选项卡下的“build.gradle”文件,在这里我们将进行构建最重要的一步。

由于我们是在编写匠魂的附属,那么我们就要声明匠魂为模组的运行库。

同时为了便于测试,这里建议将JEI添加进运行库中。

按照下文操作:

1.找到这三行字符

version = '1.0'
group = 'com.yourname.modid' // http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = 'modid'

将'com.yourname.modid'改为你的“域名”,例如slimeknights.tconstruct、com.james.tinkerscalibration,注意必须为英文

将'modid'改为你的模组名,例如tinkerscalibration,注意必须为英文

2.替换“examplemod”

这里为了操作简便,我们可以使用“编辑”选项卡下、“查找”子选项卡中的“替换”功能,将所有的“examplemod”改为你的模组名,注意必须为英文。

这部分需要特殊处理:

jar {
    manifest {
        attributes([
                "Specification-Title"     : "examplemod",
                "Specification-Vendor"    : "examplemodsareus",
                "Specification-Version"   : "1", // We are version 1 of ourselves
                "Implementation-Title"    : project.name,
                "Implementation-Version"  : project.jar.archiveVersion,
                "Implementation-Vendor"   : "examplemodsareus",
                "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ")
        ])
    }
}

将“examplemodsareus”替换为你的名字,例如“dddddsj”,最好为英文

此时“Date()”会报错,暂时不用管它。

3.添加匠魂和JEI的相关库

找到如下代码:

repositories {
    // Put repositories for dependencies here
    // ForgeGradle automatically adds the Forge maven and Maven Central for you

    // If you have mod jar dependencies in ./libs, you can declare them as a repository like so:
    // flatDir {
    //     dir 'libs'
    // }
}

将第一对大括号内的所有内容替换为:

mavenLocal()
maven { // SlimeKnights and JEI
    name 'DVS1 Maven FS'
    url 'https://dvs1.progwml6.com/files/maven'
}

这里同样提供几个其他热门模组的maven库,方便想要添加联动的读者:

待补充

然后找到下方的“dependencies {”,同样将其中所有内容替换为:

minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}"
implementation fg.deobf("slimeknights.tconstruct:TConstruct:1.18.2-${tcon_version}")
implementation fg.deobf("slimeknights.mantle:Mantle:1.18.2-${mantle_version}")
implementation fg.deobf("mezz.jei:jei-${minecraft_version}:${jei_version}")

build.gradle修改完毕

mods.toml

转到src/main/resources/META-INF,打开“mods.toml”文件

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第9张图片可以看到文件下有大段的注释,大可将其全部删去

将“modId="examplemod"”这一行中的“examplemod”改为你的模组名

将“displayName="Example Mod"”这一行中的“Example Mod”改为你的模组名的首字母大写带空格形式,注意必须为英文。

例如,“examplemod”修改为“tinkerscalibration”,“Example Mod”修改为“Tinkers Calibration”。

logoFile="examplemod.png"
credits="Thanks for this example mod goes to Java"
authors="Love, Cheese and small house plants"
description='''
This is a long form description of the mod. You can write whatever you want here

Have some lorem ipsum.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed mollis lacinia magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed sagittis luctus odio eu tempus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque volutpat ligula eget lacus auctor sagittis. In hac habitasse platea dictumst. Nunc gravida elit vitae sem vehicula efficitur. Donec mattis ipsum et arcu lobortis, eleifend sagittis sem rutrum. Cras pharetra quam eget posuere fermentum. Sed id tincidunt justo. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
'''

以上几行代码分别是模组图标文件、鸣谢名单、作者团队和模组简介,可根据需要自行修改,可同时使用中文和英文。

[[dependencies.examplemod]] #optional
    # the modid of the dependency
    modId="forge" #mandatory
    # Does this dependency have to exist - if not, ordering below must be specified
    mandatory=true #mandatory
    # The version range of the dependency
    versionRange="[40,)" #mandatory
    # An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory
    ordering="NONE"
    # Side this dependency is applied on - BOTH, CLIENT or SERVER
    side="BOTH"
# Here's another dependency
[[dependencies.examplemod]]
    modId="minecraft"
    mandatory=true
# This version range declares a minimum of the current minecraft version up to but not including the next major version
    versionRange="[1.18.2,1.19)"
    ordering="NONE"
    side="BOTH"

这里声明了模组的前置,以上代码分别声明了Forge和MC本体作为前置,需将“examplemod”改为你的模组名,然后添加如下代码:

[[dependencies.模组名]]
modId="tconstruct"
mandatory=true
versionRange="[1.18.2-3.6.4.113,)" //截至此教程编写日期,最新的tic版本
ordering="AFTER"
side="BOTH"

若要添加其他模组作为前置或联动,基本格式一致,需要注意的是若添加模组是本模组的前置,“mandatory”一行必须为“true”,否则为“false”。

gradle.properties

gralde.properties文件和build.gradle在同一目录下,打开它,把其中内容替换为如下代码:

org.gradle.jvmargs=-Xmx3G
org.gradle.daemon=false

mod_version=1.18.2-1.0.0

minecraft_base_version=1.18
minecraft_version=1.18.2
minecraft_range=[1.18.2,1.19)
forge_version=40.2.9 //如果你使用了其他版本的Forge MDK,修改此行至对应版本,例如40.2.1
mappings_version=2022.03.13
loader_range=[39.0,)
forge_range=[40.0.41,)
jei_version=9.7.1.255
tcon_version=3.6.4.113
mantle_version=1.9.44
tconstruct_range=[3.5.0.17,)
mantle_range=[1.9.20,)

这时build.gradle下的Date()不会报错了。

构建

按下图操作:

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第10张图片

点击图标后,窗口左下角会提示“Gradle sync started(片刻之前)”,然后会开始下载MC的资源,这一过程受网络的影响可能会失败,没有关系,多试几次。

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第11张图片完成构建后“Example”可展开,窗口左下角提示“Gradle sync finished”

为了方便,这里建议进行如下操作:

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第12张图片

当看到这行字时,即代表“genIntellijRuns”成功,此时就可以启动游戏了。

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第13张图片

按下图操作,最后点击右侧绿色的“”或“🐞”图标,启动游戏。

这时运行或调试栏会自动弹出,DEBUG、INFO和WARNING都属正常,若在这个阶段出现ERROR,即代表你的设置不到位,请重新阅读这一节。

如果游戏启动后弹出错误:mods.toml missing metadata for modid ***,请完成“正式开发”章节的第一步,即修改文件夹名和类名提前,检查问题是否解决。

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第14张图片

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第15张图片游戏启动后的页面,左下角提示“6 mods loaded”,分别是匠魂、Forge、MC本体、地幔、JEI和你的模组

正式开发

恭喜你,你成功度过了会让大多数人放弃的阶段,正式开启了开发之旅!

也从这一步开始,下面的步骤需要一定的编程基础,不保证100%的人都能学会。

将com文件夹下的example和examplemod分别修改为你的名字和你的模组名,删掉默认的ExampleMod类,重建一个类:

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第16张图片

在弹出的页面中给这个类一个名字,一般是你的模组名,需要英文,为了美观建议首字母大写。

注意,在以下内容中,直接复制粘贴的代码可能会报错,提示信息大多为“无法解析符号 '***'”。此时只需点击“导入类”。如果提示多选,选择有MC或匠魂的关键词的选项,例如“tconstruct”"minecraftforge"。

在主函数下,添加这一行代码:

public static final String MOD_ID = "*"; //*是你的模组名,需要英文

然后,在主函数前,添加如下代码:

@Mod(*.MOD_ID) //*是你的主类名,需要英文

注:在后面的步骤中,复制粘贴

第一个物品和创造模式物品栏

物品是MC的基础,也会在开发中不可避免地使用到,所以我们从添加物品开始。

在你的模组主类的文件夹下,建立一个新类,名字可以是“模组名”+“Items”。

在新类的主函数下,添加如下代码:

public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, *.MODID); //*是你的主类名,需要英文
public static Item register() {
    return new Item(new Item.Properties().tab(*.itemGroup));
}

此时代码会报错,暂时不用管它。

然后就要注册第一个物品了,例如我们要添加一个名为钢锭的物品,代码如下:

public static RegistryObject<Item> steel_ingot = ITEMS.register("steel_ingot", *::register); //*是这个类的名字

第一个物品注册成功,现在我们要给它添加材质和模型。

在resources文件夹下,新建这两个文件夹,注意后一个文件夹是前一个的子文件夹:assets、你的模组名(小写英文)。

接着,在后一个文件夹下,新建两个文件夹,两者是并列关系:models(模型)和textures(纹理)。

在models文件夹下,新建一个json文件,如图:

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第17张图片

在随后弹出的窗口下给这个文件名字,它应该是“steel_ingot.json”。

注意IDEA的新建文件是不包含后缀名的,不要忘记输入后缀名。

在打开的steel_ingot.json下,输入如下代码:

{
  "parent": "item/generated",
  "textures": {
    "layer0": "*:item/steel_ingot" //*是你的模组名
  }
}

在texture文件夹下,粘贴你画好的材质,需要为16×16(或其整数倍)像素。

还记得上文的报错吗?需要如下处理:

在主类所在文件夹下,新建一个类,名为“模组名(英文大写)”+Group。

在后者中删掉自动创建的函数,添加如下代码:

public class * extends CreativeModeTab {
    public *() {
        super("*");
    } //*是你的类名

    @Override
    public ItemStack makeIcon() {
        return new ItemStack(#.steel_ingot.get()); //#是注册物品的类的名字
    }
}

接下来,在主类的主函数下,添加如下代码:

public static final CreativeModeTab itemGroup = new *(); //*是上一个类名

但不要着急启动游戏,还有一步。

在模组主类的主函数下,输入如下代码:

public *() { //*是你的主类名
    IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();
    MinecraftForge.EVENT_BUS.register(this);
    #.Items.register(bus);
}

此时就可以启动游戏了。

总结:目前我们的模组应该是如下结构:

main    java               com                 example               examplemod       ExampleMod.java 

                                                                                                                  ExampleModItems.java

                                                                                                                  ExampleModGroup.java

                                                                                                                  ModGroup.java

            resources      assets              examplemod       models      item       steel_ingot.json

                                                                                     textures     item       steel_ingot.png

                                 META-INF        mods.toml

                                 pack.mcmeta

模组主类的代码举例:

package com.example.examplemod;

import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
@Mod(ExampleMod.MOD_ID)
public class ExampleMod {
    public static final String MOD_ID = "examplemod";
    public ExampleMod() {
        IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();
        MinecraftForge.EVENT_BUS.register(this);
        ExampleModItems.ITEMS.register(bus);
    }
    public static final CreativeModeTab itemGroup = new ExampleModGroup();
}

如果我手滑把模组搞坏了怎么办?

在开发时,我们偶尔会因一些操作使游戏启动时报错,如果在撤销了这些操作后游戏仍然不能正常启动且错误信息难以理解,可以进行如下操作:

1.重新下载或解压一个相同版本的Forge MDK(如果你没有把原压缩文件删掉)。

2.将项目的以下文件复制粘贴到新项目下:build.gradle,gradle.properties和mods.toml。

3.按照教程构建项目,成功后再把原项目main文件夹下的所有内容复制粘贴过来。

这个方法可以解决部分项目的疑难杂症。如果不奏效,多半是代码问题,请借助翻译工具仔细分析问题原因。

第一个材料和强化

丰富的材料和强化是匠魂的魅力所在,也是大多数附属模组的核心。

材料

创建一个新类,名字可以为“模组名”+ Materials。

在这个新类下,输入如下代码,以创建“钢”材料为例:

public static final MaterialId steel = createMaterial("steel");

你会发现并不存在“createMaterial”函数,它的代码如下,同样放在这个类中:

public static MaterialId createMaterial(String name) {
    return new MaterialId(new ResourceLocation(*.MODID, name)); //*是你的模组主类名
}

在模组主类的主函数前,添加如下代码:

@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)

在模组主类的主函数下,添加如下代码:

@SubscribeEvent
public static void gatherData(final GatherDataEvent event) {
    DataGenerator gen = event.getGenerator();
    ExistingFileHelper fileHelper = event.getExistingFileHelper();
    if(event.includeClient()){}
    if(event.includeServer()){} //两个大括号内的内容暂时不用填写

这样我们就成功创建了一种名为“steel”的新材料,但它目前仅仅是一个空壳。

想要给它添加渲染信息和等级、数据、材料特性,有两种方式,使用Java或Json。这里提供Json方法:

  1. 在resourses\assets\模组名下,新建一个名为tinkering的文件夹。在这个文件夹下,新建文件夹materials,在materials文件夹下新建一个名为steel的json文件。这个文件存放的是材料的渲染信息,详见纹理生成器及其相关内容使用教程 - [TiC3]匠魂3 (Tinkers' Construct 3) - MC百科|最大的Minecraft中文MOD百科 (mcmod.cn)

  2. 在resources文件夹下新建一个名为data的文件夹,在这个文件夹下,用你的模组名新建文件夹,再新建tinkering和materials两个文件夹,均为嵌套关系。在materials文件夹下,新建三个并列文件夹:definition、stats和traits。

definition是材料的定义,包含是否可在部件制造台上制作、材料等级(1-4)、分类(主世界、下界、近程、远程等)和是否隐藏。以玛玉灵为例:

{
  "craftable": false, //玛玉灵只可浇注
  "tier": 4, //玛玉灵是四级材料
  "sortOrder": 2, //玛玉灵主要用在武器上
  "hidden": false //玛玉灵不是隐藏材料
}

注:“sortOrder”根据材料用途,共有如下分类:

0:普通    1:采掘    2:战斗    3:特殊(例如生铁)    4:战斗(仅远程,例如)    5:联动    10:下界    15:末地

20:仅绑定结    25:仅修复或材质用(用途未知,不建议使用)

stats是材料的属性数值,同样以玛玉灵为例:

{
  "stats": {
    "tconstruct:extra": {}, //附件,无数据
    "tconstruct:handle": {
      "durability": 1.1,
      "miningSpeed": 0.9,
      "attackSpeed": 0.95,
      "attackDamage": 1.25 //近程手柄,均为倍率
    },
    "tconstruct:head": { //近程头部
      "durability": 1250, 
      "miningSpeed": 6.5,
      "harvestTier": "minecraft:netherite",
      "attack": 3.5 //除采掘等级外均为具体数值,玛玉灵采掘等级为下界合金,采掘等级共有木、石、铁、金、钻石和下界合金六种,可自定义,后文涉及
    },
    "tconstruct:limb": { //弓臂
      "durability": 1250, //具体数值
      "drawSpeed": -0.35,
      "velocity": 0.25,
      "accuracy": -0.15 //这三项若增益则为正,不带负号;若减益则为负,带负号
    },
    "tconstruct:grip": { //弓把
      "durability": 1.1, //倍率
      "accuracy": -0.2,
      "meleeAttack": 3.5 //具体数值
    }
  }
}

traits是材料的材料特性,有多种情况,这里以玛玉灵和银为例。

玛玉灵:

{
  "default": [
    {
      "name": "tconstruct:insatiable", //贪婪
      "level": 1
    }
  ]
}

银:

{
  "perStat": {
    "tconstruct:ranged": [
      {
        "name": "tconstruct:holy", //神圣
        "level": 1
      }
    ],
    "tconstruct:melee_harvest": [
      {
        "name": "tconstruct:smite", //亡灵杀手
        "level": 1
      }
    ]
  }
}

当一种材料的近程和远程特性一致时,可以玛玉灵的形式使用“default”;不一致时,需使用“perStat”。若一种材料仅能作为近程或远程材料中的一种,建议使用“perStat”,参考

{
  "perStat": {
    "tconstruct:ranged": [
      {
        "name": "tconstruct:featherweight", //轻盈
        "level": 1
      }
    ]
  }
}

看到这里读者可能会产生疑惑,如果一种材料特性只有一级,不可叠加,该如何在“traits”中实现呢?

事实上,仅有一级的强化会在强化本身中实现,见后文。

强化

简单的强化可使用Json,但较复杂的(例如贪婪、黏液覆层)需使用Java。简单的强化Java也可以,总结就是Java无所不能但也有例外

为了整齐,在项目主类的文件夹下新建一个“modifiers”文件夹,强化就存放在这个文件夹。

让我们从最简单的强化开始。定义一种“坚固”强化,每级提高工具10%的耐久度。

要编写一种强化(Modifier),我们就要在这种强化中继承匠魂的Modifier类,如图:

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第18张图片

导入类时,鼠标指针悬停在报错的“Modifier”上,点击浮窗中的“导入类”,操作如下:

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第19张图片

导入类后,添加如下代码:

@Override
public void addToolStats(ToolRebuildContext context, int level, ModifierStatsBuilder builder) {
    ToolStats.DURABILITY.multiply(builder, 1 + 0.1 * level);
}

这段代码是什么意思呢?

将鼠标指针悬停在“@Override”上,可以看到描述如下:“Indicates that a method declaration is intended to override a method declaration in a supertype……”大致意思是,“@Override”是一个注解,表示其下方的函数重写了超类中的函数。

此处“超类”即是匠魂的“Modifier”类。进行如下操作跳转到“Modifier”类:

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第20张图片

或:

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第21张图片

进入到Modifier类。可以看到这个类的代码很长,可以用查找功能找到“addToolStats”函数:

【低门槛】【超详细】用Java代码开发匠魂3附属模组(持续施工中)-第22张图片

可以看到,Modifier类中的“addToolStats”是一个空函数,在我们的强化中,我们重写了这个函数使它具有功能。

匠魂3中与“addToolStats”类似的函数有很多个,下表给出了强化常用函数的源代码和作用等:(红色加粗下划线的函数名称为常用函数,需重点理解)

注意:由于作者KnightMiner正在将方法迁移至各个不同的“钩”,此表中的内容可能与最新版本的匠魂3有差异,请以代码实际内容为准!

名称源代码作用运行条件说明示例
registerHooks
protected void registerHooks(ModifierHookMap.Builder hookBuilder)
给强化注册“Hook”(钩)打上强化时

某些特殊强化的函数在“钩”内。这个函数给强化注册了“钩

”使得这些强化的函数可以被运行。

贪婪”强化:
@Override
protected void registerHooks(Builder hookBuilder) {
  hookBuilder.addHook(this, TinkerHooks.PROJECTILE_HIT, TinkerHooks.CONDITIONAL_STAT);
} //给强化注册了两个钩,分别是ProjectileHitModifierHook和ConditionalStatModifierHook。
//ProjectileHitModifierHook是箭矢击中目标的钩,包含两个函数,允许触发一些特殊效果,例如给予射击者贪婪BUFF
//ConditionalStatModifierHook是工具的实时属性的钩,包含两个函数,允许改变工具的实时属性(拉弓速度等)

getPriority

public int getPriority() {
  return DEFAULT_PRIORITY; //默认优先级为100
}
决定此强化什么时候运行工具使用时

优先级数值越高,强化运行越早。

在匠魂3中,不毁强化不能阻止黏液覆层消耗。因为不毁的优先级为125,黏液覆层的优先级为150。

黏液覆层运行更早,消耗更早(不毁的原理是取消耐久消耗事件)。

addInformation

public void addInformation(IToolStackView tool, int level, @Nullable Player player, List<Component> tooltip, slimeknights.tconstruct.library.utils.TooltipKey tooltipKey, TooltipFlag tooltipFlag)
给工具添加额外的显示信息按下Shift键或在工匠砧上时按下Shift键时,会显示工具的额外信息,例如某些强化给予的攻击伤害等。

光照提速”强化:

@Override
public void addInformation(IToolStackView tool, int level, @Nullable Player player, List<Component> tooltip, TooltipKey tooltipKey, TooltipFlag tooltipFlag) {
  addStatTooltip(tool, ToolStats.MINING_SPEED, TinkerTags.Items.HARVEST, 9 * getScaledLevel(tool, level), tooltip);
}

addStatTooltip、addPercentTooltip等函数会稍后讲解

addVolatileData

public void addVolatileData(ToolRebuildContext context, int level, ModDataNBT volatileData)
给工具添加动态数据强化加载时工具的某些数据会不断改变,例如黏液覆层和储存的流体(储液强化)。

“厚积”强化:

@Override
public void addVolatileData(ToolRebuildContext context, int level, ModDataNBT volatileData) {
  OverslimeModifier overslime = TinkerModifiers.overslime.get(); //黏液覆层
  overslime.setFriend(volatileData); //设置黏液之友
  overslime.addCapacity(volatileData, level * 25); //增加上限
  overslime.multiplyCapacity(volatileData, 1f + (level * 0.5f)); //增加上限
}


addToolStats
public void addToolStats(ToolRebuildContext context, int level, ModifierStatsBuilder builder)
更改工具的面板属性强化加载时直接更改工具的攻击伤害、采掘速度和采掘等级等面板属性。

“晶固”强化:

@Override
public void addToolStats(ToolRebuildContext context, int level, ModifierStatsBuilder builder) {
  ToolStats.VELOCITY.add(builder, level * 0.1f); //箭矢初速度
}


beforeRemoved
public void beforeRemoved(IToolStackView tool, RestrictedCompoundTag tag)
更改工具的NBT强化即将被移除时更改强化施加时给工具添加的NBT。

“火焰保护”强化:

@Override
public void beforeRemoved(IToolStackView tool, RestrictedCompoundTag tag) {
  EnchantmentModifier.removeEnchantmentData(tag, Enchantments.FIRE_PROTECTION); //移除附魔信息
}


onRemoved
public void onRemoved(IToolStackView tool)
清理工具的“persistent data”(持久信息)强化移除后清理强化施加时给工具添加的持久信息(黏液覆层等)。

“装饰”强化:

@Override
public void onRemoved(IToolStackView tool) {
  tool.getPersistentData().remove(getId());
}


onDamageTool
public int onDamageTool(IToolStackView tool, int level, int amount, @Nullable LivingEntity holder) {
  return amount;
}
更改工具的耐久消耗工具耐久消耗时取消、减少或增加工具的耐久消耗。

“不毁”强化:

@Override
public int onDamageTool(IToolStackView tool, int level, int amount, @Nullable LivingEntity holder) {
  return 0; //工具消耗了0耐久,相当于取消了耐久消耗事件
} //返回值是耐久消耗值,整型


getRepairFactor
public float getRepairFactor(IToolStackView toolStack, int level, float factor) {
  return factor;
}
更改工具的耐久回复工具修复时取消、减少或增加工具的耐久回复。

“培植”强化:

@Override
public float getRepairFactor(IToolStackView toolStack, int level, float factor) {
  // +50% repair per level 翻译:每级增加50%回复值
  return (factor * (1 + (level * 0.5f)));
} //返回值是耐久回复值,单精度浮点


onInventoryTick
public void onInventoryTick(IToolStackView tool, int level, Level world, LivingEntity holder, int itemSlot, boolean isSelected, boolean isCorrectSlot, ItemStack stack)
更新工具游戏刷新玩家的物品栏时在玩家的物品栏内更新工具,主要是回复耐久(自我修复)。

“同调”强化(暮色森林联动):

@Override
public void onInventoryTick(IToolStackView tool, int level, Level world, LivingEntity holder, int itemSlot, boolean isSelected, boolean isCorrectSlot, ItemStack stack) {
    if (!world.isClientSide() && holder instanceof Player player && !(holder instanceof FakePlayer)) {
       if (!isCorrectSlot) return;
       if (!needsRepair(tool)) return;

       int healPower = 0;

       NonNullList<ItemStack> playerInv = player.getInventory().items;

       for (int i = 0; i < 9; i++) {
          if (i != itemSlot) {
             ItemStack invSlot = playerInv.get(i);
             if (invSlot.is(TFItems.STEELEAF_INGOT.get())) {
                healPower += invSlot.getCount();
             } else if (invSlot.is(TFBlocks.STEELEAF_BLOCK.get().asItem())) {
                healPower += invSlot.getCount() * 9;
             } else if (TFItemStackUtils.hasToolMaterial(invSlot, TwilightItemTier.STEELEAF)) {
                healPower += 1;
             }
          }
       }

       ToolDamageUtil.repair(tool, averageInt(healPower * REPAIR_DAMPENER)); //回复耐久
    }
}


processLoot

public List<ItemStack> processLoot(IToolStackView tool, int level, List<ItemStack> generatedLoot, LootContext context) {
  return generatedLoot;
}
更改战利品获取战利品时(击杀生物、破坏方块)增加或减少玩家获得的战利品。

“美味”强化:

@Override
public List<ItemStack> processLoot(IToolStackView tool, int level, List<ItemStack> generatedLoot, LootContext context) {
  // if no damage source, probably not a mob
  // otherwise blocks breaking (where THIS_ENTITY is the player) start dropping bacon
  if (!context.hasParam(LootContextParams.DAMAGE_SOURCE)) {
    return generatedLoot;
  }

  // must have an entity
  Entity entity = context.getParamOrNull(LootContextParams.THIS_ENTITY);
  if (entity != null && entity.getType().is(TinkerTags.EntityTypes.BACON_PRODUCER)) {
    // at tasty 1, 2, 3, and 4 its a 2%, 4.15%, 6.25%, 8% per level
    int looting = context.getLootingModifier();
    if (RANDOM.nextInt(48 / level) <= looting) {
      // bacon
      generatedLoot.add(new ItemStack(TinkerCommons.bacon)); //向战利品中添加培根
    }
  }
  return generatedLoot;
} //返回值是生成的战利品,列表


canPerformAction
public boolean canPerformAction(IToolStackView tool, int level, ToolAction toolAction) {
  return false;
}
检查工具是否能进行特殊动作未知若返回值为true,则工具可以进行特殊动作。

“招架”强化:

@Override
public boolean canPerformAction(IToolStackView tool, int level, ToolAction toolAction) {
  return toolAction == ToolActions.SHIELD_BLOCK; //工具是否能进行格挡动作
} //返回值为工具是否可以进行特殊动作,布尔


onBreakSpeed
public void onBreakSpeed(IToolStackView tool, int level, BreakSpeed event, Direction sideHit, boolean isEffective, float miningSpeedModifier)
更改工具采掘速度采掘速度计算时更改工具的实时采掘速度。

“悬空”强化:

@Override
public void onBreakSpeed(IToolStackView tool, int level, BreakSpeed event, Direction sideHit, boolean isEffective, float miningSpeedModifier) {
  // the speed is reduced when not on the ground, cancel out
  if (!event.getEntity().isOnGround()) {
    event.setNewSpeed(event.getNewSpeed() * 5);
  }
}
removeBlock
public Boolean removeBlock(IToolStackView tool, int level, ToolHarvestContext context) {
  return null;
}
移除方块工具使用时从世界中移除方块。

“荧光”强化:

@Nullable
@Override
public Boolean removeBlock(IToolStackView tool, int level, ToolHarvestContext context) {
  if (context.getState().is(TinkerCommons.glow.get()) && tool.getDefinitionData().getModule(ToolModuleHooks.INTERACTION).canInteract(tool, getId(), InteractionSource.LEFT_CLICK)) {
    return false;
  }
  return null;
} //返回值为布尔,若为true,则覆盖原版的方块移除逻辑并终止其他与方块破坏结果有关的强化;若为false,不移除方块;若为null,运行原版的方块移除逻辑


afterBlockBreak
public void afterBlockBreak(IToolStackView tool, int level, ToolHarvestContext context)
施加特殊效果方块破坏后在方块被破坏后施加特殊效果。

“动力”强化:

@Override
public void afterBlockBreak(IToolStackView tool, int level, ToolHarvestContext context) {
  if (context.canHarvest() && context.isEffective() && !context.isAOE()) {
    // 32 blocks gets you to max, effect is stronger at higher levels
    LivingEntity living = context.getLiving();
    int effectLevel = Math.min(31, TinkerModifiers.momentumEffect.get().getLevel(living) + 1); //效果等级递增
    // funny formula from 1.12, guess it makes faster tools have a slightly shorter effect
    int duration = (int) ((10f / tool.getStats().get(ToolStats.MINING_SPEED)) * 1.5f * 20f);
    TinkerModifiers.momentumEffect.get().apply(living, duration, effectLevel, true); //给玩家施加动力效果
  }
}


finishBreakingBlocks
public void finishBreakingBlocks(IToolStackView tool, int level, ToolHarvestContext context)
施加特殊效果方块破坏动作完成后施加特殊效果。

“末影传送”强化:

@Override
public void finishBreakingBlocks(IToolStackView tool, int level, ToolHarvestContext context) {
  if (context.canHarvest()) {
    BlockPos pos = context.getPos();
    LivingEntity living = context.getLiving();
    if (tryTeleport(living, pos.getX() + 0.5f, pos.getY(), pos.getZ() + 0.5f)) { //传送玩家到破坏的方块处
      ToolDamageUtil.damageAnimated(tool, 2, living);
    }
  }
}


getEntityDamage
public float getEntityDamage(IToolStackView tool, int level, ToolAttackContext context, float baseDamage, float damage){
  return damage;
}
计算武器伤害实体被攻击时改变武器造成的伤害。

“贪婪”强化:

public float getEntityDamage(IToolStackView tool, int level, ToolAttackContext context, float baseDamage, float damage) {
  // gives +2 damage per level at max
  return damage + (getBonus(context.getAttacker(), level, TinkerModifiers.insatiableEffect.get()) * tool.getMultiplier(ToolStats.ATTACK_DAMAGE));
} //返回值是造成的伤害,单精度浮点


beforeEntityHit
public float beforeEntityHit(IToolStackView tool, int level, ToolAttackContext context, float damage, float baseKnockback, float knockback) {
  return knockback;
}
更改击退或施加特殊效果实体被攻击前更改击退或施加需要在实体被攻击前施加的特殊效果。
@Override
public float beforeEntityHit(IToolStackView tool, int level, ToolAttackContext context, float damage, float baseKnockback, float knockback) {
  // do not boost unarmed attacks twice, thats a bit too much knockback for the cost
  if (!context.getAttacker().getItemInHand(context.getHand()).isEmpty()) {
    return knockback + level * 0.5f;
  }
  return knockback; //返回值为造成的击退,单精度浮点
}
afterEntityHit
public int afterEntityHit(IToolStackView tool, int level, ToolAttackContext context, float damageDealt) {
  return 0;
}
施加特殊效果实体被攻击时成功命中实体后施加特殊效果
@Override
public int afterEntityHit(IToolStackView tool, int level, ToolAttackContext context, float damageDealt) {
  LivingEntity target = context.getLivingTarget();
  if (target != null) {
    target.setSecondsOnFire(Math.round(getEffectiveLevel(tool, level) * 5));
  }
  return 0; //返回值为对工具的额外耐久消耗,整型
}
failedEntityHit
public void failedEntityHit(IToolStackView tool, int level, ToolAttackContext context) {}
施加特殊效果实体被攻击但未造成任何伤害时

某些强化会在命中实体后施加特殊效果,但可能出现并未造成伤害的情况

,此时需要通过此方法移除施加效果(非必要)

怒火”强化:

@Override
public void failedEntityHit(IToolStackView tool, int level, ToolAttackContext context) {
  // conclusion of vanilla hack: we don't want the target on fire if we did not hit them
  LivingEntity target = context.getLivingTarget();
  if (target != null && target.isOnFire()) { //工具点燃了目标
    target.clearFire();
  }
}


getProtectionModifier
public float getProtectionModifier(IToolStackView tool, int level, EquipmentContext context, EquipmentSlot slotType, DamageSource source, float modifierValue) {
  return modifierValue;
}
获得盔甲的保护系数。使用者被攻击时(多用在盔甲上)穿戴者被攻击时(盔甲)

每1点返回值减免4%伤害,相当于1级保护魔咒。

可正可负,正则减免伤害,负则增加伤害。

爆炸保护”强化:

@Override
public float getProtectionModifier(IToolStackView tool, int level, EquipmentContext context, EquipmentSlot slotType, DamageSource source, float modifierValue) {
  if (!source.isBypassMagic() && !source.isBypassInvul() && source.isExplosion()) { //伤害来源是爆炸
    modifierValue += getScaledLevel(tool, level) * 2.5f;
  }
  return modifierValue; //返回值是保护系数,单精度浮点
}
isSourceBlocked
public boolean isSourceBlocked(IToolStackView tool, int level, EquipmentContext context, EquipmentSlot slotType, DamageSource source, float amount) {
  return false;
}
决定穿戴者是否免疫伤害穿戴者被攻击时(盔甲)使玩家免疫某些类型的伤害。

远距防摔”强化:

@Override
public boolean isSourceBlocked(IToolStackView tool, int level, EquipmentContext context, EquipmentSlot slotType, DamageSource source, float amount) {
  return source.isFall(); //返回值是是否免疫此伤害,布尔类型。若伤害类型为摔落,则source.isFall()返回值为true,免疫伤害
}


onAttacked
public void onAttacked(IToolStackView tool, int level, EquipmentContext context, EquipmentSlot slotType, DamageSource source, float amount, boolean isDirectDamage) {}
施加特殊效果穿戴者被攻击后(盔甲)对攻击者或穿戴者施加特殊效果(反伤、施加DEBUFF等)。

润湿”强化:

@Override
public void onAttacked(IToolStackView tool, int level, EquipmentContext context, EquipmentSlot slotType, DamageSource source, float amount, boolean isDirectDamage) {
  Entity attacker = source.getEntity();
  if (doesTrigger(source, isDirectDamage)) {
    // 25% chance of working per level, 50% per level on shields
    if (RANDOM.nextInt(slotType.getType() == Type.HAND ? 2 : 4) < level) {
      FluidStack fluid = getFluid(tool);
      if (!fluid.isEmpty()) {
        LivingEntity self = context.getEntity();
        Player player = self instanceof Player p ? p : null;
        SpillingFluid recipe = SpillingFluidManager.INSTANCE.find(fluid.getFluid());
        if (recipe.hasEffects()) {
          FluidStack remaining = recipe.applyEffects(fluid, level, createContext(self, player, attacker, fluid));
          if (player == null || !player.isCreative()) {
            setFluid(tool, remaining);
          }
        }
      }
    }
  }
}
attackWithArmor
public void attackWithArmor(IToolStackView tool, int level, EquipmentContext context, EquipmentSlot slotType, LivingEntity target, DamageSource source, float amount, boolean isDirectDamage) {}
施加特殊效果穿戴者主动攻击时(盔甲)对穿戴者(攻击者)或目标施加特殊效果(增伤、施加BUFF等)。

“惊险”强化(未知黏液头颅特性)

@Override
public void attackWithArmor(IToolStackView tool, int level, EquipmentContext context, EquipmentSlot slotType, LivingEntity target, DamageSource source, float amount, boolean isDirectDamage) {
  if (isDirectDamage && !source.isProjectile()) {
    LivingEntity attacker = context.getEntity();
    int attackerAir = attacker.getAirSupply();
    int maxAir = attacker.getMaxAirSupply();
    if (attackerAir < maxAir) {
      attacker.setAirSupply(Math.min(attackerAir + 60, maxAir));
    }
    target.setAirSupply(Math.max(-20, target.getAirSupply() - 60)); //将目标的空气转换为自身的空气
  }
}
onUnequip
public void onUnequip(IToolStackView tool, int level, EquipmentChangeContext context) {}
移除特殊效果卸下盔甲时移除盔甲穿戴时施加的特殊效果。

远视”强化:

@Override
public void onEquip(IToolStackView tool, int level, EquipmentChangeContext context) {
  if (!tool.isBroken()) {
    ResourceLocation key = SLOT_KEYS[context.getChangedSlot().getFilterFlag()];
    context.getTinkerData().ifPresent(data -> data.computeIfAbsent(TinkerDataKeys.FOV_MODIFIER).set(key, 1 / (1 + 0.05f * level))); //lambda表达式,移除对玩家视场角的更改
  }
}
onEquip
public void onEquip(IToolStackView tool, int level, EquipmentChangeContext context) {}
施加特殊效果装备盔甲时应用盔甲穿戴时施加的特殊效果。

金质”强化:

@Override
public void onEquip(IToolStackView tool, int level, EquipmentChangeContext context) {
  // adding a helmet? activate bonus
  if (context.getChangedSlot() == EquipmentSlot.HEAD) {
    context.getTinkerData().ifPresent(data -> { //lambda表达式
      GoldGuardGold gold = data.get(TOTAL_GOLD);
      if (gold == null) {
        data.computeIfAbsent(TOTAL_GOLD).initialize(context);
      } else {
        gold.setGold(EquipmentSlot.HEAD, tool.getVolatileData().getBoolean(ModifiableArmorItem.PIGLIN_NEUTRAL), context.getEntity()); //使猪灵中立
      }
    });
  }
}
onEquipmentChange用法同上



shouldDisplay
public boolean shouldDisplay(boolean advanced) {
  return true;
}
决定强化是否会在工具属性信息上显示无(相当于控制器)

若返回值为true,则会在所有界面上显示。

若为false,仅会在工匠站(砧)以及强化调配台上显示。

无需
getDamagePercentage
public double getDamagePercentage(IToolStackView tool, int level) {
  return Double.NaN;
}
决定工具耐久条上的显示内容

返回值为Double.NaN则为默认。

为0则不显示(相当于满耐久)。

其他值(0~1)则为损坏率,1为彻底损坏。

“耐久护盾”强化(是黏液覆层、石盾之类强化的API):

@Override
public double getDamagePercentage(IToolStackView tool, int level) {
  int shield = getShield(tool);
  if (shield > 0) {
    int cap = getShieldCapacity(tool, level);
    if (shield > cap) {
      return 0; //覆层为满,不显示
    }
    return ((double) (cap - shield) / cap); //覆层消耗,显示覆层余量
  }
  return Double.NaN; //双精度浮点,覆层耗尽,显示正常耐久条
}
showDurabilityBar
@Nullable
public Boolean showDurabilityBar(IToolStackView tool, int level) {
  return null;
}
决定是否要显示工具耐久条无(相当于控制器)

返回值为true则显示,为false则不显示。

黏液覆层”强化:

@Nullable
@Override

public Boolean showDurabilityBar(IToolStackView tool, int level) {
  // only show as fully repaired if overslime is full
  return getOverslime(tool) < getCapacity(tool); //布尔类型
}
getDurabilityRGB
public int getDurabilityRGB(IToolStackView tool, int level) {
  return -1;
}
决定工具耐久条颜色某些强化(不毁、黏液覆层)会改变工具的耐久条外观

不毁”强化:

@Override
public int getDurabilityRGB(IToolStackView tool, int level) {
  return 0xFFFFFF; //返回值为颜色代码,一般为16进制数,即RGB颜色
}
Json类强化代码会稍后讲解。

完成了强化代码后,就需要对强化进行注册。

创建一个新类,名字可以为“模组名”+ Modifiers。

这里需要用到类似Forge延迟注册器的强化注册器,代码如下:

public static ModifierDeferredRegister MODIFIERS = ModifierDeferredRegister.create(*.MODID);