我的世界Fabric mod开发-快速漏斗

news2025/1/11 20:06:55

前往我的主页以阅读完整内容,并获取源码
DearXuan的主页

MOD介绍

使用漏斗链进行分类或传递物品时,常常会发现漏斗速度太慢,难以收集全部掉落物.或者漏斗太多,影响性能.而现有的漏斗加速mod则是引入新的快速漏斗,存在各种兼容问题.开服时发现paper服务器可以修改原版漏斗速度,而因此萌生想法,编写一个可以修改原版漏斗速度的mod.

截至发布本文时,mod已拥有物品过滤功能,未来将会添加投掷器自动发射功能.

支持版本

本mod支持Minecraft 1.19.4版本,需要FabricFabric-api,以及前置mod: modmenu(≥6.2.1)

该mod仅限客户端使用.局域网联机时,仅房主需要安装,而其他成员无需安装,即使安装了也无法修改.

该MOD理论上适用于多个版本,可以在自己电脑上尝试修改版本号重新编译.

创建项目

本项目采用 IDEA 开发,在插件市场下载Minecraft Development,来快速初始化项目.

如果项目有大写字母,则创建完成后,需要前往src/main/resources/fabric.mod.json中修改id一项为小写,因为modid不支持大写字母.

创建项目

创建项目需要下载大量文件,需耐心等待.创建完成后,先彻底关闭IDEA,再重写打开项目.

开发思路

本mod的功能是修改原版漏斗代码,因此需要用到fabric提供的Mixin功能,该功能可将自己的代码注入到游戏源码中,而无需对其进行修改.

此外modmenu模组提供了可视化菜单,可以方便菜单制作,因而列为前置mod.实际上即使没有该前置也可以正常运行,但是无法打开菜单.

引入依赖

build.gradle中引入以下maven和依赖

repositories {
    // Add repositories to retrieve artifacts from in here.
    // You should only use this when depending on other mods because
    // Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
    // See https://docs.gradle.org/current/userguide/declaring_repositories.html
    // for more information about repositories.

    /**
     * EasyHopper 依赖项
     */
    maven { url "https://maven.shedaniel.me/" }
    maven { url "https://maven.terraformersmc.com/releases/" }
    maven { url "https://maven.architectury.dev/" }
    /**
     * EasyHopper 依赖项结束
     */
}

dependencies {
    // To change the versions see the gradle.properties file
    minecraft "com.mojang:minecraft:${project.minecraft_version}"
    mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
    modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"

    // Fabric API. This is technically optional, but you probably want it anyway.
    modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"

    /**
     * EasyHopper 依赖项
     */
    modApi("me.shedaniel.cloth:cloth-config-fabric:10.0.96") {
        exclude(group: "net.fabricmc.fabric-api")
    }
    modApi "com.terraformersmc:modmenu:6.2.1"
    /**
     * EasyHopper 依赖项结束
     */
}

此时右上角会提示重写构建,等待一段时间即可.

反编译源码

展开Gradle一栏,运行genSources,即可进行反编译.所需时间视电脑配置而定.

如果找不到这一栏,则可以在项目根目录下执行以下命令.

./gradlew genSources

如果失败则重启IDEA,多尝试几次.

反编译源码

Mixin配置文件

创建Mixin配置文件src/main/resources/modid.mixin.json,如图所示.注意要修改为你自己的modid

Mixin配置文件

修改fabric.mod.json,天上你的Mixin配置文件名称

"mixins": [
    "modid.mixins.json"
  ]

注册Mixin

以我的项目为例,modid为"EasyHopper",则在com.dearxuan.easyhopper下创建目录mixin,并在该目录下创建EasyHopperMixin.java

文件目录

修改冷却时间

由于我们要修改漏斗函数,因此我们直接搜索漏斗的英文,简单查看后,可以发现控制漏斗事件的类为HopperBlockEntity.class,因此我们注入这个类.

EasyHopperMixin.java中编写代码如下,注意修改为你的包名.

@Mixin(HopperBlockEntity.class)
public abstract class EasyHopperMixin extends LootableContainerBlockEntity {
    
}

其中@Mixin注释用于标识我们要注入的类.此时IDEA会将这段代码标红,因为缺少了构造函数,可以使用IDEA来自动创建.

现在这个类已经被成功注入,但我们还不知道漏斗的控制逻辑,因此无法进行编码.接下来查看HopperBlockEntity.class的源码(按住Ctrl+右键可以快速跳转),因为该游戏的事件都是基于刻(tick)来处理的,因此我们搜索"tick",仅有一个函数与此相符.为了便于分析,此处额外展示了两个用到的函数.

