From 0691ef988666908dee60571962f298cd2a296f58 Mon Sep 17 00:00:00 2001 From: johni0702 Date: Fri, 2 Sep 2016 12:50:18 +0200 Subject: [PATCH] Fix (character, portal, enchantment) animations during speed up / slowed down path playback and rendering --- .../player/AbstractTimelinePlayer.java | 32 ++++++++++++++++-- .../player/RealtimeTimelinePlayer.java | 3 +- .../player/ReplayTimer.java} | 12 +++++-- .../render/rendering/VideoRenderer.java | 27 +++++++++++---- .../com/replaymod/replay/ReplaySender.java | 20 ++++++++--- .../replay}/mixin/MixinRenderItem.java | 12 +++++-- .../MixinTileEntityEndPortalRenderer.java | 21 ++++++++++++ .../MixinTileEntityEndPortalRenderer.java | 15 --------- .../replaymod/timer/EnchantmentTimer.java | 33 ------------------- .../resources/mixins.replay.replaymod.json | 4 ++- src/main/resources/mixins.replaymod.json | 2 -- 11 files changed, 107 insertions(+), 74 deletions(-) rename src/main/java/com/replaymod/{render/hooks/RenderReplayTimer.java => pathing/player/ReplayTimer.java} (59%) rename src/main/java/{eu/crushedpixel/replaymod => com/replaymod/replay}/mixin/MixinRenderItem.java (51%) create mode 100644 src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java delete mode 100644 src/main/java/eu/crushedpixel/replaymod/mixin/MixinTileEntityEndPortalRenderer.java delete mode 100644 src/main/java/eu/crushedpixel/replaymod/timer/EnchantmentTimer.java diff --git a/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java b/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java index 7bd35d24..21083f57 100644 --- a/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java +++ b/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java @@ -10,9 +10,9 @@ import com.replaymod.replay.ReplayHandler; import com.replaymod.replaystudio.pathing.path.Keyframe; import com.replaymod.replaystudio.pathing.path.Path; import com.replaymod.replaystudio.pathing.path.Timeline; +import net.minecraft.client.Minecraft; import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import net.minecraftforge.fml.common.gameevent.TickEvent; import javax.annotation.Nullable; import java.util.Iterator; @@ -21,8 +21,10 @@ import java.util.Iterator; * Plays a timeline. */ public abstract class AbstractTimelinePlayer { + private final Minecraft mc = Minecraft.getMinecraft(); private final ReplayHandler replayHandler; private Timeline timeline; + private long lastTime; private long lastTimestamp; private ListenableFuture future; private SettableFuture settableFuture; @@ -58,6 +60,10 @@ public abstract class AbstractTimelinePlayer { replayHandler.getReplaySender().setSyncModeAndWait(); FMLCommonHandler.instance().bus().register(this); + lastTime = 0; + mc.timer = new ReplayTimer(mc.timer); + mc.timer.timerSpeed = 1; + mc.timer.elapsedPartialTicks = mc.timer.elapsedTicks = 0; return future = settableFuture = SettableFuture.create(); } @@ -70,8 +76,9 @@ public abstract class AbstractTimelinePlayer { } @SubscribeEvent - public void onTick(TickEvent.RenderTickEvent event) { + public void onTick(ReplayTimer.UpdatedEvent event) { if (future.isDone()) { + mc.timer = ((ReplayTimer) mc.timer).getWrapped(); replayHandler.getReplaySender().setAsyncMode(true); FMLCommonHandler.instance().bus().unregister(this); return; @@ -80,8 +87,27 @@ public abstract class AbstractTimelinePlayer { if (time > lastTimestamp) { time = lastTimestamp; } + + // Apply to timeline timeline.applyToGame(time, replayHandler); - if (time == lastTimestamp) { + + // Update minecraft timer + long replayTime = replayHandler.getReplaySender().currentTimeStamp(); + if (lastTime == 0) { + // First frame, no change yet + lastTime = replayTime; + } + float timeInTicks = replayTime / 50f; + float previousTimeInTicks = lastTime / 50f; + float passedTicks = timeInTicks - previousTimeInTicks; + mc.timer.elapsedPartialTicks += passedTicks; + mc.timer.elapsedTicks = (int) mc.timer.elapsedPartialTicks; + mc.timer.elapsedPartialTicks -= mc.timer.elapsedTicks; + mc.timer.renderPartialTicks = mc.timer.elapsedPartialTicks; + + lastTime = replayTime; + + if (time >= lastTimestamp) { settableFuture.set(null); } } diff --git a/src/main/java/com/replaymod/pathing/player/RealtimeTimelinePlayer.java b/src/main/java/com/replaymod/pathing/player/RealtimeTimelinePlayer.java index 4acd3de1..eca409f4 100644 --- a/src/main/java/com/replaymod/pathing/player/RealtimeTimelinePlayer.java +++ b/src/main/java/com/replaymod/pathing/player/RealtimeTimelinePlayer.java @@ -3,7 +3,6 @@ package com.replaymod.pathing.player; import com.google.common.util.concurrent.ListenableFuture; import com.replaymod.replay.ReplayHandler; import com.replaymod.replaystudio.pathing.path.Timeline; -import net.minecraftforge.fml.common.gameevent.TickEvent; /** * Timeline player using the system time. @@ -33,7 +32,7 @@ public class RealtimeTimelinePlayer extends AbstractTimelinePlayer { } @Override - public void onTick(TickEvent.RenderTickEvent event) { + public void onTick(ReplayTimer.UpdatedEvent event) { if (secondFrame) { secondFrame = false; startTime = System.currentTimeMillis(); diff --git a/src/main/java/com/replaymod/render/hooks/RenderReplayTimer.java b/src/main/java/com/replaymod/pathing/player/ReplayTimer.java similarity index 59% rename from src/main/java/com/replaymod/render/hooks/RenderReplayTimer.java rename to src/main/java/com/replaymod/pathing/player/ReplayTimer.java index e7b998fb..ca5858ed 100644 --- a/src/main/java/com/replaymod/render/hooks/RenderReplayTimer.java +++ b/src/main/java/com/replaymod/pathing/player/ReplayTimer.java @@ -1,15 +1,17 @@ -package com.replaymod.render.hooks; +package com.replaymod.pathing.player; import com.replaymod.core.utils.WrappedTimer; import net.minecraft.util.Timer; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.eventhandler.Event; /** * Wrapper around the current timer that prevents the timer from advancing by itself. */ -public class RenderReplayTimer extends WrappedTimer { +public class ReplayTimer extends WrappedTimer { private final Timer state = new Timer(0); - public RenderReplayTimer(Timer wrapped) { + public ReplayTimer(Timer wrapped) { super(wrapped); } @@ -18,9 +20,13 @@ public class RenderReplayTimer extends WrappedTimer { copy(this, state); // Save our current state super.updateTimer(); // Update current state copy(state, this); // Restore our old state + FMLCommonHandler.instance().bus().post(new UpdatedEvent()); } public Timer getWrapped() { return wrapped; } + + public static class UpdatedEvent extends Event { + } } diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index c63dcfa4..0576bbbe 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -1,6 +1,7 @@ package com.replaymod.render.rendering; import com.replaymod.core.ReplayMod; +import com.replaymod.pathing.player.AbstractTimelinePlayer; import com.replaymod.pathing.properties.TimestampProperty; import com.replaymod.render.RenderSettings; import com.replaymod.render.VideoWriter; @@ -8,7 +9,6 @@ import com.replaymod.render.capturer.RenderInfo; import com.replaymod.render.frame.RGBFrame; import com.replaymod.render.gui.GuiVideoRenderer; import com.replaymod.render.hooks.ChunkLoadingRenderGlobal; -import com.replaymod.render.hooks.RenderReplayTimer; import com.replaymod.render.metadata.MetadataInjector; import com.replaymod.replay.ReplayHandler; import com.replaymod.replaystudio.pathing.path.Keyframe; @@ -29,6 +29,7 @@ import java.util.Collection; import java.util.EnumMap; import java.util.Map; import java.util.Optional; +import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import static com.google.common.collect.Iterables.getLast; @@ -49,6 +50,7 @@ public class VideoRenderer implements RenderInfo { private boolean debugInfoWasShown; private Map originalSoundLevels; + private Future timelinePlayerFuture; private ChunkLoadingRenderGlobal chunkLoadingRenderGlobal; private int framesDone; @@ -130,7 +132,8 @@ public class VideoRenderer implements RenderInfo { drawGui(); } - timeline.applyToGame(getVideoTime(), replayHandler); + // Updating the timer will cause the timeline player to update the game state + mc.timer.updateTimer(); int elapsedTicks = mc.timer.elapsedTicks; while (elapsedTicks-- > 0) { @@ -147,7 +150,7 @@ public class VideoRenderer implements RenderInfo { } private void setup() { - replayHandler.getReplaySender().setSyncModeAndWait(); + timelinePlayerFuture = new TimelinePlayer(replayHandler).start(timeline); if (!OpenGlHelper.isFramebufferEnabled()) { Display.setResizable(false); @@ -160,8 +163,6 @@ public class VideoRenderer implements RenderInfo { mouseWasGrabbed = true; } Mouse.setGrabbed(false); - mc.timer = new RenderReplayTimer(mc.timer); - mc.timer.timerSpeed = 1; // Mute all sounds except GUI sounds (buttons, etc.) Map mutedSounds = new EnumMap<>(SoundCategory.class); @@ -196,7 +197,9 @@ public class VideoRenderer implements RenderInfo { } private void finish() { - replayHandler.getReplaySender().setAsyncMode(true); + if (!timelinePlayerFuture.isDone()) { + timelinePlayerFuture.cancel(false); + } if (!OpenGlHelper.isFramebufferEnabled()) { Display.setResizable(true); @@ -205,7 +208,6 @@ public class VideoRenderer implements RenderInfo { if (mouseWasGrabbed) { mc.mouseHelper.grabMouseCursor(); } - mc.timer = ((RenderReplayTimer) mc.timer).getWrapped(); mc.gameSettings.mapSoundLevels = originalSoundLevels; mc.displayGuiScreen(null); if (chunkLoadingRenderGlobal != null) { @@ -309,4 +311,15 @@ public class VideoRenderer implements RenderInfo { this.cancelled = true; renderingPipeline.cancel(); } + + private class TimelinePlayer extends AbstractTimelinePlayer { + public TimelinePlayer(ReplayHandler replayHandler) { + super(replayHandler); + } + + @Override + public long getTimePassed() { + return getVideoTime(); + } + } } diff --git a/src/main/java/com/replaymod/replay/ReplaySender.java b/src/main/java/com/replaymod/replay/ReplaySender.java index da8afd91..6be29efc 100755 --- a/src/main/java/com/replaymod/replay/ReplaySender.java +++ b/src/main/java/com/replaymod/replay/ReplaySender.java @@ -87,6 +87,11 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { */ protected int lastTimeStamp; + /** + * @see #currentTimeStamp() + */ + protected int currentTimeStamp; + /** * The replay file. */ @@ -207,11 +212,18 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { } /** - * Return the timestamp of the last packet sent. + * Return a fake {@link Minecraft#getSystemTime()} value that respects slowdown/speedup/pause and works in both, + * sync and async mode. + * Note: For sync mode this returns the last value passed to {@link #sendPacketsTill(int)}. * @return The timestamp in milliseconds since the start of the replay */ public int currentTimeStamp() { - return lastTimeStamp; + if (asyncMode) { + int timePassed = (int) (System.currentTimeMillis() - lastPacketSent); + return lastTimeStamp + (int) (timePassed * getReplaySpeed()); + } else { + return lastTimeStamp; + } } /** @@ -746,9 +758,6 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { // Process packet channelRead(ctx, pd.bytes); - - // Store last timestamp - lastTimeStamp = nextTimeStamp; } catch (EOFException eof) { // Shit! We hit the end before finishing our job! What shall we do now? // well, let's just pretend we're done... @@ -761,6 +770,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { // This might be required if we change to async mode anytime soon lastPacketSent = System.currentTimeMillis(); + lastTimeStamp = timestamp; } } catch (Exception e) { e.printStackTrace(); diff --git a/src/main/java/eu/crushedpixel/replaymod/mixin/MixinRenderItem.java b/src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java similarity index 51% rename from src/main/java/eu/crushedpixel/replaymod/mixin/MixinRenderItem.java rename to src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java index de4105c4..3439c689 100644 --- a/src/main/java/eu/crushedpixel/replaymod/mixin/MixinRenderItem.java +++ b/src/main/java/com/replaymod/replay/mixin/MixinRenderItem.java @@ -1,6 +1,8 @@ -package eu.crushedpixel.replaymod.mixin; +package com.replaymod.replay.mixin; -import eu.crushedpixel.replaymod.timer.EnchantmentTimer; +import com.replaymod.replay.ReplayHandler; +import com.replaymod.replay.ReplayModReplay; +import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.entity.RenderItem; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -10,6 +12,10 @@ import org.spongepowered.asm.mixin.injection.Redirect; public class MixinRenderItem { @Redirect(method = "renderEffect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getSystemTime()J")) private long getEnchantmentTime() { - return EnchantmentTimer.getEnchantmentTime(); + ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler(); + if (replayHandler != null) { + return replayHandler.getReplaySender().currentTimeStamp(); + } + return Minecraft.getSystemTime(); } } diff --git a/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java b/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java new file mode 100644 index 00000000..f6cca14a --- /dev/null +++ b/src/main/java/com/replaymod/replay/mixin/MixinTileEntityEndPortalRenderer.java @@ -0,0 +1,21 @@ +package com.replaymod.replay.mixin; + +import com.replaymod.replay.ReplayHandler; +import com.replaymod.replay.ReplayModReplay; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.tileentity.TileEntityEndPortalRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(TileEntityEndPortalRenderer.class) +public class MixinTileEntityEndPortalRenderer { + @Redirect(method = "func_180544_a", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getSystemTime()J")) + private long replayModReplay_getEnchantmentTime() { + ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler(); + if (replayHandler != null) { + return replayHandler.getReplaySender().currentTimeStamp(); + } + return Minecraft.getSystemTime(); + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/mixin/MixinTileEntityEndPortalRenderer.java b/src/main/java/eu/crushedpixel/replaymod/mixin/MixinTileEntityEndPortalRenderer.java deleted file mode 100644 index 03c7263f..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/mixin/MixinTileEntityEndPortalRenderer.java +++ /dev/null @@ -1,15 +0,0 @@ -package eu.crushedpixel.replaymod.mixin; - -import eu.crushedpixel.replaymod.timer.EnchantmentTimer; -import net.minecraft.client.renderer.tileentity.TileEntityEndPortalRenderer; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -@Mixin(TileEntityEndPortalRenderer.class) -public class MixinTileEntityEndPortalRenderer { - @Redirect(method = "func_180544_a", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getSystemTime()J")) - private long getEnchantmentTime() { - return EnchantmentTimer.getEnchantmentTime(); - } -} diff --git a/src/main/java/eu/crushedpixel/replaymod/timer/EnchantmentTimer.java b/src/main/java/eu/crushedpixel/replaymod/timer/EnchantmentTimer.java deleted file mode 100644 index 71d0a4b8..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/timer/EnchantmentTimer.java +++ /dev/null @@ -1,33 +0,0 @@ -package eu.crushedpixel.replaymod.timer; - -public class EnchantmentTimer { - - private static long lastRealTime = System.currentTimeMillis(); - private static long lastFakeTime = System.currentTimeMillis(); - - private static long recordingTime = 0; - - public static void resetRecordingTime() { - recordingTime = 0; - } - - public static void increaseRecordingTime(long amount) { - recordingTime += amount; - } - - public static long getEnchantmentTime() { - // TODO -// if(!(ReplayHandler.isInPath() && ReplayProcess.isVideoRecording())) { -// if(ReplayHandler.isInReplay()) { -// long timeDiff = System.currentTimeMillis() - lastRealTime; -// double toAdd = timeDiff * ReplayMod.replaySender.getReplaySpeed(); -// lastFakeTime = Math.round(lastFakeTime + toAdd); -// lastRealTime = System.currentTimeMillis(); -// return lastFakeTime; -// } -// lastFakeTime = lastRealTime = System.currentTimeMillis(); -// return lastRealTime; -// } - return recordingTime; - } -} diff --git a/src/main/resources/mixins.replay.replaymod.json b/src/main/resources/mixins.replay.replaymod.json index 04ce3065..219d0632 100644 --- a/src/main/resources/mixins.replay.replaymod.json +++ b/src/main/resources/mixins.replay.replaymod.json @@ -2,7 +2,9 @@ "required": true, "package": "com.replaymod.replay.mixin", "mixins": [ - "MixinPlayerControllerMP" + "MixinPlayerControllerMP", + "MixinRenderItem", + "MixinTileEntityEndPortalRenderer" ], "server": [], "client": [], diff --git a/src/main/resources/mixins.replaymod.json b/src/main/resources/mixins.replaymod.json index 312102d1..2ec008cd 100644 --- a/src/main/resources/mixins.replaymod.json +++ b/src/main/resources/mixins.replaymod.json @@ -5,9 +5,7 @@ "MixinGuiSpectator", "MixinMinecraft", "MixinRenderArrow", - "MixinRenderItem", "MixinRenderManager", - "MixinTileEntityEndPortalRenderer", "MixinViewFrustum" ], "server": [],