我的世界Fabric mod开发-快速漏斗
前往我的主页以阅读完整内容,并获取源码
DearXuan的主页
MOD介绍
使用漏斗链进行分类或传递物品时,常常会发现漏斗速度太慢,难以收集全部掉落物.或者漏斗太多,影响性能.而现有的漏斗加速mod则是引入新的快速漏斗,存在各种兼容问题.开服时发现paper服务器可以修改原版漏斗速度,而因此萌生想法,编写一个可以修改原版漏斗速度的mod.
截至发布本文时,mod已拥有物品过滤功能,未来将会添加投掷器自动发射功能.
支持版本
本mod支持Minecraft 1.19.4版本,需要Fabric及Fabric-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 fileminecraft "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

修改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 {@Shadowprivate 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 {@Excludedpublic 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 {@Overridepublic 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 {@Overridepublic 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开发完毕.
相关文章:
我的世界Fabric mod开发-快速漏斗
前往我的主页以阅读完整内容,并获取源码 DearXuan的主页 MOD介绍 使用漏斗链进行分类或传递物品时,常常会发现漏斗速度太慢,难以收集全部掉落物.或者漏斗太多,影响性能.而现有的漏斗加速mod则是引入新的快速漏斗,存在各种兼容问题.开服时发现paper服务器可以修改原…...
AI“应用商店”来了!OpenAI首批70个ChatGPT Plugin最全梳理
OpenAI放出大招,本周将向所有ChatGPT Plus用户开放联网功能和众多插件本周将向所有ChatGPT Plus用户开放联网功能和众多插件,允许ChatGPT访问互联网并使用70个第三方插件。 本批第三方插件能够全方位覆盖衣食住行、社交、工作以及学习等日常所需&#x…...
NSS LitCTF部分wp
web 1、PHP是世界上最好的语言!! 直接cat flag flagNSSCTF{11eaebe0-3764-410d-be83-b23532a24235} 2、这是什么?SQL !注一下 ! 直接查询,发现注入点是id 使用sqlmap列出所以数据库 sqlmap -u "h…...
【开发者指南】如何在MyEclipse中编辑HTML或JSP文件?(一)
MyEclipse v2022.1.0正式版下载 如果您有HTML或JSP文件要编辑,这里将介绍如何编辑。查找以下信息: 编辑源代码大纲和属性视图参数页面 该功能在MyEclipse中是可用的。 一、HTML / JSP编辑器 要编辑HTML或JSP文件,请执行以下操作当中的一…...
关于博客停更的原因
进入我的主页浏览一下,就可以发现我写到linux就不写了,c后面页不写了,题解也不更新了,确实,我承认我懒惰了,我选择了向后学习,而不是总结,写博客是一个良好的习惯和面试官看到的能够…...
智能感知编码优化与落地实践
作者 | XHF 导读 基于人眼视觉特性出发的感知编码优化技术,成为互联网短视频、OTT 等 UGC 场景的重点优化手段,可以在降低视频码率的同时,提升视频的观看体验。 今天主要有 4 个方面的内容。首先给大家介绍一下感知编码的技术背景;…...
OpenCL编程指南-5.1工作项函数-整数函数-公共函数
工作项函数 应用程序使用clEnqueueNDRangeKernel和 clEnqueueTask API将OpenCL中的数据并行和任务并行内核排队。对于一个数据并行内核(使用clEnqueueNDRangeKernel排队等待执行),应用程序会指定全局工作大小,即可以并行执行这个内核的工作项…...
教你接入Midjourney,不用梯子也能玩
1、效果 话不多说,先上最终出图效果, 我给的关键词是一只白色的猫 2、接入流程 API文档可以来这里查(可以白嫖100次midjourney出图和10次gpt4体验),我这里精简一下接入流程,方便大家快速接入 2.1、文字生…...
Mysql中常用到的查询关键字
文章目录 1、join2、like 模糊查询3、or4、distinct5、in 包含6、group by 分组7、order by8、limit 1、join MySQL 的连接主要分为内连接和外连接。 什么是内连接: 取得两张表中满足存在连接匹配关系的记录。 什么是外连接: 不只取得两张表中满足存在…...
【ROS】ROS1工具详解
1、roscore 1.1 说明 运行roscore,将会启动三个功能:ROS Master主节点、ROS参数服务器和记录ROS日志输出节点 1.2 用法 roscore [可选参数]1.3 参数详解 -h, --help,帮助信息 -p PORT, --portPORT,指定端口号,默认…...
论Plant Simulation中的Init的使用及调用顺序
往期内容回顾: 一文搞懂Plant Simulation中的Rotation设置 Plant Simulation与python之Socket通信的数据交互问题 自主移动机器人模型制作 写在开头 在阅读之前,可以先尝试回答一下如下问题,如果都能答得上来,这篇文章就可以忽略不看了。 Q1:对于主模型中包括多…...
nginx实现正向代理
1.下载nginx nginx: download 选择自己需要的版版本下载下来 2.解压文件修改ngixn.conf配置文件 events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout…...
【spark】
实验5 Spark Structured Streaming编程实践 实验内容和要求 0.结构化流练习任务 0.1 讲义文件源-json数据任务。按照讲义中json数据的生成及分析,复现实验,并适当分析。 (1)创建程序生成JSON格式的File源测试数据 import osimp…...
ADO.NET 面试题
这里写自定义目录标题 什么是 ADO.NET?ADO.NET 的主要特点有哪些?ADO.NET 的四个组件分别是什么?什么是 Connection 串?Connection 的状态有哪些?什么是 DataAdapter?DataAdapter 的作用是什么?…...
第三篇、基于Arduino uno,用oled0.96寸屏幕显示dht11温湿度传感器的温度和湿度信息——结果导向
0、结果 说明:先来看看拍摄的显示结果,如果是你想要的,可以接着往下看。 1、外观 说明:本次使用的oled是0.96寸的,别的规格的屏幕不一定适用本教程,一般而言有显示白色、蓝色和蓝黄一起显示的࿰…...
什么是npu算力盒子,算力是越大越好吗?
一、什么是npu算力盒子?该怎么选? NPU(神经处理单元)算力盒子是一种专门用于进行人工智能计算的硬件设备,其中集成了高性能的NPU芯片。NPU是一种针对深度学习任务进行优化的处理器,具备高度并行计算和低功…...
后端返回文件流时,前端如何处理并成功下载流文件以及解决下载后打开显示不支持此文件格式
一、文件和流的关系 文件(File)和流(Stream)是既有区别又有联系的两个概念。 文件 是计算机管理数据的基本单位,同时也是应用程序保存和读取数据的一个重要场所。 存储介质:文件是指在各种存储介质上(如硬盘、可…...
Ansible的脚本-playbook 剧本
目录 1.剧本(playbook) 1.playbook介绍 2. playbooks 的组成 3.案例:编写httpd的playbook 4.定义、引用变量 5.指定远程主机sudo切换用户 6.when条件判断 7.迭代 2.playbook的模块 1.Templates 模块 2.tags 模块 3.Roles 模块 1.…...
python lambda表达式表达式详解及应用
目录 Python Lambda表达式的优势 Lambda表达式用法 1. 当作参数传递 2. 使用Lambda表达式过滤列表 3. 使用Lambda表达式计算数学表达式 4. 使用Lambda表达式作为返回值 5. 实现匿名回调函数 Lambda表达式注意事项 总结 Lambda表达式是Python中的一种匿名函数ÿ…...
Windows 10计算机性能优化:让你的电脑更流畅
Windows 10是目前最流行的操作系统之一,但在长期使用过程中,可能会出现一些性能方面的问题。本文将为你介绍如何选择合适的Windows 10版本,并提供一些优化技巧,使你的电脑性能更加流畅。此外,还将特别关注游戏用户和工…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