public static void serverTick(World world, BlockPos pos, BlockState state, HopperBlockEntity blockEntity) {
    --blockEntity.transferCooldown;
    blockEntity.lastTickTime = world.getTime();
    if (!blockEntity.needsCooldown()) {
        blockEntity.setTransferCooldown(0);
        HopperBlockEntity.insertAndExtract(world, pos, state, blockEntity, () -> HopperBlockEntity.extract(world, blockEntity));
    }
}

private boolean needsCooldown() {
    return this.transferCooldown > 0;
}

private void setTransferCooldown(int transferCooldown) {
    this.transferCooldown = transferCooldown;
}

于是我们可以猜测,该函数控制这漏斗何时输送物品,其中blockEntity.transferCooldown是冷却时间,每个tick会减一,而needsCooldown()函数根据blockEntity.transferCooldown是否为正来判断漏斗是否处于冷却中,为零或负则是冷却完毕.

一旦冷却完毕,则执行blockEntity.setTransferCooldown(0),推测是用于防止出现负数的.而HopperBlockEntity.insertAndExtract()函数显然是用来输入输出物品的.

因此我们只需要修改setTransferCooldown()这个函数,即可实现修改冷却时间的功能.

@Mixin(HopperBlockEntity.class)
public abstract class EasyHopperMixin extends LootableContainerBlockEntity {
    @Shadow
    private int transferCooldown;

    protected EasyHopperMixin(BlockEntityType<?> blockEntityType, BlockPos blockPos, BlockState blockState) {
        super(blockEntityType, blockPos, blockState);
    }

    @Inject(
            method = {"setTransferCooldown"},
            at = {@At("HEAD")},
            cancellable = true
    )
    private void EasyCooldown_head(int cooldown, CallbackInfo info){
        if(cooldown > 0){
            this.transferCooldown = cooldown - 8 + ModConfig.INSTANCE.TRANSFER_COOLDOWN;
            info.cancel();
        }
    }
}

由于transferCooldown是私有变量,无法直接访问,因此使用@Shadow来映射.这将把原私有变量映射到你自己创建的类中,以便开发者进行修改.

你会发现我在函数参数里加了CallbackInfo info一项,这是用来控制返回值的,我们需要利用它来实现中途退出.

现在开始修改setTransferCooldown()函数,这里我们选择@Inject注释,它可以将你的代码插入到原函数中,methed指原函数名,at指插入位置,为了屏蔽掉原函数,我们应该把代码插入到原函数头部,然后直接返回,以使后面部分失效.cancellable指原函数能否中途退出,显然需要为true.

下面代码中的cooldown - 8是为了减去原有的冷却时间,从而改成我们需要的值,ModConfig.INSTANCE.TRANSFER_COOLDOWN是指我们自己规定的冷却时间,该部分在配置文件中定义,会在接下来介绍.

最后的info.cancel()指取消原函数执行,相当于在我们代码的尾部,原函数头部插入了return,即屏取消了原函数的执行.

修改传输数量

上面已分析出HopperBlockEntity.insertAndExtract()是用于执行输入输出操作,因此我们继续查看源码.

private static boolean insertAndExtract(World world, BlockPos pos, BlockState state, HopperBlockEntity blockEntity, BooleanSupplier booleanSupplier) {
    if (world.isClient) {
        return false;
    }
    if (!blockEntity.needsCooldown() && state.get(HopperBlock.ENABLED).booleanValue()) {
        boolean bl = false;
        if (!blockEntity.isEmpty()) {
            bl = HopperBlockEntity.insert(world, pos, state, blockEntity);
        }
        if (!blockEntity.isFull()) {
            bl |= booleanSupplier.getAsBoolean();
        }
        if (bl) {
            blockEntity.setTransferCooldown(8);
            HopperBlockEntity.markDirty(world, pos, state);
            return true;
        }
    }
    return false;
}

先猜测代码含义,显然第一步是判断自己是客户端还是服务端,该函数仅在服务端执行.然后检查漏斗是否在冷却中或不可用.

boolean bl = false则定义了一个布尔变量,用于判断该漏斗是否使用过,如果为true,则已经使用,那么就需要blockEntity.setTransferCooldown(8)来重新设置冷却时间.而blockEntity.isEmpty()blockEntity.isFull()显然是用来检查容器中是否有物品的,即输入输出功能.因此我们只需要在这里加个循环即可实现任意数量物品的输入输出.

我们的思路仍然是在原函数头部插入自己的代码,注意到其中用到了四个private函数,因此我们需要先提取出这四个函数

