diff --git a/build.gradle b/build.gradle index 46b7c9ac..0a5994e0 100644 --- a/build.gradle +++ b/build.gradle @@ -117,6 +117,7 @@ repositories { name = "ftbMavenReleases" url = uri("https://maven.ftb.dev/releases") } + maven { url = "https://maven.bawnorton.com/releases" } } version = mod_version @@ -196,6 +197,10 @@ dependencies { // Apply Mixin AP annotationProcessor('org.spongepowered:mixin:0.8.5:processor') + // Mixin squared + compileOnly(annotationProcessor("com.github.bawnorton.mixinsquared:mixinsquared-common:0.3.7-beta.1")) + implementation(jarJar("com.github.bawnorton.mixinsquared:mixinsquared-forge:0.3.7-beta.1")) + // JEI, EMI, Jade modCompileOnly("mezz.jei:jei-${minecraft_version}-forge-api:${jei_version}") modCompileOnly("mezz.jei:jei-${minecraft_version}-common-api:${jei_version}") @@ -235,6 +240,9 @@ dependencies { // MAE2 for cloud chamber effects modImplementation("curse.maven:mae2-1028068:7084998") + + // AAE because we need to redo their mixin + modCompileOnly("maven.modrinth:advancedae:${aae_version}") } tasks.named('processResources', ProcessResources).configure { diff --git a/gradle.properties b/gradle.properties index fe010bca..bce3dc3b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -31,10 +31,11 @@ org.gradle.jvmargs=-Xmx1G configuration_version=2.2.0 jei_version=15.20.0.118 emi_version = 1.1.22 - mae2_version = 2.0.0-beta.g + mae2_version = 2.0.0-beta.i oculus_version = 1.20.1-1.8.0 embeddium_version = 0.3.31+mc1.20.1 ftblibrary_version = 2001.2.4 ftbteams_version = 2001.3.0 jade_version = 11.6.3 - ae2_version=15.4.10-cosmolite.32 + ae2_version=15.4.10-cosmolite.33 + aae_version=1.3.2-1.20.1 diff --git a/src/generated/resources/assets/monilabs/lang/en_ud.json b/src/generated/resources/assets/monilabs/lang/en_ud.json index 61917a11..b00b0671 100644 --- a/src/generated/resources/assets/monilabs/lang/en_ud.json +++ b/src/generated/resources/assets/monilabs/lang/en_ud.json @@ -76,6 +76,7 @@ "gtceu.placeholder_info.sculkVatXPBuffer.0": "˙ʇɐΛ ʞןnɔS ǝɥʇ ɟo ɹǝɟɟnq ԀX ʇuǝɹɹnɔ ǝɥʇ suɹnʇǝᴚ", "gtceu.placeholder_info.sculkVatXPBuffer.1": ":ǝbɐs∩", "gtceu.placeholder_info.sculkVatXPBuffer.2": ")sʇǝʞɔnqıןןıɯ uı ǝnןɐʌ( :pǝɹoʇS ԀX >- }ɹǝɟɟnᗺԀXʇɐΛʞןnɔs{ ", + "gui.ae2.With": "ɥʇıM", "item.monilabs.max_conveyor_module": "ǝןnpoW ɹoʎǝʌuoƆ XⱯW", "item.monilabs.max_electric_motor": "ɹoʇoW ɔıɹʇɔǝןƎ XⱯW", "item.monilabs.max_electric_piston": "uoʇsıԀ ɔıɹʇɔǝןƎ XⱯW", diff --git a/src/generated/resources/assets/monilabs/lang/en_us.json b/src/generated/resources/assets/monilabs/lang/en_us.json index d96df6ec..188efd66 100644 --- a/src/generated/resources/assets/monilabs/lang/en_us.json +++ b/src/generated/resources/assets/monilabs/lang/en_us.json @@ -76,6 +76,7 @@ "gtceu.placeholder_info.sculkVatXPBuffer.0": "Returns the current XP buffer of the Sculk Vat.", "gtceu.placeholder_info.sculkVatXPBuffer.1": "Usage:", "gtceu.placeholder_info.sculkVatXPBuffer.2": " {sculkVatXPBuffer} -> XP Stored: (value in millibuckets)", + "gui.ae2.With": "With", "item.monilabs.max_conveyor_module": "MAX Conveyor Module", "item.monilabs.max_electric_motor": "MAX Electric Motor", "item.monilabs.max_electric_piston": "MAX Electric Piston", diff --git a/src/main/java/net/neganote/monilabs/data/lang/MoniLangHandler.java b/src/main/java/net/neganote/monilabs/data/lang/MoniLangHandler.java index 111916a5..f16c54f8 100644 --- a/src/main/java/net/neganote/monilabs/data/lang/MoniLangHandler.java +++ b/src/main/java/net/neganote/monilabs/data/lang/MoniLangHandler.java @@ -187,6 +187,8 @@ public static void init(RegistrateLangProvider provider) { provider.add("config.jade.plugin_monilabs.sculk_vat_xp_info", "Sculk Vat XP Buffer Info"); provider.add("config.jade.plugin_monilabs.omnic_synth_info", "Omnic Synthesizer Info"); + provider.add("gui.ae2.With", "With"); + multiLang(provider, "gtceu.placeholder_info.prismacColor", "Returns the current color of the Prismatic Crucible.", "Usage:", diff --git a/src/main/java/net/neganote/monilabs/mixin/MoniMixinPlugin.java b/src/main/java/net/neganote/monilabs/mixin/MoniMixinPlugin.java new file mode 100644 index 00000000..dc2ad030 --- /dev/null +++ b/src/main/java/net/neganote/monilabs/mixin/MoniMixinPlugin.java @@ -0,0 +1,408 @@ +package net.neganote.monilabs.mixin; + +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.loading.LoadingModList; +import net.minecraftforge.fml.loading.moddiscovery.ModInfo; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.util.List; +import java.util.Set; + +public final class MoniMixinPlugin implements IMixinConfigPlugin { + + private static final Logger LOGGER = LogManager.getLogger("MoniLabs-Mixin"); + private static final boolean VERBOSE = Boolean.parseBoolean(System.getProperty("monilabs.mixin.verbose", "false")); + + private static final Set AAE_MIXINS = Set.of( + "net.neganote.monilabs.mixin.aae.MixinCraftingJobStatusPacketCtorShim", + "net.neganote.monilabs.mixin.aae.MixinCraftingServiceAAE", + "net.neganote.monilabs.mixin.aae.MixinEncodedPatternItemShim", + "net.neganote.monilabs.mixin.aae.MixinGuiTextShim", + "net.neganote.monilabs.mixin.aae.MixinPatternDetailsHelperShim", + "net.neganote.monilabs.mixin.aae.MixinProcessingPatternItemShim"); + + // --- Targets (internal names) + private static final String PACKET_INTERNAL = "appeng/core/sync/packets/CraftingJobStatusPacket"; + private static final String PDH_INTERNAL = "appeng/api/crafting/PatternDetailsHelper"; + private static final String PPI_INTERNAL = "appeng/crafting/pattern/ProcessingPatternItem"; + private static final String EPI_INTERNAL = "appeng/crafting/pattern/EncodedPatternItem"; + private static final String GUITEXT_INTERNAL = "appeng/core/localization/GuiText"; + + // --- Packet ctors + private static final String CTOR_AE2 = "(Ljava/util/UUID;Lappeng/api/stacks/AEKey;JJLappeng/core/sync/packets/CraftingJobStatusPacket$Status;)V"; + private static final String CTOR_AE2CL_6 = "(Ljava/util/UUID;Lappeng/api/stacks/AEKey;JJJLappeng/core/sync/packets/CraftingJobStatusPacket$Status;)V"; + private static final String CTOR_AE2CL_7 = "(Ljava/util/UUID;Lappeng/api/stacks/AEKey;JJJZLappeng/core/sync/packets/CraftingJobStatusPacket$Status;)V"; + + // --- PatternDetailsHelper.encodeCraftingPattern overloads + private static final String PDH_NO_AUTHOR = "(Lnet/minecraft/world/item/crafting/CraftingRecipe;" + + "[Lnet/minecraft/world/item/ItemStack;" + + "Lnet/minecraft/world/item/ItemStack;ZZ)" + + "Lnet/minecraft/world/item/ItemStack;"; + private static final String PDH_WITH_AUTHOR = "(Lnet/minecraft/world/item/crafting/CraftingRecipe;" + + "[Lnet/minecraft/world/item/ItemStack;" + + "Lnet/minecraft/world/item/ItemStack;ZZLjava/lang/String;)" + + "Lnet/minecraft/world/item/ItemStack;"; + + // --- ProcessingPatternItem.encode overloads + private static final String PPI_ENCODE_NO_AUTHOR = "([Lappeng/api/stacks/GenericStack;[Lappeng/api/stacks/GenericStack;)" + + "Lnet/minecraft/world/item/ItemStack;"; + private static final String PPI_ENCODE_WITH_AUTHOR = "([Lappeng/api/stacks/GenericStack;[Lappeng/api/stacks/GenericStack;Ljava/lang/String;)" + + "Lnet/minecraft/world/item/ItemStack;"; + + // --- EncodedPatternItem.getStackComponent overloads + private static final String EPI_GET_STACK_COMPONENT_WITH_BOOL = "(Lappeng/api/stacks/GenericStack;Z)Lnet/minecraft/network/chat/Component;"; + private static final String EPI_GET_STACK_COMPONENT_NO_BOOL = "(Lappeng/api/stacks/GenericStack;)Lnet/minecraft/network/chat/Component;"; + + // --- GuiText enum + private static final String GUITEXT_ENUM_DESC = "L" + GUITEXT_INTERNAL + ";"; + private static final String GUITEXT_VALUES_ARRAY_DESC = "[L" + GUITEXT_INTERNAL + ";"; + private static final String GUITEXT_CTOR_3 = "(Ljava/lang/String;ILjava/lang/String;)V"; + private static final String GUITEXT_CTOR_4 = "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V"; + + private static boolean isModLoaded(String modId) { + try { + if (ModList.get() == null) { + return LoadingModList.get().getMods().stream() + .map(ModInfo::getModId) + .anyMatch(modId::equals); + } else { + return ModList.get().isLoaded(modId); + } + } catch (Throwable t) { + return false; + } + } + + @Override + public void onLoad(String mixinPackage) { + if (VERBOSE) { + LOGGER.info("[MoniMixinPlugin] Plugin onLoad: {}", mixinPackage); + } + } + + @Override + public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + boolean apply = true; + if (AAE_MIXINS.contains(mixinClassName)) { + apply = isModLoaded("advanced_ae"); + } + if (VERBOSE) { + LOGGER.info("[MoniMixinPlugin] {} {} -> {}", apply ? "APPLY" : "SKIP", mixinClassName, targetClassName); + } + return apply; + } + + @Override + public void acceptTargets(Set myTargets, Set otherTargets) {} + + @Override + public List getMixins() { + return List.of(); + } + + @Override + public String getRefMapperConfig() { + return null; + } + + @Override + public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {} + + @Override + public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + final String targetInternal = targetClassName.replace('.', '/'); + + try { + switch (targetInternal) { + case PACKET_INTERNAL -> { + ensurePacketAe2CtorExists(targetClass); + logPatch(targetClassName, "CraftingJobStatusPacket legacy ctor", + hasMethod(targetClass, "", CTOR_AE2)); + } + case PDH_INTERNAL -> { + ensurePdhNoAuthorOverloadExists(targetClass); + logPatch(targetClassName, "PatternDetailsHelper.encodeCraftingPattern(no author)", + hasMethod(targetClass, "encodeCraftingPattern", PDH_NO_AUTHOR)); + } + case PPI_INTERNAL -> { + ensurePpiNoAuthorOverloadExists(targetClass); + logPatch(targetClassName, "ProcessingPatternItem.encode(no author)", + hasMethod(targetClass, "encode", PPI_ENCODE_NO_AUTHOR)); + } + case EPI_INTERNAL -> { + ensureEpiNoBoolOverloadExists(targetClass); + logPatch(targetClassName, "EncodedPatternItem.getStackComponent(no bool)", + hasMethod(targetClass, "getStackComponent", EPI_GET_STACK_COMPONENT_NO_BOOL)); + } + case GUITEXT_INTERNAL -> { + ensureGuiTextWithEnumConstantExists(targetClass); + logPatch(targetClassName, "GuiText.With enum constant", + hasField(targetClass, "With", GUITEXT_ENUM_DESC)); + } + default -> {} + } + } catch (Throwable t) { + LOGGER.error("[MoniMixinPlugin] postApply patch failed for target {}", targetClassName, t); + } + } + + // ========================= + // PATCH: CraftingJobStatusPacket ctor + // ========================= + private static void ensurePacketAe2CtorExists(ClassNode cn) { + if (hasMethod(cn, "", CTOR_AE2)) return; + + boolean has6 = hasMethod(cn, "", CTOR_AE2CL_6); + boolean has7 = hasMethod(cn, "", CTOR_AE2CL_7); + if (!has6 && !has7) return; + + MethodNode m = new MethodNode(Opcodes.ACC_PUBLIC, "", CTOR_AE2, null, null); + InsnList insn = m.instructions; + + // locals: 0=this, 1=UUID, 2=AEKey, 3=req(long), 5=rem(long), 7=Status + insn.add(new VarInsnNode(Opcodes.ALOAD, 0)); + insn.add(new VarInsnNode(Opcodes.ALOAD, 1)); + insn.add(new VarInsnNode(Opcodes.ALOAD, 2)); + insn.add(new VarInsnNode(Opcodes.LLOAD, 3)); + insn.add(new VarInsnNode(Opcodes.LLOAD, 5)); + insn.add(new InsnNode(Opcodes.LCONST_0)); // elapsedTime=0 + + if (has6) { + insn.add(new VarInsnNode(Opcodes.ALOAD, 7)); + insn.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, PACKET_INTERNAL, "", CTOR_AE2CL_6, false)); + insn.add(new InsnNode(Opcodes.RETURN)); + m.maxStack = 10; + m.maxLocals = 8; + } else { + insn.add(new InsnNode(Opcodes.ICONST_0)); // isFollowing=false + insn.add(new VarInsnNode(Opcodes.ALOAD, 7)); + insn.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, PACKET_INTERNAL, "", CTOR_AE2CL_7, false)); + insn.add(new InsnNode(Opcodes.RETURN)); + m.maxStack = 11; + m.maxLocals = 8; + } + + cn.methods.add(m); + } + + // ========================= + // PATCH: PatternDetailsHelper overload (no author) + // ========================= + private static void ensurePdhNoAuthorOverloadExists(ClassNode cn) { + if (hasMethod(cn, "encodeCraftingPattern", PDH_NO_AUTHOR)) return; + if (!hasMethod(cn, "encodeCraftingPattern", PDH_WITH_AUTHOR)) return; + + MethodNode m = new MethodNode( + Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + "encodeCraftingPattern", + PDH_NO_AUTHOR, + null, + null); + + InsnList insn = m.instructions; + insn.add(new VarInsnNode(Opcodes.ALOAD, 0)); + insn.add(new VarInsnNode(Opcodes.ALOAD, 1)); + insn.add(new VarInsnNode(Opcodes.ALOAD, 2)); + insn.add(new VarInsnNode(Opcodes.ILOAD, 3)); + insn.add(new VarInsnNode(Opcodes.ILOAD, 4)); + insn.add(new LdcInsnNode("")); + + insn.add(new MethodInsnNode(Opcodes.INVOKESTATIC, PDH_INTERNAL, "encodeCraftingPattern", PDH_WITH_AUTHOR, + false)); + insn.add(new InsnNode(Opcodes.ARETURN)); + + m.maxStack = 6; + m.maxLocals = 5; + cn.methods.add(m); + } + + // ========================= + // PATCH: ProcessingPatternItem.encode overload (no author) + // ========================= + private static void ensurePpiNoAuthorOverloadExists(ClassNode cn) { + if (hasMethod(cn, "encode", PPI_ENCODE_NO_AUTHOR)) return; + if (!hasMethod(cn, "encode", PPI_ENCODE_WITH_AUTHOR)) return; + + MethodNode m = new MethodNode(Opcodes.ACC_PUBLIC, "encode", PPI_ENCODE_NO_AUTHOR, null, null); + InsnList insn = m.instructions; + + insn.add(new VarInsnNode(Opcodes.ALOAD, 0)); + insn.add(new VarInsnNode(Opcodes.ALOAD, 1)); + insn.add(new VarInsnNode(Opcodes.ALOAD, 2)); + insn.add(new LdcInsnNode("")); + + insn.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, PPI_INTERNAL, "encode", PPI_ENCODE_WITH_AUTHOR, false)); + insn.add(new InsnNode(Opcodes.ARETURN)); + + m.maxStack = 4; + m.maxLocals = 3; + cn.methods.add(m); + } + + // ========================= + // PATCH: EncodedPatternItem.getStackComponent overload (no bool) + // ========================= + private static void ensureEpiNoBoolOverloadExists(ClassNode cn) { + if (hasMethod(cn, "getStackComponent", EPI_GET_STACK_COMPONENT_NO_BOOL)) return; + if (!hasMethod(cn, "getStackComponent", EPI_GET_STACK_COMPONENT_WITH_BOOL)) return; + + MethodNode m = new MethodNode( + Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC, + "getStackComponent", + EPI_GET_STACK_COMPONENT_NO_BOOL, + null, + null); + + InsnList insn = m.instructions; + insn.add(new VarInsnNode(Opcodes.ALOAD, 0)); + insn.add(new InsnNode(Opcodes.ICONST_0)); + insn.add(new MethodInsnNode( + Opcodes.INVOKESTATIC, EPI_INTERNAL, "getStackComponent", EPI_GET_STACK_COMPONENT_WITH_BOOL, false)); + insn.add(new InsnNode(Opcodes.ARETURN)); + + m.maxStack = 2; + m.maxLocals = 1; + cn.methods.add(m); + } + + // ========================= + // PATCH: GuiText.With enum constant + // ========================= + private static void ensureGuiTextWithEnumConstantExists(ClassNode cn) { + if (hasField(cn, "With", GUITEXT_ENUM_DESC)) return; + + String valuesField = findEnumValuesArrayFieldName(cn); + if (valuesField == null) return; + + String ctorDesc = null; + if (hasMethod(cn, "", GUITEXT_CTOR_3)) ctorDesc = GUITEXT_CTOR_3; + else if (hasMethod(cn, "", GUITEXT_CTOR_4)) ctorDesc = GUITEXT_CTOR_4; + else return; + + cn.fields.add(new FieldNode( + Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_ENUM, + "With", + GUITEXT_ENUM_DESC, + null, + null)); + + MethodNode clinit = null; + for (MethodNode m : cn.methods) { + if ("".equals(m.name)) { + clinit = m; + break; + } + } + if (clinit == null) return; + + AbstractInsnNode ret = null; + for (AbstractInsnNode insn = clinit.instructions.getLast(); insn != null; insn = insn.getPrevious()) { + if (insn.getOpcode() == Opcodes.RETURN) { + ret = insn; + break; + } + } + if (ret == null) return; + + int ordLocal = clinit.maxLocals; + int oldArrLocal = ordLocal + 1; + int newArrLocal = ordLocal + 2; + clinit.maxLocals += 3; + + InsnList patch = new InsnList(); + + patch.add(new FieldInsnNode(Opcodes.GETSTATIC, GUITEXT_INTERNAL, valuesField, GUITEXT_VALUES_ARRAY_DESC)); + patch.add(new VarInsnNode(Opcodes.ASTORE, oldArrLocal)); + + patch.add(new VarInsnNode(Opcodes.ALOAD, oldArrLocal)); + patch.add(new InsnNode(Opcodes.ARRAYLENGTH)); + patch.add(new VarInsnNode(Opcodes.ISTORE, ordLocal)); + + patch.add(new TypeInsnNode(Opcodes.NEW, GUITEXT_INTERNAL)); + patch.add(new InsnNode(Opcodes.DUP)); + patch.add(new LdcInsnNode("With")); + patch.add(new VarInsnNode(Opcodes.ILOAD, ordLocal)); + patch.add(new LdcInsnNode("with")); + if (GUITEXT_CTOR_4.equals(ctorDesc)) { + patch.add(new LdcInsnNode("gui.ae2")); + } + patch.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, GUITEXT_INTERNAL, "", ctorDesc, false)); + patch.add(new FieldInsnNode(Opcodes.PUTSTATIC, GUITEXT_INTERNAL, "With", GUITEXT_ENUM_DESC)); + + patch.add(new VarInsnNode(Opcodes.ILOAD, ordLocal)); + patch.add(new InsnNode(Opcodes.ICONST_1)); + patch.add(new InsnNode(Opcodes.IADD)); + patch.add(new TypeInsnNode(Opcodes.ANEWARRAY, GUITEXT_INTERNAL)); + patch.add(new VarInsnNode(Opcodes.ASTORE, newArrLocal)); + + patch.add(new VarInsnNode(Opcodes.ALOAD, oldArrLocal)); + patch.add(new InsnNode(Opcodes.ICONST_0)); + patch.add(new VarInsnNode(Opcodes.ALOAD, newArrLocal)); + patch.add(new InsnNode(Opcodes.ICONST_0)); + patch.add(new VarInsnNode(Opcodes.ILOAD, ordLocal)); + patch.add(new MethodInsnNode( + Opcodes.INVOKESTATIC, + "java/lang/System", + "arraycopy", + "(Ljava/lang/Object;ILjava/lang/Object;II)V", + false)); + + patch.add(new VarInsnNode(Opcodes.ALOAD, newArrLocal)); + patch.add(new VarInsnNode(Opcodes.ILOAD, ordLocal)); + patch.add(new FieldInsnNode(Opcodes.GETSTATIC, GUITEXT_INTERNAL, "With", GUITEXT_ENUM_DESC)); + patch.add(new InsnNode(Opcodes.AASTORE)); + + patch.add(new VarInsnNode(Opcodes.ALOAD, newArrLocal)); + patch.add(new FieldInsnNode(Opcodes.PUTSTATIC, GUITEXT_INTERNAL, valuesField, GUITEXT_VALUES_ARRAY_DESC)); + + clinit.maxStack = Math.max(clinit.maxStack, 8); + clinit.instructions.insertBefore(ret, patch); + } + + private static String findEnumValuesArrayFieldName(ClassNode cn) { + for (FieldNode f : cn.fields) { + if (GUITEXT_VALUES_ARRAY_DESC.equals(f.desc) && (f.access & Opcodes.ACC_STATIC) != 0) { + if ((f.access & Opcodes.ACC_SYNTHETIC) != 0) return f.name; + } + } + for (FieldNode f : cn.fields) { + if (GUITEXT_VALUES_ARRAY_DESC.equals(f.desc) && (f.access & Opcodes.ACC_STATIC) != 0) { + return f.name; + } + } + return null; + } + + // ========================= + // Helpers + // ========================= + private static boolean hasMethod(ClassNode cn, String name, String desc) { + return findMethod(cn, name, desc) != null; + } + + private static MethodNode findMethod(ClassNode cn, String name, String desc) { + for (MethodNode m : cn.methods) { + if (name.equals(m.name) && desc.equals(m.desc)) return m; + } + return null; + } + + private static boolean hasField(ClassNode cn, String name, String desc) { + for (FieldNode f : cn.fields) { + if (name.equals(f.name) && desc.equals(f.desc)) return true; + } + return false; + } + + private static void logPatch(String target, String what, boolean ok) { + if (VERBOSE) { + LOGGER.info("[MoniMixinPlugin] PATCH {} | {} => {}", target, what, ok ? "OK" : "FAILED"); + } + } +} diff --git a/src/main/java/net/neganote/monilabs/mixin/aae/MixinCraftingJobStatusPacketCtorShim.java b/src/main/java/net/neganote/monilabs/mixin/aae/MixinCraftingJobStatusPacketCtorShim.java new file mode 100644 index 00000000..34a2f6ba --- /dev/null +++ b/src/main/java/net/neganote/monilabs/mixin/aae/MixinCraftingJobStatusPacketCtorShim.java @@ -0,0 +1,7 @@ +package net.neganote.monilabs.mixin.aae; + +import appeng.core.sync.packets.CraftingJobStatusPacket; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(CraftingJobStatusPacket.class) +public abstract class MixinCraftingJobStatusPacketCtorShim {} diff --git a/src/main/java/net/neganote/monilabs/mixin/aae/MixinCraftingServiceAAE.java b/src/main/java/net/neganote/monilabs/mixin/aae/MixinCraftingServiceAAE.java new file mode 100644 index 00000000..cc139689 --- /dev/null +++ b/src/main/java/net/neganote/monilabs/mixin/aae/MixinCraftingServiceAAE.java @@ -0,0 +1,292 @@ +package net.neganote.monilabs.mixin.aae; + +import net.minecraft.nbt.CompoundTag; +import net.pedroksl.advanced_ae.common.cluster.AdvCraftingCPUCluster; +import net.pedroksl.advanced_ae.common.entities.AdvCraftingBlockEntity; + +import appeng.api.config.Actionable; +import appeng.api.networking.IGrid; +import appeng.api.networking.IGridNode; +import appeng.api.networking.crafting.*; +import appeng.api.networking.energy.IEnergyService; +import appeng.api.networking.security.IActionSource; +import appeng.api.stacks.AEKey; +import appeng.crafting.CraftingLink; +import appeng.crafting.execution.CraftingSubmitResult; +import appeng.me.cluster.implementations.CraftingCPUCluster; +import appeng.me.service.CraftingService; +import com.google.common.collect.ImmutableSet; +import org.apache.commons.lang3.mutable.MutableObject; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; + +/** + * It's just the original AAE mixin + * but fixed to work with ae2cl + */ +@Mixin(value = CraftingService.class, remap = false) +public class MixinCraftingServiceAAE { + + @Unique + private static final Comparator FAST_FIRST_COMPARATOR = Comparator.comparingInt( + AdvCraftingCPUCluster::getCoProcessors) + .reversed() + .thenComparingLong(AdvCraftingCPUCluster::getAvailableStorage); + + @Unique + private final Set advancedAE$advCraftingCPUClusters = new HashSet<>(); + + @Shadow + private long lastProcessedCraftingLogicChangeTick; + + @Final + @Shadow + private Set craftingCPUClusters; + + @Final + @Shadow + private IGrid grid; + + @Final + @Shadow + private IEnergyService energyGrid; + + @Shadow + private boolean updateList; + + @Shadow + public void addLink(CraftingLink link) {} + + @Shadow + private void updateCPUClusters() {} + + @Final + @Shadow + private Set currentlyCrafting; + + @Inject( + method = "onServerEndTick", + at = @At( + value = "FIELD", + target = "Lappeng/me/service/CraftingService;lastProcessedCraftingLogicChangeTick:J", + opcode = Opcodes.GETFIELD, + ordinal = 0), + locals = LocalCapture.CAPTURE_FAILHARD) + private void tickAdvClusters1(CallbackInfo ci, long latestChange) { + long latestChangeLocal = 0; + for (var cluster : this.advancedAE$advCraftingCPUClusters) { + if (cluster != null) { + for (var cpu : cluster.getActiveCPUs()) { + cpu.craftingLogic.tickCraftingLogic(energyGrid, (CraftingService) (Object) this); + latestChangeLocal = Math.max(latestChangeLocal, cpu.craftingLogic.getLastModifiedOnTick()); + } + } + } + + if (latestChangeLocal > latestChange) { + this.lastProcessedCraftingLogicChangeTick = -1; + } + } + + @Inject( + method = "onServerEndTick", + at = @At( + value = "FIELD", + target = "Lappeng/me/service/CraftingService;interests:Lcom/google/common/collect/Multimap;", + opcode = Opcodes.GETFIELD, + ordinal = 0)) + private void tickAdvClusters2(CallbackInfo ci) { + for (var cluster : this.advancedAE$advCraftingCPUClusters) { + if (cluster != null) { + for (var cpu : cluster.getActiveCPUs()) { + cpu.craftingLogic.getAllWaitingFor(this.currentlyCrafting); + } + } + } + } + + @Inject(method = "removeNode", at = @At("TAIL")) + private void onRemoveNode(IGridNode gridNode, CallbackInfo ci) { + if (gridNode.getOwner() instanceof AdvCraftingBlockEntity) { + this.updateList = true; + } + } + + @Inject(method = "addNode", at = @At("TAIL")) + private void onAddNode(IGridNode gridNode, CompoundTag savedData, CallbackInfo ci) { + if (gridNode.getOwner() instanceof AdvCraftingBlockEntity) { + this.updateList = true; + } + } + + @Inject(method = "updateCPUClusters", at = @At("TAIL")) + private void onUpdateCPUClusters(CallbackInfo ci) { + this.advancedAE$advCraftingCPUClusters.clear(); + + for (var blockEntity : this.grid.getMachines(AdvCraftingBlockEntity.class)) { + final AdvCraftingCPUCluster cluster = blockEntity.getCluster(); + if (cluster != null) { + this.advancedAE$advCraftingCPUClusters.add(cluster); + + for (var cpu : cluster.getActiveCPUs()) { + ICraftingLink maybeLink = cpu.craftingLogic.getLastLink(); + if (maybeLink != null) { + this.addLink((CraftingLink) maybeLink); + } + } + } + } + } + + /** + * @author Pedroksl + * @reason Add Advanced CPU Clusters to this method + */ + @Overwrite + public long insertIntoCpus(AEKey what, long amount, Actionable type) { + long inserted = 0; + for (var cpu : this.craftingCPUClusters) { + inserted += cpu.craftingLogic.insert(what, amount - inserted, type); + } + + for (var cluster : this.advancedAE$advCraftingCPUClusters) { + if (cluster != null) { + for (var cpu : cluster.getActiveCPUs()) { + inserted += cpu.craftingLogic.insert(what, amount - inserted, type); + } + } + } + return inserted; + } + + @Inject( + method = "submitJob(Lappeng/api/networking/crafting/ICraftingPlan;Lappeng/api/networking/crafting/ICraftingRequester;Lappeng/api/networking/crafting/ICraftingCPU;ZLappeng/api/networking/security/IActionSource;Z)Lappeng/api/networking/crafting/ICraftingSubmitResult;", + at = @At( + value = "INVOKE_ASSIGN", + target = "appeng/me/service/CraftingService.findSuitableCraftingCPU " + + "(Lappeng/api/networking/crafting/ICraftingPlan;ZLappeng/api/networking/security/IActionSource;" + + "Lorg/apache/commons/lang3/mutable/MutableObject;)" + + "Lappeng/me/cluster/implementations/CraftingCPUCluster;"), + cancellable = true, + locals = LocalCapture.CAPTURE_FAILHARD) + private void onSubmitJob( + ICraftingPlan job, + ICraftingRequester requestingMachine, + ICraftingCPU target, + boolean prioritizePower, + IActionSource src, + boolean isFollowing, + CallbackInfoReturnable cir, + CraftingCPUCluster cpuCluster, + MutableObject unsuitableCpusResult) { + if (target instanceof AdvCraftingCPUCluster advCpuCluster) { + cir.setReturnValue(advCpuCluster.submitJob(this.grid, job, src, requestingMachine)); + } else { + var advCluster = advancedAE$findSuitableAdvCraftingCPU(job, src, unsuitableCpusResult); + if (advCluster != null) { + updateList = true; + cir.setReturnValue(advCluster.submitJob(this.grid, job, src, requestingMachine)); + } else if (cpuCluster == null) { + var unsuitableCpus = unsuitableCpusResult.getValue(); + if (unsuitableCpus == null) { + cir.setReturnValue(CraftingSubmitResult.NO_CPU_FOUND); + } else { + cir.setReturnValue(CraftingSubmitResult.noSuitableCpu(unsuitableCpus)); + } + } + } + } + + @Unique + private AdvCraftingCPUCluster advancedAE$findSuitableAdvCraftingCPU( + ICraftingPlan job, IActionSource src, + MutableObject unsuitableCpusResult) { + var validCpusClusters = new ArrayList(this.advancedAE$advCraftingCPUClusters.size()); + int offline = 0; + int tooSmall = 0; + int excluded = 0; + + for (var cluster : this.advancedAE$advCraftingCPUClusters) { + if (!cluster.isActive()) { + offline++; + continue; + } + if (cluster.getAvailableStorage() < job.bytes()) { + tooSmall++; + continue; + } + if (!cluster.canBeAutoSelectedFor(src)) { + excluded++; + continue; + } + validCpusClusters.add(cluster); + } + + if (validCpusClusters.isEmpty()) { + if (offline > 0 || tooSmall > 0 || excluded > 0) { + unsuitableCpusResult.setValue(new UnsuitableCpus(offline, 0, tooSmall, excluded)); + } + return null; + } + + validCpusClusters.sort((a, b) -> { + var firstPreferred = a.isPreferredFor(src); + var secondPreferred = b.isPreferredFor(src); + if (firstPreferred != secondPreferred) { + return Boolean.compare(secondPreferred, firstPreferred); + } + return FAST_FIRST_COMPARATOR.compare(a, b); + }); + + return validCpusClusters.get(0); + } + + @Inject(method = "getCpus", at = @At("RETURN"), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD) + private void onGetCpus( + CallbackInfoReturnable> cir, + ImmutableSet.Builder cpus) { + for (var cluster : this.advancedAE$advCraftingCPUClusters) { + for (var cpu : cluster.getActiveCPUs()) { + cpus.add(cpu); + } + cpus.add(cluster.getRemainingCapacityCPU()); + } + cir.setReturnValue(cpus.build()); + } + + @Inject( + method = "getRequestedAmount", + at = @At("RETURN"), + cancellable = true, + locals = LocalCapture.CAPTURE_FAILHARD) + private void onGetRequestedAmount(AEKey what, CallbackInfoReturnable cir, long requested) { + for (var cluster : this.advancedAE$advCraftingCPUClusters) { + for (var cpu : cluster.getActiveCPUs()) { + requested += cpu.craftingLogic.getWaitingFor(what); + } + } + cir.setReturnValue(requested); + } + + @Inject(method = "hasCpu", at = @At("HEAD"), cancellable = true) + private void onHasCpu(ICraftingCPU cpu, CallbackInfoReturnable cir) { + for (var cluster : this.advancedAE$advCraftingCPUClusters) { + for (var activeCpu : cluster.getActiveCPUs()) { + if (activeCpu == cpu) { + cir.setReturnValue(true); + return; + } + } + } + } +} diff --git a/src/main/java/net/neganote/monilabs/mixin/aae/MixinEncodedPatternItemShim.java b/src/main/java/net/neganote/monilabs/mixin/aae/MixinEncodedPatternItemShim.java new file mode 100644 index 00000000..93f10d28 --- /dev/null +++ b/src/main/java/net/neganote/monilabs/mixin/aae/MixinEncodedPatternItemShim.java @@ -0,0 +1,7 @@ +package net.neganote.monilabs.mixin.aae; + +import appeng.crafting.pattern.EncodedPatternItem; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(EncodedPatternItem.class) +public abstract class MixinEncodedPatternItemShim {} diff --git a/src/main/java/net/neganote/monilabs/mixin/aae/MixinGuiTextShim.java b/src/main/java/net/neganote/monilabs/mixin/aae/MixinGuiTextShim.java new file mode 100644 index 00000000..dd40a595 --- /dev/null +++ b/src/main/java/net/neganote/monilabs/mixin/aae/MixinGuiTextShim.java @@ -0,0 +1,7 @@ +package net.neganote.monilabs.mixin.aae; + +import appeng.core.localization.GuiText; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(GuiText.class) +public abstract class MixinGuiTextShim {} diff --git a/src/main/java/net/neganote/monilabs/mixin/aae/MixinPatternDetailsHelperShim.java b/src/main/java/net/neganote/monilabs/mixin/aae/MixinPatternDetailsHelperShim.java new file mode 100644 index 00000000..b3837536 --- /dev/null +++ b/src/main/java/net/neganote/monilabs/mixin/aae/MixinPatternDetailsHelperShim.java @@ -0,0 +1,7 @@ +package net.neganote.monilabs.mixin.aae; + +import appeng.api.crafting.PatternDetailsHelper; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(PatternDetailsHelper.class) +public abstract class MixinPatternDetailsHelperShim {} diff --git a/src/main/java/net/neganote/monilabs/mixin/aae/MixinProcessingPatternItemShim.java b/src/main/java/net/neganote/monilabs/mixin/aae/MixinProcessingPatternItemShim.java new file mode 100644 index 00000000..bf9cf221 --- /dev/null +++ b/src/main/java/net/neganote/monilabs/mixin/aae/MixinProcessingPatternItemShim.java @@ -0,0 +1,7 @@ +package net.neganote.monilabs.mixin.aae; + +import appeng.crafting.pattern.ProcessingPatternItem; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(ProcessingPatternItem.class) +public abstract class MixinProcessingPatternItemShim {} diff --git a/src/main/java/net/neganote/monilabs/mixin/mixinsquared/MoniMixinCanceller.java b/src/main/java/net/neganote/monilabs/mixin/mixinsquared/MoniMixinCanceller.java new file mode 100644 index 00000000..5255d9a8 --- /dev/null +++ b/src/main/java/net/neganote/monilabs/mixin/mixinsquared/MoniMixinCanceller.java @@ -0,0 +1,27 @@ +package net.neganote.monilabs.mixin.mixinsquared; + +import com.bawnorton.mixinsquared.api.MixinCanceller; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.List; +import java.util.Objects; + +public class MoniMixinCanceller implements MixinCanceller { + + private static final String AAE_CRAFTING_SERVICE_MIXIN = "net.pedroksl.advanced_ae.mixins.cpu.MixinCraftingService"; + private static final Logger LOGGER = LogManager.getLogger("MoniLabs-MixinSquared"); + private static final boolean VERBOSE = Boolean.parseBoolean(System.getProperty("monilabs.mixin.verbose", "false")); + + @Override + public boolean shouldCancel(List targetClassNames, String mixinClassName) { + // We cancel whole AAE mixin so we can implement our own that works with ae2cl + if (Objects.equals(mixinClassName, AAE_CRAFTING_SERVICE_MIXIN)) { + if (VERBOSE) { + LOGGER.info("Skipping: {}", mixinClassName); + } + return true; + } + return false; + } +} diff --git a/src/main/resources/META-INF/services/com.bawnorton.mixinsquared.api.MixinCanceller b/src/main/resources/META-INF/services/com.bawnorton.mixinsquared.api.MixinCanceller new file mode 100644 index 00000000..0a1864bd --- /dev/null +++ b/src/main/resources/META-INF/services/com.bawnorton.mixinsquared.api.MixinCanceller @@ -0,0 +1 @@ +net.neganote.monilabs.mixin.mixinsquared.MoniMixinCanceller \ No newline at end of file diff --git a/src/main/resources/monilabs.mixins.json b/src/main/resources/monilabs.mixins.json index f65faf58..7a396b8d 100644 --- a/src/main/resources/monilabs.mixins.json +++ b/src/main/resources/monilabs.mixins.json @@ -3,12 +3,19 @@ "package": "net.neganote.monilabs.mixin", "compatibilityLevel": "JAVA_17", "minVersion": "0.8", + "plugin": "net.neganote.monilabs.mixin.MoniMixinPlugin", "mixins": [ "DataAccessHatchMixin", "KubeJSPluginsMixin", "NotifiableEnergyContainerMixin", "OpticalDataHatchMixin", - "PowerSubstationMachineMixin" + "PowerSubstationMachineMixin", + "aae.MixinCraftingJobStatusPacketCtorShim", + "aae.MixinCraftingServiceAAE", + "aae.MixinEncodedPatternItemShim", + "aae.MixinGuiTextShim", + "aae.MixinPatternDetailsHelperShim", + "aae.MixinProcessingPatternItemShim" ], "client": [ ],