From e18c5966697dd144cbbbc5411aad37c36f86ecbe Mon Sep 17 00:00:00 2001 From: SoniEx2 Date: Thu, 7 Jan 2016 18:53:53 -0200 Subject: [PATCH] Correctly emulate vanilla behaviour --- build.gradle | 2 +- .../github/soniex2/notebetter/NoteBetter.java | 2 +- .../soniex2/notebetter/NoteMethods.java | 3 + .../asm/NoteBetterClassTransformer.java | 71 +++++++++++++++++- .../notebetter/event/WorkaroundHandler.java | 73 ++++++++++++++----- 5 files changed, 127 insertions(+), 24 deletions(-) diff --git a/build.gradle b/build.gradle index d155941..9654f80 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ plugins { } */ -version = "0.2.3" +version = "0.2.4" group = "com.github.soniex2.notebetter" // http://maven.apache.org/guides/mini/guide-naming-conventions.html archivesBaseName = "notebetter" diff --git a/src/main/java/com/github/soniex2/notebetter/NoteBetter.java b/src/main/java/com/github/soniex2/notebetter/NoteBetter.java index 80e3c0c..6ce957f 100644 --- a/src/main/java/com/github/soniex2/notebetter/NoteBetter.java +++ b/src/main/java/com/github/soniex2/notebetter/NoteBetter.java @@ -16,7 +16,7 @@ guiFactory = "com.github.soniex2.notebetter.gui.NoteBetterGuiFactory") public class NoteBetter { public static final String MODID = "notebetter"; - public static final String VERSION = "0.2.3"; + public static final String VERSION = "0.2.4"; @Mod.Instance public static NoteBetter instance; diff --git a/src/main/java/com/github/soniex2/notebetter/NoteMethods.java b/src/main/java/com/github/soniex2/notebetter/NoteMethods.java index 226d339..d31be68 100644 --- a/src/main/java/com/github/soniex2/notebetter/NoteMethods.java +++ b/src/main/java/com/github/soniex2/notebetter/NoteMethods.java @@ -16,6 +16,8 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.world.NoteBlockEvent; +import java.util.logging.Logger; + /** * @author soniex2 */ @@ -37,6 +39,7 @@ private static boolean tryPlay(World world, BlockPos pos, NoteBetterInstrument i public static boolean handleTileEntity(World world, BlockPos pos) { if (world.getBlockState(pos.up()).getBlock().getMaterial() == Material.air) { + //NoteBetter.instance.log.info("PLAYNOTE: " + world.getWorldTime()); if (!(world.getTileEntity(pos) instanceof TileEntityNote)) return false; TileEntityNote te = ((TileEntityNote) world.getTileEntity(pos)); diff --git a/src/main/java/com/github/soniex2/notebetter/asm/NoteBetterClassTransformer.java b/src/main/java/com/github/soniex2/notebetter/asm/NoteBetterClassTransformer.java index aded6b4..f464562 100644 --- a/src/main/java/com/github/soniex2/notebetter/asm/NoteBetterClassTransformer.java +++ b/src/main/java/com/github/soniex2/notebetter/asm/NoteBetterClassTransformer.java @@ -11,6 +11,9 @@ import java.util.Map; /** + * This class adds 2 things to 2 methods in 2 different classes. This class adds a simple hook to + * WorldServer.sendQueuedBlockEvents and a cancelable hook to TileEntityNote.triggerNote. + * * @author soniex2 */ public class NoteBetterClassTransformer implements IClassTransformer { @@ -19,16 +22,26 @@ public class NoteBetterClassTransformer implements IClassTransformer { private void setupDeobfMaps() { maps = new HashMap(); + /*TileEntityNote*/ maps.put("te_mname", "triggerNote"); maps.put("te_mdesc", "(Lnet/minecraft/world/World;Lnet/minecraft/util/BlockPos;)V"); maps.put("nm_te_hndlr_desc", "(Lnet/minecraft/world/World;Lnet/minecraft/util/BlockPos;)Z"); + /*WorldServer*/ + maps.put("ws_mname", "sendQueuedBlockEvents"); + maps.put("ws_mdesc", "()V"); + maps.put("nm_ws_hook_desc", "(Lnet/minecraft/world/World;)V"); } private void setupObfMaps() { maps = new HashMap(); + /*TileEntityNote*/ maps.put("te_mname", "func_175108_a"); maps.put("te_mdesc", "(Lnet/minecraft/world/World;Lnet/minecraft/util/BlockPos;)V"); maps.put("nm_te_hndlr_desc", "(Lnet/minecraft/world/World;Lnet/minecraft/util/BlockPos;)Z"); + /*WorldServer*/ + maps.put("ws_mname", "func_147488_Z"); + maps.put("ws_mdesc", "()V"); + maps.put("nm_ws_hook_desc", "(Lnet/minecraft/world/World;)V"); } @Override @@ -36,6 +49,8 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) isObfuscated = !name.equals(transformedName); if (transformedName.equals("net.minecraft.tileentity.TileEntityNote")) return transformTileEntityNote(basicClass); + else if (transformedName.equals("net.minecraft.world.WorldServer")) + return transformWorldServer(basicClass); return basicClass; } @@ -61,10 +76,10 @@ private byte[] transformTileEntityNote(byte[] basicClass) { VarInsnNode vigetPos = new VarInsnNode(Opcodes.ALOAD, 2); it.add(vigetPos); MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, - "com/github/soniex2/notebetter/NoteMethods", - "handleTileEntity", - maps.get("nm_te_hndlr_desc"), - false); + "com/github/soniex2/notebetter/NoteMethods", + "handleTileEntity", + maps.get("nm_te_hndlr_desc"), + false); it.add(call); LabelNode cont = new LabelNode(); JumpInsnNode test = new JumpInsnNode(Opcodes.IFEQ, cont); @@ -94,4 +109,52 @@ private byte[] transformTileEntityNote(byte[] basicClass) { return basicClass; } + + private byte[] transformWorldServer(byte[] basicClass) { + if (isObfuscated) setupObfMaps(); + else setupDeobfMaps(); + + System.out.println("Transforming WorldServer"); + + try { + ClassNode classNode = new ClassNode(); + ClassReader classReader = new ClassReader(basicClass); + classReader.accept(classNode, 0); + + boolean transformed = false; + + for (MethodNode m : classNode.methods) { + if (m.name.equals(maps.get("ws_mname")) && m.desc.equals(maps.get("ws_mdesc"))) { + ListIterator it = m.instructions.iterator(); + System.out.println("Transforming WorldServer.sendQueuedBlockEvents"); + VarInsnNode vigetWorld = new VarInsnNode(Opcodes.ALOAD, 0); + it.add(vigetWorld); + MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, + "com/github/soniex2/notebetter/event/WorkaroundHandler", + "sendNoteUpdates", + maps.get("nm_ws_hook_desc"), + false); + it.add(call); + System.out.println("Done!"); + transformed = true; + } + } + if (!transformed) + System.out.println("Failed to transform WorldServer.sendQueuedBlockEvents"); + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + classNode.accept(writer); + + if (transformed) + System.out.println("Transforming WorldServer - Success!"); + else + System.out.println("Transforming WorldServer - Failed!"); + return writer.toByteArray(); + } catch (Exception e) { + System.out.println("Transforming WorldServer - Failed!"); + e.printStackTrace(); + } + + return basicClass; + } } diff --git a/src/main/java/com/github/soniex2/notebetter/event/WorkaroundHandler.java b/src/main/java/com/github/soniex2/notebetter/event/WorkaroundHandler.java index b04dd12..d77eac2 100644 --- a/src/main/java/com/github/soniex2/notebetter/event/WorkaroundHandler.java +++ b/src/main/java/com/github/soniex2/notebetter/event/WorkaroundHandler.java @@ -1,5 +1,6 @@ package com.github.soniex2.notebetter.event; +import com.github.soniex2.notebetter.NoteBetter; import com.github.soniex2.notebetter.util.EventHelper; import net.minecraft.util.BlockPos; import net.minecraft.util.EnumParticleTypes; @@ -10,11 +11,9 @@ import net.minecraftforge.event.world.NoteBlockEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.minecraftforge.fml.relauncher.Side; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.WeakHashMap; +import java.util.*; /** * @author soniex2 @@ -48,6 +47,7 @@ public boolean equals(Object other) { } public void play(World world) { + NoteBetter.instance.log.info("PLAY: " + world.getWorldTime()); // Our event /*NoteBetterEvent.Play event = new NoteBetterEvent.Play(world, pos, world.getBlockState(pos), note, instrument); if (MinecraftForge.EVENT_BUS.post(event)) return; @@ -74,31 +74,68 @@ public void play(World world) { world.playSoundEffect(x, y, z, instrument.toString(), volume, pitch); if (world instanceof WorldServer) // just in case it *isn't* being called from a WorldServer ((WorldServer) world).spawnParticle(EnumParticleTypes.NOTE, false, x, y + .7, z, 0, note / 24.0, 0.0, 0.0, 1.0); + //NoteBetter.instance.log.info("PLAYWORKAROUND: " + world.getWorldTime()); } } - public static final Map> map = Collections.synchronizedMap(new WeakHashMap>()); + public static final Map map = Collections.synchronizedMap(new WeakHashMap()); public static void addNoteTickWorkaround(World world, NoteTickWorkaround workaround) { + NoteTickWorkaroundWorldData data; synchronized (map) { - LinkedHashSet set = map.get(world); - if (set == null) map.put(world, set = new LinkedHashSet()); - set.add(workaround); + data = map.get(world); + if (data == null) + map.put(world, data = new NoteTickWorkaroundWorldData()); } + data.add(workaround); } - @SubscribeEvent + /*@SubscribeEvent public void onWorldTick(TickEvent.WorldTickEvent event) { - if (event.phase == TickEvent.Phase.END && event.type == TickEvent.Type.WORLD) { - synchronized (map) { - for (Map.Entry> entry : map.entrySet()) { - World w = entry.getKey(); - LinkedHashSet set = entry.getValue(); - for (NoteTickWorkaround ntw : set) { - ntw.play(w); - } - set.clear(); + if (event.phase == TickEvent.Phase.END && event.type == TickEvent.Type.WORLD && event.side == Side.SERVER) { + NoteTickWorkaroundWorldData data = map.get(event.world); + if (data == null) return; + data.sendQueuedBlockEvents(event.world); + } + }*/ + + // This needs to happen between the start of the tick and TileEntity updates, but also after block ticks. + public static void sendNoteUpdates(World world) { + NoteTickWorkaroundWorldData data = map.get(world); + if (data == null) return; + data.sendQueuedBlockEvents(world); + } + + @SubscribeEvent + public void onNotePlay(NoteBlockEvent.Play event) { + if (!event.world.isRemote) NoteBetter.instance.log.info("PLAYEVENT: " + event.world.getWorldTime()); + } + + private static class NoteTickWorkaroundWorldData { + private int blockEventCacheIndex = 0; + @SuppressWarnings("unchecked") + private List[] blockEventCaches = new ArrayList[] { + new ArrayList(), new ArrayList() + }; + + public synchronized void add(NoteTickWorkaround workaround) { + for (NoteTickWorkaround blockeventdata1 : blockEventCaches[blockEventCacheIndex]) { + if (blockeventdata1.equals(workaround)) return; + } + + blockEventCaches[blockEventCacheIndex].add(workaround); + } + + private synchronized void sendQueuedBlockEvents(World w) { + while (!blockEventCaches[blockEventCacheIndex].isEmpty()) { + int i = blockEventCacheIndex; + blockEventCacheIndex ^= 1; + + for (NoteTickWorkaround blockeventdata : blockEventCaches[i]) { + blockeventdata.play(w); } + + this.blockEventCaches[i].clear(); } } }