@Mixin(HopperBlockEntity.class)
interface IEasyHopperEntity {

    @Invoker("needsCooldown")
    public boolean Invoke_needsCooldown();

    @Invoker("isFull")
    public boolean Invoke_isFull();

    @Invoker("setTransferCooldown")
    public void Invoke_setTransferCooldown(int transferCooldown);

    @Invoker("insert")
    public static boolean Invoke_insert(World world, BlockPos pos, BlockState state, Inventory inventory){
        return false;
    };
}

@Invoker允许你访问一个私有的方法,类似@Shadow,它可以将原本不可见的方法映射为你新定义的方法.其中静态函数必须要有函数体,不过它永远也不会执行.

编写的函数如下

@Inject(
            method = {"insertAndExtract"},
            at = {@At("HEAD")},
            cancellable = true
    )
private static void Insert(
        World world,
        BlockPos pos,
        BlockState state,
        HopperBlockEntity blockEntity,
        BooleanSupplier booleanSupplier,
        CallbackInfoReturnable<Boolean> info){
    if (world.isClient) {
        info.setReturnValue(false);
    }
    if (!((IEasyHopperEntity) blockEntity).Invoke_needsCooldown() && state.get(HopperBlock.ENABLED).booleanValue()) {
        boolean bl = false;
        for(int i=0;i<ModConfig.INSTANCE.TRANSFER_OUTPUT_COUNT;i++){
            if (!blockEntity.isEmpty()) {
                bl = IEasyHopperEntity.Invoke_insert(world, pos, state, blockEntity);
            }
        }
        for(int i=0;i<ModConfig.INSTANCE.TRANSFER_INPUT_COUNT;i++){
            if (!((IEasyHopperEntity) blockEntity).Invoke_isFull()) {
                bl |= booleanSupplier.getAsBoolean();
            }
        }
        if (bl) {
            ((IEasyHopperEntity) blockEntity).Invoke_setTransferCooldown(8);
            HopperBlockEntity.markDirty(world, pos, state);
            info.setReturnValue(true);
        }
    }
    info.setReturnValue(false);
}

由于原函数有返回值,因此末尾需要改用CallbackInfoReturnable<Boolean> info,将原本的return true改为info.setReturnValue(true),即可实现中途退出的功能.其他代码直接复制下来即可,部分私有函数需要转换成自己刚刚定义的接口来调用.

最后,我们在easyhopper.mixin.json中注册上面的两个Mixin

{
  "required": true,
  "minVersion": "0.8",
  "package": "com.dearxuan.easyhopper.mixin",
  "compatibilityLevel": "JAVA_17",
  "mixins": [
    "EasyHopperMixin",
    "IEasyHopperEntity"
  ],
  "client": [
  ],
  "injectors": {
    "defaultRequire": 1
  }
}

完整的EasyHopperMixin.java代码可在我的主页查看

保存配置

创建Config目录和其中的两个java文件,如下图所示

文件目录

ModConfig编写如下代码

@Config(
        name = "easyhopper"
)
public class ModConfig implements ConfigData {
    @Excluded
    public static ModConfig INSTANCE;

    @Comment("控制漏斗输送物品冷却时间")
    public int TRANSFER_COOLDOWN = 8;

    @Comment("控制漏斗每次输入多少个物品")
    public int TRANSFER_INPUT_COUNT = 1;

    @Comment("控制漏斗每次输出多少个物品")
    public int TRANSFER_OUTPUT_COUNT = 1;

    public ModConfig(){

    }

    public static void init(){
        AutoConfig.register(ModConfig.class, GsonConfigSerializer::new);
        INSTANCE = (ModConfig) AutoConfig
                .getConfigHolder(ModConfig.class)
                .getConfig();
    }
}

@Config表面这个类是一个配置类,它将会被序列化后保存在游戏目录的config文件夹下.

@Excluded注释表面该字段不会被保存,@Comment则是注释,在设置界面,当鼠标悬浮于某一项上方时显示.init()函数用于注册和初始化这个类.在EasyHopper.java中执行这个函数

public class EasyHopper implements ModInitializer {

    @Override
    public void onInitialize() {
        ModConfig.init();
    }
}

ModMenu.java中编写如下代码

package com.dearxuan.easyhopper.Config;

import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import me.shedaniel.autoconfig.AutoConfig;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;

@Environment(EnvType.CLIENT)
public class ModMenu implements ModMenuApi {

    @Override
    public ConfigScreenFactory<?> getModConfigScreenFactory(){
        return parent -> AutoConfig
                .getConfigScreen(ModConfig.class, parent)
                .get();
    }
}

