提示

上期教程:1.18模组开发之访问转换器的使用和简易功能实现

Mixin的使用

简绍

Mixin 可以在不修改源文件的情况下修改 Minecraft 的代码,以达到实现自定义功能的效果。

使用

以下是 Mixin 的配置文件:

{
  "required": true,
  "minVersion": "0.8",
  "package": "cn.ksmcbrigade.em.mixin",
  "compatibilityLevel": "JAVA_8",
  "refmap": "em.refmap.json",
  "mixins": [
  ],
  "client": [
  ],
  "injectors": {
    "defaultRequire": 1
  }
}

其中的 mixin 项代表着双端都要进行注入的 mixin,client 则代表只需要在客户端被注入的 mixin,这里还有一个没有显示出来,便是 server 项,该项内的 mixin 只会在服务端被注入。

首先在 package 项所指定的目录中创建一个类,这里以 Block 类为例:

package cn.ksmcbrigade.em.mixin;

import net.minecraft.world.level.block.Block;
import org.spongepowered.asm.mixin.Mixin;

@Mixin(Block.class)
public class BlockMixin {
}

这里的 Mixin 注解代表着被注入的类,Mixin 注解除了以上写法,还有以下几种写法:

@Mixin({Block.class})
@Mixin(targets = {"net.minecraft.world.level.block.Block"})

然后对 Block 类中的 shouldRenderFace 函数进行注入:

@Inject(method = "shouldRenderFace",at = @At("RETURN"))
private static void renderFace(BlockState p_152445_, BlockGetter p_152446_, BlockPos p_152447_, Direction p_152448_, BlockPos p_152449_, CallbackInfoReturnable<Boolean> cir){
    
}

这里的 Inject 注解表示注入,method 为被注入的函数,at 为位置,这里除了 RETURN(返回时) 以外还可以填 TAIL(末尾),HEAD(开头),INVOKE_ASSIGN(指定函数执行后),INVOKE(指定函数执行前),当被注入的函数为 <init> 或者 <client> 时将无法使用 HEAD,若使用将会因代码位于 super 函数前而报错。

若被注入的函数含有 static 修饰符,则需要将其写为 private static void 的形式,反之则写成 public void 的形式。

这里尝试判断方块是否为指定方块,是则返回 true,否则返回 false:


private static Block[] blocks = new Block[]{Blocks.COAL_ORE,Blocks.IRON_ORE,Blocks.GOLD_ORE,Blocks.DIAMOND_ORE,Blocks.EMERALD_ORE,Blocks.DEEPSLATE_COAL_ORE,Blocks.COPPER_ORE,Blocks.DEEPSLATE_DIAMOND_ORE,Blocks.DEEPSLATE_LAPIS_ORE,Blocks.LAPIS_ORE};

@Inject(method = "shouldRenderFace",at = @At("RETURN"), cancellable = true)
private static void renderFace(BlockState p_152445_, BlockGetter p_152446_, BlockPos p_152447_, Direction p_152448_, BlockPos p_152449_, CallbackInfoReturnable<Boolean> cir){
    if(Arrays.stream(blocks).toList().contains(p_152445_.getBlock())){  //if
        cir.setReturnValue(true);  //set
    }
    else{
        cir.setReturnValue(false);  //set
    }
}

若需要将其取消或重新设置返回值,需要在注解中添加 cancellable = true 字样,否则运行时将会报错。

这里的 blocks 数组仅作为演示。

最后记得将 mixin 添加到配置文件内:

{
  "required": true,
  "minVersion": "0.8",
  "package": "cn.ksmcbrigade.em.mixin",
  "compatibilityLevel": "JAVA_8",
  "refmap": "em.refmap.json",
  "mixins": [
  ],
  "client": [
    "BlockMixin"
  ],
  "injectors": {
    "defaultRequire": 1
  }
}

简易功能实现

X-Ray

首先在 module 目录创建一个类并继承 Module 类:

package cn.ksmcbrigade.em.modules;

import cn.ksmcbrigade.em.Module;

import java.awt.event.KeyEvent;

public class XRay extends Module {
    public XRay() {
        super("XRay", KeyEvent.VK_X); //KEY X
    }
}

然后创建一个 Block 类型的空的可变数组和用于获取的函数,并在函数内判断数组是否为空,若为空则利用反射机制获取所有矿石类型的方块并添加到数组,最后返回数组:

