提示
上期教程: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
}
然后运行测试即可。
完整代码
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
}
}