此代码将会为该配置类自动生成一个图形界面,便于用户可视化修改.只有安装了前置模组modmenu才会看到设置按钮,同时许多模组也使用了该前置mod.即使不安装,也可以正常运行,但是修改配置较为麻烦,因此列入到前置模组名单,来强制用户安装.同时还要添加modmenu的入口点.修改fabric.mod.json如下

{
  //***//
  "entrypoints": {
    "client": [
      "com.dearxuan.easyhopper.client.EasyHopperClient"
    ],
    "main": [
      "com.dearxuan.easyhopper.EasyHopper"
    ],
    "modmenu": [
      "com.dearxuan.easyhopper.Config.ModMenu"
    ]
  },
  "mixins": [
    "easyhopper.mixins.json"
  ],
  "depends": {
    "fabricloader": ">=${loader_version}",
    "fabric": "*",
    "fabric-api": "*",
    "minecraft": "${minecraft_version}",
    "modmenu": ">=6.2.1"
  }
}

至此mod开发完毕.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/547920.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为OD机试真题 Java 实现【区间连接器】【2023Q1 200分】

一、题目描述 有一组区间 [a0, b0], [a1, b1], … (a, b 表示起点, 终点)&#xff0c;区间有可能重叠、相邻&#xff0c;重叠或相邻则可以合并为更大的区间&#xff1b; 给定一组连接器[x1, x2, x3, …]&#xff08;x 表示连接器的最大可连接长度&#xff0c;即 x>gap&…

支付宝沙箱支付(java电脑版)

目录 下载支付demo配置环境AlipayConfig 下载支付demo 网址&#xff1a;https://open.alipay.com/ 下载并打开项目发现无法运行&#xff1a; 手动转化项目&#xff1a; 等待下载整理一下maven pom 通过tomat部署运行测试。 导入阿里支付的pom依赖 <dependency> &l…

都2023了,你竟然还不知道网络安全该怎么学

前言 网络安全是指网络系统的硬件、软件及其系统中的数据受到保护&#xff0c;不因偶然或恶意原因而遭受破坏、更改、泄露&#xff0c;系统连续可靠正常地运行&#xff0c;网络服务不中断。网络安全因何而重要&#xff1f; 截至2023年4月,我国网民规模为_11.51亿_&#xff0c…

模板初阶(泛型编程)

模板初阶 &#x1f506;泛型编程&#x1f506;函数模板函数模板概念函数模板格式函数模板的原理函数模板的实例化模板参数的匹配原则 &#x1f506;类模板类模板的定义格式类模板的实例化类模板与模板类的区别 &#x1f506;结语 &#x1f506;泛型编程 泛型编程&#xff1a;编…

【总结】Numpy2

Numpy 1. 数组和数的运算 array1 np.arange(1,10) array1 # array([1, 2, 3, 4, 5, 6, 7, 8, 9]) array1 10 # array([11, 12, 13, 14, 15, 16, 17, 18, 19]) array1 - 10 # array([-9, -8, -7, -6, -5, -4, -3, -2, -1]) array1 * 10 # array([10, 20, 30, 40, 50, 60, 70…

Flutter:如何在Android中实现串口通信调试

本文介绍如何通过flutter_libserialport插件在Flutter中实现串口通信调试。 1、引入依赖 在flutter工程的pubspec.yaml文件中引入flutter_libserialport依赖&#xff1a; dependencies:flutter_libserialport: ^0.3.0 2、导入import依赖包 在dart代码中import导入flutter_li…

快手国际化 后端开发面经二面