public static ArrayList<Block> blocks = new ArrayList<>();

public XRay() {
    super("XRay", KeyEvent.VK_X); //KEY X
}

public static ArrayList<Block> get(){
   if(blocks.size()==0){
       Arrays.stream(Blocks.class.getDeclaredFields())
               .filter(f -> f.getType().equals(Block.class))  //filter Block type
               .filter(f -> Modifier.isStatic(f.getModifiers()))  //filter static
               .filter(f -> f.getName().toLowerCase().contains("ore"))  //filter ore
               .toList()
               .forEach(f -> {
                   try {
                       blocks.add(f.get(null));  //add to arrays list
                   } catch (IllegalAccessException e) {
                       e.printStackTrace();
                   }
               });
   }
    return blocks;
}

然后再在 BlockMixin 中判断是否启用该功能并调用该函数即可:

@Inject(method = "shouldRenderFace",at = @At("RETURN"), cancellable = true)
private static void renderFace(BlockState p_152445_, BlockGetter p_152446_, BlockPos p_152447_, Direction p_152448_, BlockPos p_152449_, CallbackInfoReturnable<Boolean> cir){
    if(ModuleManager.modulesClass.XRay.enabled && XRay.get().contains(p_152445_.getBlock())){
        cir.setReturnValue(true);
    }
    else if(ModuleManager.modulesClass.XRay.enabled){
        cir.setReturnValue(false);
    }
}
public static class modulesClass {
    public static Module NoFall = new NoFall("NoFall", KeyEvent.VK_N);
    public static Module XYZ = new XYZ("XYZ", KeyEvent.VK_B);
    public static Module Timer = new Timer();  //key y
    public static Module Pegasus = new Pegasus(); //key i
    public static Module XRay = new XRay(); //KEY X
}

BoatFly

简绍

该功能可以使玩家位于船上时可以飞行。

实现

首先在 module 目录中新建一个类并继承 Module,然后再在重写 update 函数,并在函数内判断玩家是否含有坐骑,坐骑是否为 Boat 类型,空格是否被按下,若被按下则让坐骑上升 0.3 格。

package cn.ksmcbrigade.em.modules;

import cn.ksmcbrigade.em.Module;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;

import java.awt.event.KeyEvent;

public class BoatFly extends Module {
    public BoatFly() {
        super("BoatFly", KeyEvent.VK_Z); //KEY Z
    }

    @Override
    public void update() {
        Minecraft MC = Minecraft.getInstance();
        if(MC.player==null){ //if player
            return;
        }
        if(MC.player.getVehicle()==null){  //if vehicle
            return;
        }
        Entity vehicle = MC.player.getVehicle();
        if(!vehicle.getType().equals(EntityType.BOAT)){  //if type
            return;
        }
        if(!MC.options.keyJump.isDown()){ //if jump
            return;
        }
        vehicle.setDeltaMovement(0,0.3,0); //set
    }
}

FullBright

首先在 module 目录中新建一个类并继承 Module 类,然后在启用时保存 gamma 值,并在被禁用后设置为保存的 gamma 值,并重新 update 函数,在 update 函数内不断设置 gamma 值为最高。

package cn.ksmcbrigade.em.modules;

import cn.ksmcbrigade.em.Module;
import net.minecraft.client.Minecraft;

import java.awt.event.KeyEvent;

public class FullBright extends Module {
    
    public double gamma = 0.0D;
    
    public FullBright() {
        super("FullBright", KeyEvent.VK_C);
    }

    @Override
    public void enabled() {
        this.gamma = Minecraft.getInstance().options.gamma;
    }

    @Override
    public void disabled() {
        Minecraft.getInstance().options.gamma = this.gamma;
    }

    @Override
    public void update() {
        Minecraft.getInstance().options.gamma = 3000.0D;
    }
}

注册功能

最后在 ModuleManager.moduleClass 中注册功能即可:

public static class modulesClass {
    public static Module NoFall = new NoFall("NoFall", KeyEvent.VK_N);
    public static Module XYZ = new XYZ("XYZ", KeyEvent.VK_B);
    public static Module Timer = new Timer();  //key y
    public static Module Pegasus = new Pegasus(); //key i
    public static Module XRay = new XRay(); //KEY X
    public static Module BoatFly = new BoatFly(); //key z
    public static Module FullBright = new FullBright(); //key c
}

