From 29ef38213f03c7ce808bc398e01d92bd7660683b Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 7 Dec 2021 13:50:53 +0100 Subject: [PATCH] Fix vanilla entity update packet handling (fixes #607) --- .../com/replaymod/core/versions/MCVer.java | 13 ++- .../com/replaymod/core/versions/Patterns.java | 10 ++ .../com/replaymod/replay/ext/EntityExt.java | 9 ++ .../entity_tracking/Mixin_EntityExt.java | 36 ++++++ .../Mixin_FixPartialUpdates.java | 109 ++++++++++++++++++ .../resources/mixins.replay.replaymod.json | 2 + 6 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/replaymod/replay/ext/EntityExt.java create mode 100644 src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_EntityExt.java create mode 100644 src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_FixPartialUpdates.java diff --git a/src/main/java/com/replaymod/core/versions/MCVer.java b/src/main/java/com/replaymod/core/versions/MCVer.java index ac4dd754..cb7c7f00 100644 --- a/src/main/java/com/replaymod/core/versions/MCVer.java +++ b/src/main/java/com/replaymod/core/versions/MCVer.java @@ -13,6 +13,12 @@ import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.render.BufferBuilder; import net.minecraft.util.Identifier; import net.minecraft.util.Util; +import net.minecraft.util.math.Vec3d; + +//#if MC>=11604 +//#else +//$$ import net.minecraft.entity.Entity; +//#endif //#if MC>=11600 import net.minecraft.resource.ResourcePackSource; @@ -55,7 +61,6 @@ import org.lwjgl.glfw.GLFW; //#if MC>=10904 import com.replaymod.render.blend.mixin.ParticleAccessor; import net.minecraft.client.particle.Particle; -import net.minecraft.util.math.Vec3d; //#endif //#if MC>=10800 @@ -297,6 +302,12 @@ public class MCVer { } //#endif + //#if MC<=11601 + //$$ public static Vec3d getTrackedPosition(Entity entity) { + //$$ return new Vec3d(entity.trackedX, entity.trackedY, entity.trackedZ); + //$$ } + //#endif + public static void openFile(File file) { //#if MC>=11400 Util.getOperatingSystem().open(file); diff --git a/src/main/java/com/replaymod/core/versions/Patterns.java b/src/main/java/com/replaymod/core/versions/Patterns.java index 811ccb51..884eb955 100644 --- a/src/main/java/com/replaymod/core/versions/Patterns.java +++ b/src/main/java/com/replaymod/core/versions/Patterns.java @@ -21,6 +21,7 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.PacketByteBuf; import net.minecraft.util.Identifier; +import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import net.minecraft.world.chunk.WorldChunk; @@ -563,4 +564,13 @@ class Patterns { return new CrashException(((MinecraftAccessor) mc).getCrashReporter()); //#endif } + + @Pattern + private static Vec3d getTrackedPosition(Entity entity) { + //#if MC>=11604 + return entity.getTrackedPosition(); + //#else + //$$ return com.replaymod.core.versions.MCVer.getTrackedPosition(entity); + //#endif + } } diff --git a/src/main/java/com/replaymod/replay/ext/EntityExt.java b/src/main/java/com/replaymod/replay/ext/EntityExt.java new file mode 100644 index 00000000..5f6e4ce4 --- /dev/null +++ b/src/main/java/com/replaymod/replay/ext/EntityExt.java @@ -0,0 +1,9 @@ +package com.replaymod.replay.ext; + +public interface EntityExt { + float replaymod$getTrackedYaw(); + void replaymod$setTrackedYaw(float value); + + float replaymod$getTrackedPitch(); + void replaymod$setTrackedPitch(float value); +} diff --git a/src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_EntityExt.java b/src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_EntityExt.java new file mode 100644 index 00000000..993e9c6e --- /dev/null +++ b/src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_EntityExt.java @@ -0,0 +1,36 @@ +package com.replaymod.replay.mixin.entity_tracking; + +import com.replaymod.replay.ext.EntityExt; +import net.minecraft.entity.Entity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(Entity.class) +public abstract class Mixin_EntityExt implements EntityExt { + + @Unique + private float trackedYaw; + + @Unique + private float trackedPitch; + + @Override + public float replaymod$getTrackedYaw() { + return this.trackedYaw; + } + + @Override + public float replaymod$getTrackedPitch() { + return this.trackedPitch; + } + + @Override + public void replaymod$setTrackedYaw(float value) { + this.trackedYaw = value; + } + + @Override + public void replaymod$setTrackedPitch(float value) { + this.trackedPitch = value; + } +} diff --git a/src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_FixPartialUpdates.java b/src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_FixPartialUpdates.java new file mode 100644 index 00000000..255102ae --- /dev/null +++ b/src/main/java/com/replaymod/replay/mixin/entity_tracking/Mixin_FixPartialUpdates.java @@ -0,0 +1,109 @@ +package com.replaymod.replay.mixin.entity_tracking; + +import com.replaymod.replay.ext.EntityExt; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.entity.Entity; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * Every Entity on the client has at least two position/rotation states: The local one and the server ("tracked") one. + * When receiving an update from the server, the server one needs to be updated and the local one will then usually be + * interpolated to the server one within a few ticks. + * + * Minecraft however incorrectly implements the server rotation update for position-only update packets by + * interpolating to the local rotation, which might not yet match the server rotation, rather than to the previously + * received server rotation. + * Similarly, with 1.15 and later, it incorrectly implements the server position update for rotation-only update packets + * by interpolating to the local position rather than the server one. + * + * Each of these will cause the client position to be in an incorrect state until the next update packed for the + * respective rotation/position of that entity. + * + * This mixin fixes those two issues by redirecting to the server rotation/position respectively. + * Minecraft does not currently even track the server rotation, so we need to do that as well. + */ +@Mixin(ClientPlayNetworkHandler.class) +public class Mixin_FixPartialUpdates { + + // + // Use correct rotation for position-only updates + // + + //#if MC>=11700 + //$$ @Redirect(method = "onEntityUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getYaw()F")) + //#else + @Redirect(method = "onEntityUpdate", at = @At(value = "FIELD", target = "Lnet/minecraft/entity/Entity;yaw:F", opcode = Opcodes.GETFIELD)) + //#endif + private float getTrackedYaw(Entity instance) { + return ((EntityExt) instance).replaymod$getTrackedYaw(); + } + + //#if MC>=11700 + //$$ @Redirect(method = "onEntityUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getPitch()F")) + //#else + @Redirect(method = "onEntityUpdate", at = @At(value = "FIELD", target = "Lnet/minecraft/entity/Entity;pitch:F", opcode = Opcodes.GETFIELD)) + //#endif + private float getTrackedPitch(Entity instance) { + return ((EntityExt) instance).replaymod$getTrackedPitch(); + } + + //#if MC>=11500 + // + // Use correct position for rotation-only updates + // + + @Redirect(method = "onEntityUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getX()D")) + private double getTrackedX(Entity instance) { + return instance.getTrackedPosition().getX(); + } + + @Redirect(method = "onEntityUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getY()D")) + private double getTrackedY(Entity instance) { + return instance.getTrackedPosition().getY(); + } + + @Redirect(method = "onEntityUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;getZ()D")) + private double getTrackedZ(Entity instance) { + return instance.getTrackedPosition().getZ(); + } + //#endif + + // + // Track server rotation + // + + private static final String ENTITY_UPDATE = "Lnet/minecraft/entity/Entity;updateTrackedPositionAndAngles(DDDFFIZ)V"; + + @Unique + private Entity entity; + + @ModifyVariable(method = "onEntityUpdate", at = @At(value = "INVOKE", target = ENTITY_UPDATE), ordinal = 0) + private Entity captureEntity(Entity entity) { + return this.entity = entity; + } + + @Inject(method = "onEntityUpdate", at = @At("RETURN")) + private void resetEntityField(CallbackInfo ci) { + this.entity = null; + } + + @ModifyArg(method = "onEntityUpdate", at = @At(value = "INVOKE", target = ENTITY_UPDATE), index = 3) + private float captureTrackedYaw(float value) { + ((EntityExt) this.entity).replaymod$setTrackedYaw(value); + return value; + } + + @ModifyArg(method = "onEntityUpdate", at = @At(value = "INVOKE", target = ENTITY_UPDATE), index = 4) + private float captureTrackedPitch(float value) { + ((EntityExt) this.entity).replaymod$setTrackedPitch(value); + return value; + } +} diff --git a/src/main/resources/mixins.replay.replaymod.json b/src/main/resources/mixins.replay.replaymod.json index 6a481a03..f373ed49 100644 --- a/src/main/resources/mixins.replay.replaymod.json +++ b/src/main/resources/mixins.replay.replaymod.json @@ -5,6 +5,8 @@ "mixins": [], "server": [], "client": [ + "entity_tracking.Mixin_EntityExt", + "entity_tracking.Mixin_FixPartialUpdates", "Mixin_FixNPCSkinCaching", //#if MC>=11800 //$$ "Mixin_FixEntityNotTracking",