目录 1.Redis用的什么数据类型2.Hash底层结构3.JVM垃圾判别阶段算法4.MySQL索引模型5.为什么用B树6.联合索引在B树如何构造的7.覆盖索引知道吗 1.Redis用的什么数据类型 1.String(字符类型) 2.Hash(散列类型) 3.List(列表类型) 4.Set(集合类型) 5.SortedSet(有序集合类型&…

设计模式 单例模式(创建型)

一、前言 学习设计模式我们关注的是什么&#xff0c;如何实现么&#xff1f;是也不是。我认为比了解如何实现设计模式更重要的是这些设计模式的应用场景&#xff0c;什么场景下我们该用这种设计模式&#xff1b;以及这些设计模式所包含的思想&#xff0c;最终帮助我们把代码写…

继承 + 多态 + final + 权限修饰符

目录 继承 多态 final 权限修饰符 继承 继承定义&#xff1a; 可以让类跟类之间产生子父的关系继承的好处 可以把多个子类中重复的代码抽取到父类中&#xff0c;子类可以直接使用&#xff0c;减少代码几余&#xff0c;提高代码的复用性子类继承内容 非私有private构造方法…

#机器学习--深度学习中的正则化

#机器学习--深度学习中的正则化 引言1、参数范数惩罚2、 L 2 L^{2} L2 正则化3、 L 1 L^{1} L1 正则化4、显式约束和重投影5、参数绑定和参数共享6、Bagging7、Dropout 引言 本系列博客旨在为机器学习(深度学习)提供数学理论基础。因此内容更为精简&#xff0c;适合二次学习的…

uniapp实现条码扫描 可开闪光灯,原生H5调用,不需要任何sdk。

主要思路 使用QuaggaJs这个库。调用摄像头使用的 navigator.mediaDevices.getUserMedia 这个H5的api。通过 video 和 canvas 把摄像头获取到的数据展现到页面上&#xff0c;同时调用监听Quagga解析。 获取设备摄像头权限,用于后续开启摄像头。创建video元素显示摄像头画面,和ca…

AcWing算法提高课-1.3.10混合背包问题

宣传一下算法提高课整理 <— CSDN个人主页&#xff1a;更好的阅读体验 <— 本题链接&#xff08;AcWing&#xff09; 点这里 题目描述 有 N N N 种物品和一个容量是 V V V 的背包。 物品一共有三类&#xff1a; 第一类物品只能用1次&#xff08;01背包&#xff0…

opencv相机标定

当你把摄像机放在一个特定的位置&#xff0c;在它的后面放一个目标图像&#xff0c;或者是把摄像机放到某个物体上&#xff0c;摄像机周围的物体是什么形状&#xff0c;你需要知道这些信息。 当你在计算机上处理图像时&#xff0c;会使用以下三个参数&#xff1a; 1.像素坐标&a…

软件测试5年了,薪资25K,我还怕00后卷吗?

前言 沅哥在这个行业爬摸滚打5年了&#xff0c;从最开始点点点的功能测试到现在到现在成为高级测试&#xff0c;工资也翻了几倍&#xff0c;到了25k。他和我说&#xff0c;最开始他是想躺平的&#xff0c;后来也是被迫卷的&#xff0c;但好在这个结果他很满意。 之所以改变的…

一文3000字从0到1使用Selenium进行自动化测试

对于很多刚入门的测试新手来说&#xff0c;大家都将自动化测试作为自己职业发展的一个主要阶段。可是&#xff0c;在成为一名合格的自动化测试工程师之前&#xff0c;我们不仅要掌握相应的理论知识&#xff0c;还要进行大量的实践&#xff0c;积累足够的经验&#xff0c;以便快…

【重新定义matlab强大系列七】利用matlab函数ischange查找数据变化点

&#x1f517; 运行环境&#xff1a;matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 #### 防伪水印——左手の明天 #### &#x1f497; 大家好&#x1f917;&#x1f91…

为了入职字节测试岗准备半年,上岸拿个30K应该不算高吧?

历时近半年的面试终于告一段落&#xff0c;终于有时间整理下自己这次的换工作心得&#xff0c;分享给大家。 面试经历 我的基本情况是这样&#xff1a;中下等 985 学校本科毕业&#xff0c;非计算机专业&#xff0c;在北京工作五年&#xff0c;之前一直在中小私企、国企工作。…

程序语言易错题

程序语言易错题 包含8个成员的开发小组的沟通路径最多有&#xff08;&#xff09;条。 A、28 B、32 C、56 D、64 解析 软件开发小组的沟通路径受到小组组织形式和规模的影响。若任意小组成员之间均可能有沟通路径&#xff0c;则可用完全连通图来对开发小组的沟通路径建模&#…

Anaconda-labelimg的使用

文章目录 一、创建Anaco的虚拟环境并激活二、下载labelimg以及使用1.下载2.使用 在这里我是创建了一个虚拟环境&#xff0c;虚拟环境名字为labelimg 之后将labelimg下载到该虚拟环境中 一、创建Anaco的虚拟环境并激活 conda create -n labelimg conda activate labelimg二、下…

Smoothieware_best-for-pnp 工具链的升级尝试

文章目录 Smoothieware_best-for-pnp 工具链的升级尝试概述实验工具链安装的思路更换工具链的工作备注END Smoothieware_best-for-pnp 工具链的升级尝试 概述 正在迁移Smoothieware_best-for-pnp到MCUXPresso的失败实验中徘徊. 现在已知2者的工具链版本是不一样的. 通过2进制…