diff --git a/src/main/java/eu/crushedpixel/replaymod/coremod/LoadingPlugin.java b/src/main/java/eu/crushedpixel/replaymod/coremod/LoadingPlugin.java index bdc3df25..663d29fa 100755 --- a/src/main/java/eu/crushedpixel/replaymod/coremod/LoadingPlugin.java +++ b/src/main/java/eu/crushedpixel/replaymod/coremod/LoadingPlugin.java @@ -12,6 +12,7 @@ public class LoadingPlugin implements IFMLLoadingPlugin { CameraRollCT.class.getName(), ForceChunkLoadingCT.class.getName(), EntityRendererCT.class.getName(), + RenderManagerCT.class.getName(), SoundManagerCT.class.getName(), EnchantmentTimerCT.class.getName() }; diff --git a/src/main/java/eu/crushedpixel/replaymod/coremod/RenderManagerCT.java b/src/main/java/eu/crushedpixel/replaymod/coremod/RenderManagerCT.java new file mode 100644 index 00000000..b1f11c99 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/coremod/RenderManagerCT.java @@ -0,0 +1,81 @@ +package eu.crushedpixel.replaymod.coremod; + +import net.minecraft.launchwrapper.IClassTransformer; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.*; + +import java.util.ListIterator; + +import static org.objectweb.asm.Opcodes.*; + +/** + * Transforms the RenderManager class: + * - Adds a new field 'CubicEntityRenderer hook' + * - Injects CubicEntityRenderer.beforeEntityRender(dx, dy, dz) call at beginning of 'doRenderEntity' + */ +public class RenderManagerCT implements IClassTransformer { + + private static final String HOOK = "eu.crushedpixel.replaymod.video.entity.CubicEntityRenderer"; + private static final String HOOK_JVM = HOOK.replace('.', '/'); + private static final String HOOK_TYPE = "L" + HOOK_JVM + ";"; + private static final String RENDER_MANAGER = "net.minecraft.client.renderer.entity.RenderManager"; + + @Override + public byte[] transform(String name, String transformedName, byte[] bytes) { + if (RENDER_MANAGER.equals(transformedName)) { + if (name.equals(transformedName)) { + return transformRenderManager(bytes, "doRenderEntity", "(Lnet/minecraft/entity/Entity;DDDFFZ)Z"); + } else { + return transformRenderManager(bytes, "a", "(Lwv;DDDFFZ)Z"); + } + } + return bytes; + } + + private byte[] transformRenderManager(byte[] bytes, String name_doRenderEntity, String desc_doRenderEntity) { + ClassReader classReader = new ClassReader(bytes); + ClassNode classNode = new ClassNode(); + classReader.accept(classNode, 0); + + // Add field + classNode.visitField(ACC_PUBLIC, "hook", HOOK_TYPE, null, null); + + // Find method + MethodNode doRenderEntity = null; + for (MethodNode m : classNode.methods) { + if (name_doRenderEntity.equals(m.name) && desc_doRenderEntity.equals(m.desc)) { + doRenderEntity = m; + } + } + if (doRenderEntity == null) { + throw new NoSuchMethodError("doRenderEntity"); + } + + injectDoRenderEntity(classNode, doRenderEntity); + + ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(classWriter); + return classWriter.toByteArray(); + } + + private MethodNode injectDoRenderEntity(ClassNode classNode, MethodNode method) { + ListIterator list = method.instructions.iterator(); + list.add(new VarInsnNode(ALOAD, 0)); + list.add(new FieldInsnNode(GETFIELD, classNode.name, "hook", HOOK_TYPE)); + LabelNode labelNull = new LabelNode(); + list.add(new JumpInsnNode(IFNULL, labelNull)); + + list.add(new VarInsnNode(ALOAD, 0)); // this + list.add(new FieldInsnNode(GETFIELD, classNode.name, "hook", HOOK_TYPE)); + list.add(new VarInsnNode(DLOAD, 2)); // x + list.add(new VarInsnNode(DLOAD, 4)); // y + list.add(new VarInsnNode(DLOAD, 6)); // z + list.add(new MethodInsnNode(INVOKEVIRTUAL, HOOK_JVM, "beforeEntityRender", "(DDD)V", false)); + + list.add(labelNull); + list.add(new FrameNode(F_SAME, 0, null, 0, null)); + + return method; + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/entity/CubicEntityRenderer.java b/src/main/java/eu/crushedpixel/replaymod/video/entity/CubicEntityRenderer.java index 46b15c70..7420ab42 100644 --- a/src/main/java/eu/crushedpixel/replaymod/video/entity/CubicEntityRenderer.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/entity/CubicEntityRenderer.java @@ -5,9 +5,12 @@ import eu.crushedpixel.replaymod.settings.RenderOptions; import net.minecraft.client.particle.EntityFX; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.entity.Entity; import net.minecraft.util.ResourceLocation; +import java.lang.reflect.Field; + public class CubicEntityRenderer extends CustomEntityRenderer { public enum Direction { @@ -39,6 +42,28 @@ public class CubicEntityRenderer extends CustomEntityRenderer { public CubicEntityRenderer(RenderOptions options, int frameSize, boolean stable) { super(options, frameSize, frameSize); this.stable = stable; + + try { + Field hookField = RenderManager.class.getField("hook"); + hookField.set(mc.getRenderManager(), this); + } catch (NoSuchFieldException e) { + throw new Error(e); + } catch (IllegalAccessException e) { + throw new Error(e); + } + } + + @Override + public void cleanup() { + super.cleanup(); + try { + Field hookField = RenderManager.class.getField("hook"); + hookField.set(mc.getRenderManager(), null); + } catch (NoSuchFieldException e) { + throw new Error(e); + } catch (IllegalAccessException e) { + throw new Error(e); + } } public void setFrame(int id) { @@ -144,4 +169,12 @@ public class CubicEntityRenderer extends CustomEntityRenderer { super.renderParticle(fx, worldRenderer, view, partialTicks, rotX, rotXZ, rotZ, rotYZ, rotXY); } + + @SuppressWarnings("unused") // Called by ASM + public void beforeEntityRender(double dx, double dy, double dz) { + double pitch = -Math.atan2(dy, Math.sqrt(dx * dx + dz * dz)); + double yaw = -Math.atan2(dx, dz); + mc.getRenderManager().playerViewX = (float) Math.toDegrees(pitch); + mc.getRenderManager().playerViewY = (float) Math.toDegrees(yaw); + } }