提示

  1. 上期模组开发教程:1.18模组开发

  2. 此教程中的部分功能实现使用了 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);
}

最后运行测试即可。

1.18模组开发之ModuleManager与配置文件及简易功能实现-第1张图片

完整代码

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());
        }
    }
}