然后运行测试即可。

1.18模组制作之Mixin的使用和简易功能实现-第1张图片

完整代码

ModuleManager.modulesClass

public static class modulesClass {
    public static Module NoFall = new NoFall("NoFall", KeyEvent.VK_N);
    public static Module XYZ = new XYZ("XYZ", KeyEvent.VK_B);
    public static Module Timer = new Timer();  //key y
    public static Module Pegasus = new Pegasus(); //key i
    public static Module XRay = new XRay(); //KEY X
    public static Module BoatFly = new BoatFly(); //key z
    public static Module FullBright = new FullBright(); //key c
}

BoatFly.java

package cn.ksmcbrigade.em.modules;

import cn.ksmcbrigade.em.Module;
import net.minecraft.client.Minecraft;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;

import java.awt.event.KeyEvent;

public class BoatFly extends Module {
    public BoatFly() {
        super("BoatFly", KeyEvent.VK_Z); //KEY Z
    }

    @Override
    public void update() {
        Minecraft MC = Minecraft.getInstance();
        if(MC.player==null){
            return;
        }
        if(MC.player.getVehicle()==null){
            return;
        }
        Entity vehicle = MC.player.getVehicle();
        if(!vehicle.getType().equals(EntityType.BOAT)){
            return;
        }
        if(!MC.options.keyJump.isDown()){
            return;
        }
        vehicle.setDeltaMovement(0,0.3,0);
    }
}

XRay.java

package cn.ksmcbrigade.em.modules;

import cn.ksmcbrigade.em.Module;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;

import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;

public class XRay extends Module {

    public static ArrayList<Block> blocks = new ArrayList<>();

    public XRay() {
        super("XRay", KeyEvent.VK_X); //KEY X
    }

    public static ArrayList<Block> get(){
       if(blocks.size()==0){
           Arrays.stream(Blocks.class.getDeclaredFields())
                   .filter(f -> f.getType().equals(Block.class))
                   .filter(f -> f.getName().toLowerCase().contains("ore"))
                   .toList()
                   .forEach(f -> {
                       try {
                           blocks.add((Block) f.get(null));
                       } catch (IllegalAccessException e) {
                           e.printStackTrace();
                       }
                   });
       }
        return blocks;
    }
}

FullBright.java

package cn.ksmcbrigade.em.modules;

import cn.ksmcbrigade.em.Module;
import net.minecraft.client.Minecraft;

import java.awt.event.KeyEvent;

public class FullBright extends Module {

    public double gamma = 0.0D;

    public FullBright() {
        super("FullBright", KeyEvent.VK_C);
    }

    @Override
    public void enabled() {
        this.gamma = Minecraft.getInstance().options.gamma;
    }

    @Override
    public void disabled() {
        Minecraft.getInstance().options.gamma = this.gamma;
    }

    @Override
    public void update() {
        Minecraft.getInstance().options.gamma = 3000.0D;
    }
}

BlockMixin.java

package cn.ksmcbrigade.em.mixin;

import cn.ksmcbrigade.em.ModuleManager;
import cn.ksmcbrigade.em.modules.XRay;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Mixin(Block.class)
public abstract class BlockMixin {

    @Inject(method = "shouldRenderFace",at = @At("RETURN"), cancellable = true)
    private static void renderFace(BlockState p_152445_, BlockGetter p_152446_, BlockPos p_152447_, Direction p_152448_, BlockPos p_152449_, CallbackInfoReturnable<Boolean> cir){
        if(ModuleManager.modulesClass.XRay.enabled && XRay.get().contains(p_152445_.getBlock())){
            cir.setReturnValue(true);
        }
        else if(ModuleManager.modulesClass.XRay.enabled){
            cir.setReturnValue(false);
        }
    }
}

modid.mixins.json

{
  "required": true,
  "minVersion": "0.8",
  "package": "cn.ksmcbrigade.em.mixin",
  "compatibilityLevel": "JAVA_8",
  "refmap": "em.refmap.json",
  "mixins": [
  ],
  "client": [
    "BlockMixin"
  ],
  "injectors": {
    "defaultRequire": 1
  }
}