diff --git a/build.gradle b/build.gradle index 632b7b97..16160bda 100755 --- a/build.gradle +++ b/build.gradle @@ -61,7 +61,7 @@ configurations { dependencies { compile 'org.projectlombok:lombok:1.16.4' - compile 'org.spongepowered:mixin:0.6.8-SNAPSHOT' + compile 'org.spongepowered:mixin:0.7.5-SNAPSHOT' shade 'com.googlecode.mp4parser:isoparser:1.1.7' shade 'org.apache.commons:commons-exec:1.3' shade 'com.google.apis:google-api-services-youtube:v3-rev178-1.22.0' diff --git a/src/main/java/com/replaymod/compat/mapwriter/mixin/MixinMinecraft.java b/src/main/java/com/replaymod/compat/mapwriter/mixin/MixinMinecraft.java new file mode 100644 index 00000000..edc62203 --- /dev/null +++ b/src/main/java/com/replaymod/compat/mapwriter/mixin/MixinMinecraft.java @@ -0,0 +1,35 @@ +package com.replaymod.compat.mapwriter.mixin; + +import com.replaymod.replay.ReplayModReplay; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ServerData; +import net.minecraftforge.fml.common.Loader; +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.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * Approximately this for <1.12: https://github.com/Vectron/mapwriter/commit/68234520c7a3a0ae8201a085d7e66369900586ac + */ +@Mixin(Minecraft.class) +public abstract class MixinMinecraft { + + @Shadow + private ServerData currentServerData; + + @Inject(method = "getCurrentServerData", cancellable = true, at = @At("HEAD")) + private void replayModCompat_fixBug96(CallbackInfoReturnable ci) { + if (currentServerData == null + && (Loader.isModLoaded("mapwriter") || Loader.isModLoaded("MapWriter")) + && ReplayModReplay.instance.getReplayHandler() != null) { + for (StackTraceElement elem : Thread.currentThread().getStackTrace()) { + if ("mapwriter.util.Utils".equals(elem.getClassName()) && "getWorldName".equals(elem.getMethodName())) { + ci.setReturnValue(new ServerData(null, "replay", false)); + return; + } + } + } + } +} diff --git a/src/main/java/com/replaymod/core/LoadingPlugin.java b/src/main/java/com/replaymod/core/LoadingPlugin.java index bcefaad5..43f27c65 100755 --- a/src/main/java/com/replaymod/core/LoadingPlugin.java +++ b/src/main/java/com/replaymod/core/LoadingPlugin.java @@ -19,6 +19,7 @@ public class LoadingPlugin implements IFMLLoadingPlugin { Mixins.addConfiguration("mixins.recording.replaymod.json"); Mixins.addConfiguration("mixins.render.replaymod.json"); Mixins.addConfiguration("mixins.replay.replaymod.json"); + Mixins.addConfiguration("mixins.compat.mapwriter.replaymod.json"); Mixins.addConfiguration("mixins.compat.shaders.replaymod.json"); Mixins.addConfiguration("mixins.extras.playeroverview.replaymod.json"); diff --git a/src/main/java/com/replaymod/replay/ReplaySender.java b/src/main/java/com/replaymod/replay/ReplaySender.java index 64248d7f..6904d62f 100755 --- a/src/main/java/com/replaymod/replay/ReplaySender.java +++ b/src/main/java/com/replaymod/replay/ReplaySender.java @@ -21,11 +21,15 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.network.*; import net.minecraft.network.play.server.*; +import net.minecraft.util.ClassInheritanceMultiMap; +import net.minecraft.util.math.MathHelper; import net.minecraft.util.text.ITextComponent; import net.minecraft.world.EnumDifficulty; import net.minecraft.world.GameType; import net.minecraft.world.World; import net.minecraft.world.WorldType; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.IChunkProvider; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.gameevent.TickEvent; @@ -33,6 +37,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import java.io.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -816,6 +821,50 @@ public class ReplaySender extends ChannelDuplexHandler { } protected Packet processPacketSync(Packet p) { + if (p instanceof SPacketUnloadChunk) { + SPacketUnloadChunk packet = (SPacketUnloadChunk) p; + // If the chunk is getting unloaded, we will have to forcefully update the position of all entities + // within. Otherwise, if there wasn't a game tick recently, there may be entities that have moved + // out of the chunk by now but are still registered in it. If we do not update those, they will get + // unloaded even though they shouldn't. + // To make things worse, it seems like players were never supposed to be unloaded this way because + // they will remain glitched in the World#playerEntities list. + World world = mc.theWorld; + IChunkProvider chunkProvider = world.getChunkProvider(); + // Get the chunk that will be unloaded + Chunk chunk = chunkProvider.provideChunk(packet.getX(), packet.getZ()); + if (!chunk.isEmpty()) { + List entitiesInChunk = new ArrayList<>(); + // Gather all entities in that chunk + for (ClassInheritanceMultiMap entityList : chunk.getEntityLists()) { + entitiesInChunk.addAll(entityList); + } + for (Entity entity : entitiesInChunk) { + // Skip interpolation of position updates coming from server + // (See: newX in EntityLivingBase or otherPlayerMPX in EntityOtherPlayerMP) + // Needs to be called at least 4 times thanks to + // EntityOtherPlayerMP#otherPlayerMPPosRotationIncrements (max vanilla value is 3) + for (int i = 0; i < 4; i++) { + entity.onUpdate(); + } + + // Check whether the entity has left the chunk + int chunkX = MathHelper.floor_double(entity.posX / 16); + int chunkZ = MathHelper.floor_double(entity.posZ / 16); + if (entity.chunkCoordX != chunkX || entity.chunkCoordZ != chunkZ) { + // Entity has left the chunk + chunk.removeEntityAtIndex(entity, entity.chunkCoordY); + Chunk newChunk = chunkProvider.getLoadedChunk(chunkX, chunkZ); + if (newChunk != null) { + newChunk.addEntity(entity); + } else { + // Entity has left all loaded chunks + entity.addedToChunk = false; + } + } + } + } + } return p; // During synchronous playback everything is sent normally } diff --git a/src/main/java/com/replaymod/replay/camera/CameraEntity.java b/src/main/java/com/replaymod/replay/camera/CameraEntity.java index 3e3634ee..a733b511 100755 --- a/src/main/java/com/replaymod/replay/camera/CameraEntity.java +++ b/src/main/java/com/replaymod/replay/camera/CameraEntity.java @@ -160,6 +160,11 @@ public class CameraEntity extends EntityPlayerSP { if (spectating != null && (view.getUniqueID() != spectating || view.worldObj != worldObj) || worldObj.getEntityByID(view.getEntityId()) != view) { + if (spectating == null) { + // Entity (non-player) died, stop spectating + ReplayModReplay.instance.getReplayHandler().spectateEntity(this); + return; + } view = worldObj.getPlayerEntityByUUID(spectating); if (view != null) { mc.setRenderViewEntity(view); diff --git a/src/main/resources/mixins.compat.mapwriter.replaymod.json b/src/main/resources/mixins.compat.mapwriter.replaymod.json new file mode 100644 index 00000000..eb02cea2 --- /dev/null +++ b/src/main/resources/mixins.compat.mapwriter.replaymod.json @@ -0,0 +1,12 @@ +{ + "required": false, + "package": "com.replaymod.compat.mapwriter.mixin", + "mixins": [], + "server": [], + "client": [ + "MixinMinecraft" + ], + "compatibilityLevel": "JAVA_8", + "minVersion": "0.6.11", + "refmap": "mixins.replaymod.refmap.json" +} \ No newline at end of file diff --git a/src/main/resources/mixins.compat.shaders.replaymod.json b/src/main/resources/mixins.compat.shaders.replaymod.json index 792af576..ed6e1a9a 100644 --- a/src/main/resources/mixins.compat.shaders.replaymod.json +++ b/src/main/resources/mixins.compat.shaders.replaymod.json @@ -10,5 +10,6 @@ "MixinShadersRender" ], "compatibilityLevel": "JAVA_8", + "minVersion": "0.6.11", "refmap": "mixins.replaymod.refmap.json" } \ No newline at end of file diff --git a/src/main/resources/mixins.extras.playeroverview.replaymod.json b/src/main/resources/mixins.extras.playeroverview.replaymod.json index 7c9a0a1f..9c9bf8e7 100644 --- a/src/main/resources/mixins.extras.playeroverview.replaymod.json +++ b/src/main/resources/mixins.extras.playeroverview.replaymod.json @@ -7,5 +7,6 @@ "MixinRender" ], "compatibilityLevel": "JAVA_8", + "minVersion": "0.6.11", "refmap": "mixins.replaymod.refmap.json" } \ No newline at end of file diff --git a/src/main/resources/mixins.recording.replaymod.json b/src/main/resources/mixins.recording.replaymod.json index 234ec574..5a88e180 100644 --- a/src/main/resources/mixins.recording.replaymod.json +++ b/src/main/resources/mixins.recording.replaymod.json @@ -12,5 +12,6 @@ "MixinWorldClient" ], "compatibilityLevel": "JAVA_8", + "minVersion": "0.6.11", "refmap": "mixins.replaymod.refmap.json" } \ No newline at end of file diff --git a/src/main/resources/mixins.render.replaymod.json b/src/main/resources/mixins.render.replaymod.json index c1dcaee3..60b10552 100644 --- a/src/main/resources/mixins.render.replaymod.json +++ b/src/main/resources/mixins.render.replaymod.json @@ -12,5 +12,6 @@ "server": [], "client": [], "compatibilityLevel": "JAVA_8", + "minVersion": "0.6.11", "refmap": "mixins.replaymod.refmap.json" } \ No newline at end of file diff --git a/src/main/resources/mixins.replay.replaymod.json b/src/main/resources/mixins.replay.replaymod.json index ffd70c48..ba74e0a0 100644 --- a/src/main/resources/mixins.replay.replaymod.json +++ b/src/main/resources/mixins.replay.replaymod.json @@ -16,5 +16,6 @@ "server": [], "client": [], "compatibilityLevel": "JAVA_8", + "minVersion": "0.6.11", "refmap": "mixins.replaymod.refmap.json" } \ No newline at end of file