diff --git a/src/main/java/top/hendrixshen/tweakmyclient/compat/CompatMixinPlugin.java b/src/main/java/top/hendrixshen/tweakmyclient/compat/CompatMixinPlugin.java
new file mode 100644
index 00000000..8bd4c3d8
--- /dev/null
+++ b/src/main/java/top/hendrixshen/tweakmyclient/compat/CompatMixinPlugin.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) Copyright 2020 - 2022 The Cat Town Craft and contributors.
+ * This source code is subject to the terms of the GNU Lesser General Public
+ * License, version 3. If a copy of the LGPL was not distributed with this
+ * file, You can obtain one at: https://www.gnu.org/licenses/lgpl-3.0.txt
+ */
+package top.hendrixshen.tweakmyclient.compat;
+
+import com.google.common.collect.Lists;
+import net.fabricmc.tinyremapper.IMappingProvider.Member;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.*;
+import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
+import org.spongepowered.asm.mixin.transformer.meta.MixinMerged;
+import org.spongepowered.asm.util.Annotations;
+import top.hendrixshen.tweakmyclient.TweakMyClientMixinPlugin;
+import top.hendrixshen.tweakmyclient.util.RemapUtil;
+import top.hendrixshen.tweakmyclient.util.mixin.MixinType;
+import top.hendrixshen.tweakmyclient.util.mixin.annotation.MagicAttack;
+import top.hendrixshen.tweakmyclient.util.mixin.annotation.MagicInterruption;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CompatMixinPlugin extends TweakMyClientMixinPlugin {
+ @Override
+ public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
+ super.postApply(targetClassName, targetClass, mixinClassName, mixinInfo);
+
+ // Magical chanting.
+ ClassNode mixinClass = mixinInfo.getClassNode(0);
+ AnnotationNode magicInterruption = Annotations.getVisible(mixinClass, MagicInterruption.class);
+
+ if (magicInterruption == null) {
+ return;
+ }
+
+ for (MethodNode methodNode : mixinClass.methods) {
+ AnnotationNode magicAttack = Annotations.getVisible(methodNode, MagicAttack.class);
+
+ if (magicAttack == null) {
+ continue;
+ }
+
+ this.jikuTsuiho(targetClass, methodNode, magicInterruption, magicAttack);
+ }
+ }
+
+ /**
+ * 時空追放.
+ *
Erase target.
+ * @param targetClass Injection target class.
+ * @param magicAttackMethodNode Spell casting MethodNode.
+ * @param magicInterruption Spell casting interruption AnnotationNode.
+ * @param magicAttack Magical attacking AnnotationNode.
+ */
+ private void jikuTsuiho(ClassNode targetClass, MethodNode magicAttackMethodNode, AnnotationNode magicInterruption, AnnotationNode magicAttack) {
+ List> tekitaiClass = Annotations.getValue(magicInterruption, "value", true);
+ List tekitaiTarget = Annotations.getValue(magicInterruption, "targets", true);
+
+ ArrayList tekitaiClasses = Lists.newArrayList();
+ for (Class> cls : tekitaiClass) {
+ String s = cls.getName();
+ if (!tekitaiClasses.contains(s)) {
+ tekitaiClasses.add(s);
+ }
+ }
+ for (String s : tekitaiTarget) {
+ if (!tekitaiClasses.contains(s)) {
+ tekitaiClasses.add(s);
+ }
+ }
+
+ MethodNode tekitaiMethod = this.findTekitaiMethod(targetClass, magicAttack, tekitaiClasses);
+
+ if (tekitaiMethod == null) {
+ return;
+ }
+
+ // Magic releasing.
+
+ this.tsuihoTarget(targetClass, tekitaiMethod, magicAttackMethodNode, magicAttack);
+
+ // Magic Harvesting.
+ targetClass.visibleAnnotations.removeIf(annotationNode -> annotationNode.desc.equals("Ltop/catowncraft/carpettctcaddition/util/mixin/annotation/MagicInterruption;"));
+ }
+
+ /**
+ * Find the enemy's MethodNode.
+ * @param targetClass Injection target class.
+ * @param magicAttack Spell casting MethodNode.
+ * @param tekitaiTarget Enemy's class.
+ * @return Enemy's MethodNode.
+ */
+ private MethodNode findTekitaiMethod(ClassNode targetClass, AnnotationNode magicAttack, ArrayList tekitaiTarget) {
+ List type = Annotations.getValue(magicAttack, "type", true, MixinType.class);
+ String name = Annotations.getValue(magicAttack, "name");
+ int priority = Annotations.getValue(magicAttack, "priority", 1000);
+ for (MethodNode methodNode : targetClass.methods) {
+ if (!(methodNode.name.contains(type.get(0).getPrefix()) && methodNode.name.contains(name))) {
+ continue;
+ }
+
+ AnnotationNode annotationNode = Annotations.getVisible(methodNode, MixinMerged.class);
+ String mixin = Annotations.getValue(annotationNode, "mixin");
+
+ if (!tekitaiTarget.contains(mixin)) {
+ continue;
+ }
+
+ int targetPriority = Annotations.getValue(annotationNode, "priority");
+ if (priority != targetPriority) {
+ continue;
+ }
+
+ return methodNode;
+ }
+ return null;
+ }
+
+ /**
+ * Erase target and infuse soul.
+ * @param targetClass Injection target ClassNode.
+ * @param tekitaiMethodNode Enemy's MethodNode.
+ * @param magicAttackMethodNode Spell casting MethodNode.
+ * @param magicAttack Magical attacking AnnotationNode.
+ */
+ private void tsuihoTarget(ClassNode targetClass, MethodNode tekitaiMethodNode, MethodNode magicAttackMethodNode, AnnotationNode magicAttack) {
+ String method = Annotations.getValue(magicAttack, "method");
+ String owner = Annotations.getValue(magicAttack, "owner");
+ String desc = Annotations.getValue(magicAttack, "desc");
+ Member remappedMethod = RemapUtil.mapMethod(owner, method, desc);
+ int ordinal = Annotations.getValue(magicAttack, "ordinal", -1);
+ boolean keep = Annotations.getValue(magicAttack, "keep", Boolean.FALSE);
+
+ for (MethodNode methodNode : targetClass.methods) {
+ if (methodNode.name.equals(remappedMethod.name) && methodNode.desc.equals(remappedMethod.desc)) {
+ int offset = 0;
+ int found = 0;
+ int processed = 0;
+
+ for(AbstractInsnNode abstractInsnNode : methodNode.instructions) {
+ if (!(abstractInsnNode instanceof MethodInsnNode)) {
+ continue;
+ }
+
+ MethodInsnNode methodInsnNode = (MethodInsnNode) abstractInsnNode;
+
+ if (!methodInsnNode.name.equals(tekitaiMethodNode.name) ||
+ !methodInsnNode.desc.equals(tekitaiMethodNode.desc)) {
+ continue;
+ }
+
+ if ((tekitaiMethodNode.access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC &&
+ methodInsnNode.getOpcode() != Opcodes.INVOKESTATIC) {
+ continue;
+ }
+
+ if ((tekitaiMethodNode.access & Opcodes.ACC_STATIC) == 0 &&
+ methodInsnNode.getOpcode() == Opcodes.INVOKESTATIC) {
+ continue;
+ }
+
+ if (ordinal < 0 || ordinal == offset) {
+ MethodInsnNode invokeMethod = new MethodInsnNode(methodInsnNode.getOpcode(), methodInsnNode.owner,
+ magicAttackMethodNode.name, methodInsnNode.desc);
+ methodNode.instructions.insertBefore(methodInsnNode, invokeMethod);
+ methodNode.instructions.remove(methodInsnNode);
+ processed++;
+ }
+
+ offset++;
+ found++;
+
+ if (found == processed && !keep) {
+ targetClass.methods.remove(tekitaiMethodNode);
+ }
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/src/main/java/top/hendrixshen/tweakmyclient/compat/wurst/mixin/MixinLocalPlayer.java b/src/main/java/top/hendrixshen/tweakmyclient/compat/wurst/mixin/MixinLocalPlayer.java
new file mode 100644
index 00000000..86e68a92
--- /dev/null
+++ b/src/main/java/top/hendrixshen/tweakmyclient/compat/wurst/mixin/MixinLocalPlayer.java
@@ -0,0 +1,42 @@
+package top.hendrixshen.tweakmyclient.compat.wurst.mixin;
+
+import net.minecraft.client.player.LocalPlayer;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.level.Level;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Redirect;
+import top.hendrixshen.magiclib.dependency.annotation.Dependencies;
+import top.hendrixshen.magiclib.dependency.annotation.Dependency;
+import top.hendrixshen.tweakmyclient.config.Configs;
+import top.hendrixshen.tweakmyclient.util.mixin.MixinType;
+import top.hendrixshen.tweakmyclient.util.mixin.annotation.MagicAttack;
+import top.hendrixshen.tweakmyclient.util.mixin.annotation.MagicInterruption;
+
+@MagicInterruption(targets = "net.wurstclient.mixin.ClientPlayerEntityMixin")
+@Dependencies(and = @Dependency(value = "wurst"))
+@Mixin(value = LocalPlayer.class, priority = 1100)
+public abstract class MixinLocalPlayer extends LivingEntity {
+ @Shadow
+ private boolean startedUsingItem;
+
+ protected MixinLocalPlayer(EntityType extends LivingEntity> entityType, Level level) {
+ super(entityType, level);
+ }
+
+ @MagicAttack(
+ type = MixinType.REDIRECT,
+ name = "wurstIsUsingItem",
+ owner = "class_1309",
+ method = "method_6007",
+ desc = "()V"
+ )
+ private boolean tmc$getUsingItemState(LocalPlayer instance) {
+ if (Configs.disableSlowdown) {
+ return false;
+ }
+ return this.startedUsingItem;
+ }
+}
diff --git a/src/main/java/top/hendrixshen/tweakmyclient/mixin/disable/disableSlowdown/MixinLocalPlayer.java b/src/main/java/top/hendrixshen/tweakmyclient/mixin/disable/disableSlowdown/MixinLocalPlayer.java
index 6e4fbce1..beffa2bc 100644
--- a/src/main/java/top/hendrixshen/tweakmyclient/mixin/disable/disableSlowdown/MixinLocalPlayer.java
+++ b/src/main/java/top/hendrixshen/tweakmyclient/mixin/disable/disableSlowdown/MixinLocalPlayer.java
@@ -8,8 +8,11 @@
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
+import top.hendrixshen.magiclib.dependency.annotation.Dependencies;
+import top.hendrixshen.magiclib.dependency.annotation.Dependency;
import top.hendrixshen.tweakmyclient.config.Configs;
+@Dependencies(not = @Dependency(value = "wurst"))
@Mixin(LocalPlayer.class)
public abstract class MixinLocalPlayer extends LivingEntity {
@Shadow
diff --git a/src/main/java/top/hendrixshen/tweakmyclient/util/RemapUtil.java b/src/main/java/top/hendrixshen/tweakmyclient/util/RemapUtil.java
new file mode 100644
index 00000000..83460b0f
--- /dev/null
+++ b/src/main/java/top/hendrixshen/tweakmyclient/util/RemapUtil.java
@@ -0,0 +1,41 @@
+package top.hendrixshen.tweakmyclient.util;
+
+import net.fabricmc.loader.api.FabricLoader;
+import net.fabricmc.loader.api.MappingResolver;
+import net.fabricmc.tinyremapper.IMappingProvider.Member;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class RemapUtil {
+ private static final Pattern CLASS_FINDER = Pattern.compile("Lnet\\/minecraft\\/([^;]+);");
+ private static final String INTERMEDIARY = "intermediary";
+ private static final MappingResolver RESOLVER = FabricLoader.getInstance().getMappingResolver();
+
+ private static String fromIntermediaryDot(String className) {
+ return RESOLVER.mapClassName(INTERMEDIARY, String.format("net.minecraft.%s", className));
+ }
+
+ public static String getClassName(String className) {
+ return RemapUtil.fromIntermediaryDot(className).replace('.', '/');
+ }
+
+ public static String getMethodName(String owner, String methodName, String desc) {
+ return RemapUtil.RESOLVER.mapMethodName(INTERMEDIARY, "net.minecraft." + owner, methodName, desc);
+ }
+
+ public static Member mapMethod(String owner, String name, String desc) {
+ return new Member(RemapUtil.getClassName(owner), RemapUtil.getMethodName(owner, name, desc), RemapUtil.mapMethodDescriptor(desc));
+ }
+
+ public static String mapMethodDescriptor(String desc) {
+ StringBuilder stringBuffer = new StringBuilder();
+
+ Matcher matcher = CLASS_FINDER.matcher(desc);
+ while (matcher.find()) {
+ matcher.appendReplacement(stringBuffer, Matcher.quoteReplacement('L' + RemapUtil.getClassName(matcher.group(1)) + ';'));
+ }
+
+ return matcher.appendTail(stringBuffer).toString();
+ }
+}
diff --git a/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/MixinType.java b/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/MixinType.java
new file mode 100644
index 00000000..d3bbd363
--- /dev/null
+++ b/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/MixinType.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) Copyright 2020 - 2022 The Cat Town Craft and contributors.
+ * This source code is subject to the terms of the GNU Lesser General Public
+ * License, version 3. If a copy of the LGPL was not distributed with this
+ * file, You can obtain one at: https://www.gnu.org/licenses/lgpl-3.0.txt
+ */
+package top.hendrixshen.tweakmyclient.util.mixin;
+
+public enum MixinType {
+ // ModifyVariable
+ MODIFY_ARG("modify"),
+ MODIFY_ARGS("args"),
+ MODIFY_CONSTANT("constant"),
+ MODIFY_VARIABLE("localvar"),
+ REDIRECT("redirect");
+
+ private final String prefix;
+
+ MixinType(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+}
diff --git a/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/annotation/MagicAttack.java b/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/annotation/MagicAttack.java
new file mode 100644
index 00000000..6ed6c252
--- /dev/null
+++ b/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/annotation/MagicAttack.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) Copyright 2020 - 2022 The Cat Town Craft and contributors.
+ * This source code is subject to the terms of the GNU Lesser General Public
+ * License, version 3. If a copy of the LGPL was not distributed with this
+ * file, You can obtain one at: https://www.gnu.org/licenses/lgpl-3.0.txt
+ */
+package top.hendrixshen.tweakmyclient.util.mixin.annotation;
+
+import top.hendrixshen.tweakmyclient.util.mixin.MixinType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * You need magic to defeat magic.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface MagicAttack {
+ /**
+ * The type of injector you want to erase the target.
+ * @return Injector Type.
+ */
+ MixinType type();
+
+ /**
+ * Name of the method you want to erase the target.
+ * @return Target method name.
+ */
+ String name();
+
+ /**
+ * From which owner you want to erase the target. Here you should use the intermediary name.
+ * @return Source method owner.
+ */
+ String owner();
+
+ /**
+ * From which method you want to erase the target. Here you should use the intermediary name.
+ * @return Source method name.
+ */
+ String method();
+
+ /**
+ * From which method you want to erase the target. Here you should use the intermediary name.
+ * @return Source method desc.
+ */
+ String desc();
+
+ /**
+ * The point at which you want to erase the target.
+ * @return Offset(-1 means all).
+ */
+ int ordinal() default -1;
+
+ /**
+ * Priority of the method you want to erase the target.
+ * @return Priority.
+ */
+ int priority() default 1000;
+
+ /**
+ * Whether to retain the target original injection.
+ * @return Whether to keep.
+ */
+ boolean keep() default false;
+}
diff --git a/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/annotation/MagicInterruption.java b/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/annotation/MagicInterruption.java
new file mode 100644
index 00000000..c78d74c3
--- /dev/null
+++ b/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/annotation/MagicInterruption.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) Copyright 2020 - 2022 The Cat Town Craft and contributors.
+ * This source code is subject to the terms of the GNU Lesser General Public
+ * License, version 3. If a copy of the LGPL was not distributed with this
+ * file, You can obtain one at: https://www.gnu.org/licenses/lgpl-3.0.txt
+ */
+package top.hendrixshen.tweakmyclient.util.mixin.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface MagicInterruption {
+ Class>[] value() default {};
+
+ String[] targets() default {};
+}
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index e5d35bad..2188686f 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -29,7 +29,8 @@
]
},
"mixins": [
- "${mod_id}.mixins.json"
+ "${mod_id}.mixins.json",
+ "${mod_id}-compat-wurst.mixins.json"
],
"depends": {
"minecraft": "${minecraft_dependency}",
diff --git a/src/main/resources/tweakmyclient-compat-wurst.mixins.json b/src/main/resources/tweakmyclient-compat-wurst.mixins.json
new file mode 100644
index 00000000..178d36bd
--- /dev/null
+++ b/src/main/resources/tweakmyclient-compat-wurst.mixins.json
@@ -0,0 +1,15 @@
+{
+ "required": true,
+ "minVersion": "0.8",
+ "package": "top.hendrixshen.tweakmyclient.compat.wurst.mixin",
+ "plugin": "top.hendrixshen.tweakmyclient.compat.CompatMixinPlugin",
+ "compatibilityLevel": "JAVA_8",
+ "mixins": [
+ ],
+ "client": [
+ "MixinLocalPlayer"
+ ],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
diff --git a/src/main/resources/tweakmyclient.mixins.json b/src/main/resources/tweakmyclient.mixins.json
index 0e310edd..997140dd 100644
--- a/src/main/resources/tweakmyclient.mixins.json
+++ b/src/main/resources/tweakmyclient.mixins.json
@@ -5,11 +5,10 @@
"plugin": "top.hendrixshen.tweakmyclient.TweakMyClientMixinPlugin",
"compatibilityLevel": "JAVA_8",
"mixins": [
- "accessor.PlayerTabOverlayAccessor",
- "patch.litematicaSchematicWailaCompat.jade.MixinOverlayRenderer"
],
"client": [
"accessor.MultiPlayerGameModeAccessor",
+ "accessor.PlayerTabOverlayAccessor",
"disable.disableAttackEntity.MixinMinecraft",
"disable.disableClientBlockEvents.MixinClientPacketListener",
"disable.disableClientEntityRendering.MixinEntityRenderDispatcher",
@@ -65,6 +64,7 @@
"patch.litematicaSchematicWailaCompat.wthit.MixinClientTickHandler",
"patch.litematicaSchematicWailaCompat.MixinLocalPlayer",
"patch.litematicaSchematicWailaCompat.hwyla.MixinOverlayRenderer",
+ "patch.litematicaSchematicWailaCompat.jade.MixinOverlayRenderer",
"patch.litematicaSchematicWailaCompat.wthit.MixinTickHandler"
],
"injectors": {