提示
上期模组开发教程:1.18模组开发。
此教程中的部分功能实现使用了 WinAPI,在非 Windows 系统上运行时可能会出现报错。
ModuleManager实现
基础文件实现
创建类文件 ModuleManager.java 和 Module.java ,并在 ModuleManager.java 类文件中再新建一个类,并将上期实现的功能移动到新建的
类里:
ModuleManager.java
package cn.ksmcbrigade.em;
import java.awt.event.KeyEvent;
public class ModuleManager {
static class modulesClass {
public static Module NoFall = new Module("NoFall", KeyEvent.VK_N);
}
}
Module.java
package cn.ksmcbrigade.em;
public class Module {
public final String name;
public final int key;
public boolean enabled = false;
public Module(String name, int key){
this.name = name;
this.key = key;
}
public void disabled() throws Exception{
}
public void update() throws Exception{
}
public void render(PoseStack matrixStack) throws Exception{
}
public void keyInput(int key) throws Exception{
}
public void enabled() throws Exception{
}
public void set(boolean enabled) throws Exception {
this.enabled = enabled;
if(enabled){
enabled();
}
else{
disabled();
}
}
}
上期功能迁移
然后新建一个目录 modules,并在 modules 目录中创建一个继承自 Module 类的类文件 NoFall.java,将上期写好的功能移动到 NoFall.java 中,然后将 ModuleManager.modules 中的 NoFall 改为新建的 NoFall.java:
NoFall.java
package cn.ksmcbrigade.em.modules;
import cn.ksmcbrigade.em.Module;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
public class NoFall extends Module {
public NoFall(String name, int key) {
super(name, key);
}
@Override
public void update() throws Exception {
Minecraft MC = Minecraft.getInstance();
if(MC.player != null && MC.player.fallDistance>=3){
ClientPacketListener connection = MC.getConnection();
if(connection!=null){
connection.getConnection().send(new ServerboundMovePlayerPacket.StatusOnly(true));
}
}
}
}
然后在 ModuleManager 中添加获取所有 modules 的函数,并在主类调用:
public static ArrayList<Module> modules;
public static void getModules() throws IllegalAccessException {
modules = new ArrayList<>();
for(Field field : modulesClass.class.getDeclaredFields()){
modules.add((Module) field.get(null));
}
}
接下来依次注册 PlayerTickEvent(上期已注册),KeyInputEvent,RenderEvent,并在事件中判断是否启用,若启用则调用相关函数:
ExampleMod.java
package cn.ksmcbrigade.em;
import com.sun.jna.platform.KeyboardUtils;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@Mod("em")
@Mod.EventBusSubscriber
public class ExampleMod {
private static Logger LOGGER = LogManager.getLogger();
public ExampleMod() throws IllegalAccessException {
MinecraftForge.EVENT_BUS.register(this);
ModuleManager.getModules();
if(Boolean.parseBoolean(System.getProperty("java.awt.headless"))){
System.setProperty("java.awt.headless","false");
}
}
@SubscribeEvent
public void PlayerTick(TickEvent.PlayerTickEvent event) throws Exception {
if(ModuleManager.modules!=null){
for(Module module: ModuleManager.modules.stream().filter(module -> module.enabled).toList()){
module.update();
}
}
}
@SubscribeEvent
@OnlyIn(Dist.CLIENT)
public void RenderTick(RenderGameOverlayEvent.Pre event) throws Exception {
if(event.getType()==RenderGameOverlayEvent.ElementType.ALL && ModuleManager.modules!=null){
for(Module module: ModuleManager.modules.stream().filter(module -> module.enabled).toList()){
module.render(event.getMatrixStack());
}
}
}
@SubscribeEvent
public void keyInputEvent(InputEvent.KeyInputEvent event) throws Exception {
if(ModuleManager.modules!=null){
for(Module module : ModuleManager.modules){
if(module.key!=-1){
int key = event.getKey();
if(module.enabled){
module.keyInput(key);
}
if(KeyboardUtils.isPressed(module.key)){
module.set(!module.enabled);
if (Minecraft.getInstance().player != null) {
Minecraft.getInstance().player.sendMessage(Component.nullToEmpty(module.name+": "+module.enabled),Minecraft.getInstance().player.getUUID());
}
}
}
}
}
}
}
配置文件
首先在主类中创建一个 init 函数,函数判断配置文件是否存在,若不存在则创建,并遍历配置文件的所有项名称,将模块名称与项名称对应的模块启用,然后在主类中调用。
ExampleMod.java
package cn.ksmcbrigade.em;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.sun.jna.platform.KeyboardUtils;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@Mod("em")
@Mod.EventBusSubscriber
public class ExampleMod {
public static Logger LOGGER = LogManager.getLogger();
public static File file = new File("config/em-config.json");
public ExampleMod() throws IllegalAccessException, IOException {
MinecraftForge.EVENT_BUS.register(this);
ModuleManager.getModules();
init();
}
public void init() throws IOException{
if(!file.exists()){
Files.write(file.toPath(),"{}".getBytes());
}
JsonObject json = JsonParser.parseString(Files.readString(file.toPath())).getAsJsonObject();
if(ModuleManager.modules!=null){
for(String name:json.keySet()){
ModuleManager.modules.stream().filter(module -> module.name.equals(name)).forEach(module -> {
module.enabled = true;
});
}
}
if(Boolean.parseBoolean(System.getProperty("java.awt.headless"))){
System.setProperty("java.awt.headless","false");
}
LOGGER.info("ExampleMod is done.");
}
@SubscribeEvent
public void PlayerTick(TickEvent.PlayerTickEvent event) throws Exception {
if(ModuleManager.modules!=null){
for(Module module: ModuleManager.modules.stream().filter(module -> module.enabled).toList()){
module.update();
}
}
}
@SubscribeEvent
@OnlyIn(Dist.CLIENT)
public void RenderTick(RenderGameOverlayEvent.Pre event) throws Exception {
if(event.getType()==RenderGameOverlayEvent.ElementType.ALL && ModuleManager.modules!=null){
for(Module module: ModuleManager.modules.stream().filter(module -> module.enabled).toList()){
module.render(event.getMatrixStack());
}
}
}
@SubscribeEvent
public void keyInputEvent(InputEvent.KeyInputEvent event) throws Exception {
if(ModuleManager.modules!=null){
for(Module module : ModuleManager.modules){
if(module.key!=-1){
int key = event.getKey();
if(module.enabled){
module.keyInput(key);
}
if(KeyboardUtils.isPressed(module.key)){
module.set(!module.enabled);
if (Minecraft.getInstance().player != null) {
Minecraft.getInstance().player.sendMessage(Component.nullToEmpty(module.name+": "+module.enabled),Minecraft.getInstance().player.getUUID());
}
}
}
}
}
}
}
接下来再将 Module.java 中的 set 函数进行修改,通过判断是否启用来修改配置文件:
Module.java set函数
public void set(boolean enabled) throws Exception {
this.enabled = enabled;
JsonObject json = JsonParser.parseString(Files.readString(ExampleMod.file.toPath())).getAsJsonObject();
if(enabled){
enabled();
json.add(this.name,null);
}
else{
disabled();
try {
json.remove(this.name);
}
catch (Exception e){
ExampleMod.LOGGER.error("Failed to remove the module enabled in the config file: "+this.name);
}
}
Files.writeString(ExampleMod.file.toPath(),json.toString());
}
简易功能实现-XYZ
功能名称解释
此功能将会在屏幕上显示当前坐标,当按住 shift 键(下蹲键)时隐藏。
实现
首先在 modules 目录中新建类文件 XYZ.java,然后继承 Module.java,然后覆写 render 函数,在函数内判断下蹲键是否没有被按下,若没有则在屏幕的左上角(x: 0,y: 2)显示当前玩家坐标,保留三位小数,并写入到 ModuleManager.modulesClass 类中。
XYZ.java
package cn.ksmcbrigade.em.modules;
import cn.ksmcbrigade.em.Module;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.world.phys.Vec3;
import java.awt.*;
public class XYZ extends Module {
public XYZ(String name, int key) {
super(name, key);
}
@Override
public void render(PoseStack matrixStack) throws Exception {
Minecraft MC = Minecraft.getInstance();
if(!MC.options.keyShift.isDown() && MC.player!=null){
Vec3 pos = MC.player.position();
MC.font.drawShadow(matrixStack,"XYZ: "+Math.round(pos.x*1000.0D)/1000.0D+", "+Math.round(pos.y*1000.0D)/1000.0D+", "+Math.round(pos.z*1000.0D)/1000.0D,0,2, Color.WHITE.getRGB());
}
}
}
ModuleManager.modulesClass
static class modulesClass {
public static Module NoFall = new NoFall("NoFall", KeyEvent.VK_N);
public static Module XYZ = new XYZ("XYZ", KeyEvent.VK_B);
}
最后运行测试即可。
完整代码
ExampleMod.java
package cn.ksmcbrigade.em;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.sun.jna.platform.KeyboardUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@Mod("em")
@Mod.EventBusSubscriber
public class ExampleMod {
public static Logger LOGGER = LogManager.getLogger();
public static File file = new File("config/em-config.json");
public ExampleMod() throws IllegalAccessException, IOException {
MinecraftForge.EVENT_BUS.register(this);
ModuleManager.getModules();
init();
}
public void init() throws IOException{
if(!file.exists()){
Files.write(file.toPath(),"{}".getBytes());
}
JsonObject json = JsonParser.parseString(Files.readString(file.toPath())).getAsJsonObject();
if(ModuleManager.modules!=null){
for(String name:json.keySet()){
ModuleManager.modules.stream().filter(module -> module.name.equals(name)).forEach(module -> {
module.enabled = true;
});
}
}
if(Boolean.parseBoolean(System.getProperty("java.awt.headless"))){
System.setProperty("java.awt.headless","false");
}
LOGGER.info("ExampleMod is done.");
}
@SubscribeEvent
public void PlayerTick(TickEvent.PlayerTickEvent event) throws Exception {
if(ModuleManager.modules!=null){
for(Module module: ModuleManager.modules.stream().filter(module -> module.enabled).toList()){
module.update();
}
}
}
@SubscribeEvent(priority = EventPriority.NORMAL)
@OnlyIn(Dist.CLIENT)
public void RenderTick(RenderGameOverlayEvent.Pre event) throws Exception {
if(event.getType()==RenderGameOverlayEvent.ElementType.ALL && ModuleManager.modules!=null){
for(Module module: ModuleManager.modules.stream().filter(module -> module.enabled).toList()){
module.render(event.getMatrixStack());
}
}
}
@SubscribeEvent
public void keyInputEvent(InputEvent.KeyInputEvent event) throws Exception {
if(ModuleManager.modules!=null){
for(Module module : ModuleManager.modules){
if(module.key!=-1){
int key = event.getKey();
if(module.enabled){
module.keyInput(key);
}
if(KeyboardUtils.isPressed(module.key)){
module.set(!module.enabled);
if (Minecraft.getInstance().player != null) {
Minecraft.getInstance().player.sendMessage(Component.nullToEmpty(module.name+": "+module.enabled),Minecraft.getInstance().player.getUUID());
}
}
}
}
}
}
}
ModuleManager.java
package cn.ksmcbrigade.em;
import cn.ksmcbrigade.em.modules.NoFall;
import cn.ksmcbrigade.em.modules.XYZ;
import java.awt.event.KeyEvent;
import java.lang.reflect.Field;
import java.util.ArrayList;
public class ModuleManager {
public static ArrayList<Module> modules;
public static void getModules() throws IllegalAccessException {
modules = new ArrayList<>();
for(Field field : modulesClass.class.getDeclaredFields()){
modules.add((Module) field.get(null));
}
}
static class modulesClass {
public static Module NoFall = new NoFall("NoFall", KeyEvent.VK_N);
public static Module XYZ = new XYZ("XYZ", KeyEvent.VK_B);
}
}
Module.java
package cn.ksmcbrigade.em;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.mojang.blaze3d.vertex.PoseStack;
import java.nio.file.Files;
public class Module {
public final String name;
public final int key;
public boolean enabled = false;
public Module(String name, int key){
this.name = name;
this.key = key;
}
public void disabled() throws Exception{
}
public void update() throws Exception{
}
public void render(PoseStack matrixStack) throws Exception{
}
public void keyInput(int key) throws Exception{
}
public void enabled() throws Exception{
}
public void set(boolean enabled) throws Exception {
this.enabled = enabled;
JsonObject json = JsonParser.parseString(Files.readString(ExampleMod.file.toPath())).getAsJsonObject();
if(enabled){
enabled();
json.add(this.name,null);
}
else{
disabled();
try {
json.remove(this.name);
}
catch (Exception e){
ExampleMod.LOGGER.error("Failed to remove the module enabled in the config file: "+this.name);
}
}
Files.writeString(ExampleMod.file.toPath(),json.toString());
}
}
NoFall.java
package cn.ksmcbrigade.em.modules;
import cn.ksmcbrigade.em.Module;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
public class NoFall extends Module {
public NoFall(String name, int key) {
super(name, key);
}
@Override
public void update() throws Exception {
Minecraft MC = Minecraft.getInstance();
if (MC.player != null && MC.player.fallDistance >= 3) {
ClientPacketListener connection = MC.getConnection();
if (connection != null) {
connection.getConnection().send(new ServerboundMovePlayerPacket.StatusOnly(true));
}
}
}
}
XYZ.java
package cn.ksmcbrigade.em.modules;
import cn.ksmcbrigade.em.Module;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.world.phys.Vec3;
import java.awt.*;
public class XYZ extends Module {
public XYZ(String name, int key) {
super(name, key);
}
@Override
public void render(PoseStack matrixStack) throws Exception {
Minecraft MC = Minecraft.getInstance();
if(!MC.options.keyShift.isDown() && MC.player!=null){
Vec3 pos = MC.player.position();
MC.font.drawShadow(matrixStack,"XYZ: "+Math.round(pos.x*1000.0D)/1000.0D+", "+Math.round(pos.y*1000.0D)/1000.0D+", "+Math.round(pos.z*1000.0D)/1000.0D,0,2, Color.WHITE.getRGB());
}
}
}