From 9528a5761ca54ed5cc65d417932ae754ab275f20 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 27 Feb 2022 09:10:35 +0100 Subject: [PATCH 01/28] Update old ModMenu versions To use the new maven group and for compatibility with newer fabric-loader. --- build.gradle | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 59fd9e7b..b486fbaf 100644 --- a/build.gradle +++ b/build.gradle @@ -353,9 +353,11 @@ dependencies { } else if (mcVersion >= 11602) { modImplementation 'com.terraformersmc:modmenu:1.16.8' } else if (mcVersion >= 11600) { - modImplementation 'io.github.prospector:modmenu:1.14.0+build.24' + modImplementation('com.terraformersmc:modmenu:1.14.15') { + exclude module: 'fabric-resource-loader-v0' // inappropriate version for 1.16.1 + } } else { - modImplementation 'io.github.prospector.modmenu:ModMenu:1.6.2-92' + modImplementation 'com.terraformersmc:modmenu:1.10.6' } } From e378ae8f93fbfa4053b1366600454df515ba0a67 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 27 Feb 2022 10:54:23 +0100 Subject: [PATCH 02/28] Fix entities disappearing on 1.16.1 and 1.15.2 client (fixes #657) --- src/main/java/com/replaymod/core/versions/MCVer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/core/versions/MCVer.java b/src/main/java/com/replaymod/core/versions/MCVer.java index cb7c7f00..c239d9b0 100644 --- a/src/main/java/com/replaymod/core/versions/MCVer.java +++ b/src/main/java/com/replaymod/core/versions/MCVer.java @@ -304,7 +304,7 @@ public class MCVer { //#if MC<=11601 //$$ public static Vec3d getTrackedPosition(Entity entity) { - //$$ return new Vec3d(entity.trackedX, entity.trackedY, entity.trackedZ); + //$$ return new Vec3d(entity.trackedX / 4096.0, entity.trackedY / 4096.0, entity.trackedZ / 4096.0); //$$ } //#endif From 7e9a1e7c135761837cfa26f793664d3d5aff2bcb Mon Sep 17 00:00:00 2001 From: Andrew S Date: Sat, 25 Dec 2021 20:01:48 -0500 Subject: [PATCH 03/28] Fix Render GUI on Retina Screens (fixes #338) window.getFramebufferWidth/Height() should be used instead of window.getWidth/Height() Co-authored-by: Jonas Herzig --- .../render/mixin/MainWindowAccessor.java | 9 ++++ .../render/rendering/VideoRenderer.java | 47 +++++++++++++------ .../com/replaymod/core/versions/Window.java | 5 ++ .../render/mixin/MainWindowAccessor.java | 3 ++ 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/replaymod/render/mixin/MainWindowAccessor.java b/src/main/java/com/replaymod/render/mixin/MainWindowAccessor.java index 335c9908..a54dd178 100644 --- a/src/main/java/com/replaymod/render/mixin/MainWindowAccessor.java +++ b/src/main/java/com/replaymod/render/mixin/MainWindowAccessor.java @@ -3,6 +3,7 @@ package com.replaymod.render.mixin; import net.minecraft.client.util.Window; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; @Mixin(Window.class) public interface MainWindowAccessor { @@ -14,4 +15,12 @@ public interface MainWindowAccessor { int getFramebufferHeight(); @Accessor void setFramebufferHeight(int value); + // FIXME preprocessor should be able to infer this mapping + // FIXME preprocessor should be able to remap this one when the mapping is given manually + //#if MC>=11500 + @Invoker + //#else + //$$ @Invoker("method_4483") + //#endif + void invokeUpdateFramebufferSize(); } diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index dfa7e096..9eb819d5 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -120,6 +120,7 @@ public class VideoRenderer implements RenderInfo { private Framebuffer guiFramebuffer; private int displayWidth, displayHeight; + private int framebufferWidth, framebufferHeight; public VideoRenderer(RenderSettings settings, ReplayHandler replayHandler, Timeline timeline) throws IOException { this.settings = settings; @@ -245,10 +246,10 @@ public class VideoRenderer implements RenderInfo { public float updateForNextFrame() { // because the jGui lib uses Minecraft's displayWidth and displayHeight values, update these temporarily MainWindowAccessor acc = (MainWindowAccessor) (Object) mc.getWindow(); - int displayWidthBefore = acc.getFramebufferWidth(); - int displayHeightBefore = acc.getFramebufferHeight(); - acc.setFramebufferWidth(displayWidth); - acc.setFramebufferHeight(displayHeight); + int framebufferWidthBefore = acc.getFramebufferWidth(); + int framebufferHeightBefore = acc.getFramebufferHeight(); + acc.setFramebufferWidth(framebufferWidth); + acc.setFramebufferHeight(framebufferHeight); if (!settings.isHighPerformance() || framesDone % fps == 0) { while (drawGui() && paused) { @@ -289,8 +290,8 @@ public class VideoRenderer implements RenderInfo { } // change Minecraft's display size back - acc.setFramebufferWidth(displayWidthBefore); - acc.setFramebufferHeight(displayHeightBefore); + acc.setFramebufferWidth(framebufferWidthBefore); + acc.setFramebufferHeight(framebufferHeightBefore); if (cameraPathExporter != null) { cameraPathExporter.recordFrame(timer.tickDelta); @@ -362,6 +363,7 @@ public class VideoRenderer implements RenderInfo { } updateDisplaySize(); + updateFramebufferSize(); gui.toMinecraft().init(mc, mc.getWindow().getScaledWidth(), mc.getWindow().getScaledHeight()); @@ -369,9 +371,9 @@ public class VideoRenderer implements RenderInfo { // Set up our own framebuffer to render the GUI to //#if MC>=11700 - //$$ guiFramebuffer = new WindowFramebuffer(displayWidth, displayHeight); + //$$ guiFramebuffer = new WindowFramebuffer(framebufferWidth, framebufferHeight); //#else - guiFramebuffer = new Framebuffer(displayWidth, displayHeight, true + guiFramebuffer = new Framebuffer(framebufferWidth, framebufferHeight, true //#if MC>=11400 , false //#endif @@ -425,7 +427,7 @@ public class VideoRenderer implements RenderInfo { } // Finally, resize the Minecraft framebuffer to the actual width/height of the window - resizeMainWindow(mc, displayWidth, displayHeight); + resizeMainWindow(mc, framebufferWidth, framebufferHeight); } private void executeTaskQueue() { @@ -483,17 +485,23 @@ public class VideoRenderer implements RenderInfo { return false; } - // Resize the GUI framebuffer if the display size changed + // Check if display size has changes and force recalculate GUI framebuffer size. if (displaySizeChanged()) { updateDisplaySize(); + ((MainWindowAccessor) (Object) window).invokeUpdateFramebufferSize(); + } + + // Resize the GUI framebuffer if the display size changed + if (framebufferSizeChanged()) { + updateFramebufferSize(); //#if MC>=11400 - guiFramebuffer.resize(displayWidth, displayHeight + guiFramebuffer.resize(framebufferWidth, framebufferHeight //#if MC>=11400 , false //#endif ); //#else - //$$ guiFramebuffer.createBindFramebuffer(mc.displayWidth, mc.displayHeight); + //$$ guiFramebuffer.createBindFramebuffer(framebufferWidth, framebufferHeight); //#endif } @@ -594,7 +602,7 @@ public class VideoRenderer implements RenderInfo { guiFramebuffer.endWrite(); popMatrix(); pushMatrix(); - guiFramebuffer.draw(displayWidth, displayHeight); + guiFramebuffer.draw(framebufferWidth, framebufferHeight); popMatrix(); //#if MC>=11500 @@ -633,12 +641,18 @@ public class VideoRenderer implements RenderInfo { private boolean displaySizeChanged() { int realWidth = mc.getWindow().getWidth(); int realHeight = mc.getWindow().getHeight(); + return displayWidth != realWidth || displayHeight != realHeight; + } + + private boolean framebufferSizeChanged() { + int realWidth = mc.getWindow().getFramebufferWidth(); + int realHeight = mc.getWindow().getFramebufferHeight(); if (realWidth == 0 || realHeight == 0) { // These can be zero on Windows if minimized. // Creating zero-sized framebuffers however will throw an error, so we never want to switch to zero values. return false; } - return displayWidth != realWidth || displayHeight != realHeight; + return framebufferWidth != realWidth || framebufferHeight != realHeight; } private void updateDisplaySize() { @@ -646,6 +660,11 @@ public class VideoRenderer implements RenderInfo { displayHeight = mc.getWindow().getHeight(); } + private void updateFramebufferSize() { + framebufferWidth = mc.getWindow().getFramebufferWidth(); + framebufferHeight = mc.getWindow().getFramebufferHeight(); + } + public int getFramesDone() { return framesDone; } diff --git a/versions/1.12.2/src/main/java/com/replaymod/core/versions/Window.java b/versions/1.12.2/src/main/java/com/replaymod/core/versions/Window.java index fdcadef3..26d697d2 100644 --- a/versions/1.12.2/src/main/java/com/replaymod/core/versions/Window.java +++ b/versions/1.12.2/src/main/java/com/replaymod/core/versions/Window.java @@ -34,6 +34,11 @@ public class Window implements MainWindowAccessor { mc.displayHeight = value; } + @Override + public void invokeUpdateFramebufferSize() { + // no-op, pre-LWJGL3 MC doesn't differentiate between window and framebuffer size + } + public long getHandle() { return 0; } diff --git a/versions/1.12.2/src/main/java/com/replaymod/render/mixin/MainWindowAccessor.java b/versions/1.12.2/src/main/java/com/replaymod/render/mixin/MainWindowAccessor.java index 2d91cb58..a6166095 100644 --- a/versions/1.12.2/src/main/java/com/replaymod/render/mixin/MainWindowAccessor.java +++ b/versions/1.12.2/src/main/java/com/replaymod/render/mixin/MainWindowAccessor.java @@ -3,6 +3,7 @@ package com.replaymod.render.mixin; import net.minecraft.client.Minecraft; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; @Mixin(Minecraft.class) public interface MainWindowAccessor { @@ -14,4 +15,6 @@ public interface MainWindowAccessor { int getFramebufferHeight(); @Accessor("displayHeight") void setFramebufferHeight(int value); + @Invoker + void invokeUpdateFramebufferSize(); } From 0b8a2a107c0c21c312aff75918f47baf8f9c1069 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 27 Feb 2022 12:59:19 +0100 Subject: [PATCH 04/28] Use separate thread to send packets, even in sync mode (fixes #674) --- .../java/com/replaymod/extras/QuickMode.java | 2 +- .../replaymod/replay/FullReplaySender.java | 185 +++++++++++++----- .../replaymod/replay/QuickReplaySender.java | 4 +- 3 files changed, 137 insertions(+), 54 deletions(-) diff --git a/src/main/java/com/replaymod/extras/QuickMode.java b/src/main/java/com/replaymod/extras/QuickMode.java index 454d20be..ab1f677d 100644 --- a/src/main/java/com/replaymod/extras/QuickMode.java +++ b/src/main/java/com/replaymod/extras/QuickMode.java @@ -25,7 +25,7 @@ public class QuickMode extends EventRegistrations implements Extra { return; } replayHandler.getReplaySender().setSyncModeAndWait(); - mod.runLater(() -> { + mod.runLaterWithoutLock(() -> { replayHandler.ensureQuickModeInitialized(() -> { boolean enabled = !replayHandler.isQuickMode(); updateIndicator(replayHandler.getOverlay(), enabled); diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java index 95b944a5..2aee5e57 100644 --- a/src/main/java/com/replaymod/replay/FullReplaySender.java +++ b/src/main/java/com/replaymod/replay/FullReplaySender.java @@ -125,6 +125,9 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; import static com.replaymod.core.versions.MCVer.*; import static com.replaymod.replaystudio.util.Utils.readInt; @@ -349,6 +352,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend return; } terminate = true; + syncSender.shutdown(); events.unregister(); try { channelInactive(ctx); @@ -403,53 +407,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend super.channelRead(ctx, p); } - // If we do not give minecraft time to tick, there will be dead entity artifacts left in the world - // Therefore we have to remove all loaded, dead entities manually if we are in sync mode. - // We do this after every SpawnX packet and after the destroy entities packet. - if (!asyncMode && mc.world != null) { - if (p instanceof PlayerSpawnS2CPacket - || p instanceof EntitySpawnS2CPacket - || p instanceof MobSpawnS2CPacket - //#if MC<11600 - //$$ || p instanceof EntitySpawnGlobalS2CPacket - //#endif - || p instanceof PaintingSpawnS2CPacket - || p instanceof ExperienceOrbSpawnS2CPacket - || p instanceof EntitiesDestroyS2CPacket) { - ClientWorld world = mc.world; - //#if MC>=11700 - //$$ // From the looks of it, this has now been resolved (thanks to EntityChangeListener) - //#elseif MC>=11400 - // Note: Not sure if it's still required but there's this really handy method anyway - world.finishRemovingEntities(); - //#else - //$$ Iterator iter = world.loadedEntityList.iterator(); - //$$ while (iter.hasNext()) { - //$$ Entity entity = iter.next(); - //$$ if (entity.isDead) { - //$$ int chunkX = entity.chunkCoordX; - //$$ int chunkY = entity.chunkCoordZ; - //$$ - //#if MC>=11400 - //$$ if (entity.addedToChunk && world.getChunkProvider().provideChunk(chunkX, chunkY, false, false) != null) { - //#else - //#if MC>=10904 - //$$ if (entity.addedToChunk && world.getChunkProvider().getLoadedChunk(chunkX, chunkY) != null) { - //#else - //$$ if (entity.addedToChunk && world.getChunkProvider().chunkExists(chunkX, chunkY)) { - //#endif - //#endif - //$$ world.getChunkFromChunkCoords(chunkX, chunkY).removeEntity(entity); - //$$ } - //$$ - //$$ iter.remove(); - //$$ world.onEntityRemoved(entity); - //$$ } - //$$ - //$$ } - //#endif - } - } + maybeRemoveDeadEntities(p); //#if MC>=11400 if (p instanceof ChunkDataS2CPacket) { @@ -504,6 +462,69 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend return p; } + // If we do not give minecraft time to tick, there will be dead entity artifacts left in the world + // Therefore we have to remove all loaded, dead entities manually if we are in sync mode. + // We do this after every SpawnX packet and after the destroy entities packet. + private void maybeRemoveDeadEntities(Packet packet) { + if (asyncMode) { + return; // MC should have enough time to tick + } + + boolean relevantPacket = packet instanceof PlayerSpawnS2CPacket + || packet instanceof EntitySpawnS2CPacket + || packet instanceof MobSpawnS2CPacket + //#if MC<11600 + //$$ || packet instanceof EntitySpawnGlobalS2CPacket + //#endif + || packet instanceof PaintingSpawnS2CPacket + || packet instanceof ExperienceOrbSpawnS2CPacket + || packet instanceof EntitiesDestroyS2CPacket; + if (!relevantPacket) { + return; // don't want to do it too often, only when there's likely to be a dead entity + } + + mc.send(() -> { + ClientWorld world = mc.world; + if (world != null) { + removeDeadEntities(world); + } + }); + } + + private void removeDeadEntities(ClientWorld world) { + //#if MC>=11700 + //$$ // From the looks of it, this has now been resolved (thanks to EntityChangeListener) + //#elseif MC>=11400 + // Note: Not sure if it's still required but there's this really handy method anyway + world.finishRemovingEntities(); + //#else + //$$ Iterator iter = world.loadedEntityList.iterator(); + //$$ while (iter.hasNext()) { + //$$ Entity entity = iter.next(); + //$$ if (entity.isDead) { + //$$ int chunkX = entity.chunkCoordX; + //$$ int chunkY = entity.chunkCoordZ; + //$$ + //#if MC>=11400 + //$$ if (entity.addedToChunk && world.getChunkProvider().provideChunk(chunkX, chunkY, false, false) != null) { + //#else + //#if MC>=10904 + //$$ if (entity.addedToChunk && world.getChunkProvider().getLoadedChunk(chunkX, chunkY) != null) { + //#else + //$$ if (entity.addedToChunk && world.getChunkProvider().chunkExists(chunkX, chunkY)) { + //#endif + //#endif + //$$ world.getChunkFromChunkCoords(chunkX, chunkY).removeEntity(entity); + //$$ } + //$$ + //$$ iter.remove(); + //$$ world.onEntityRemoved(entity); + //$$ } + //$$ + //$$ } + //#endif + } + /** * Process a packet and return the result. * @param p The packet to process @@ -824,7 +845,13 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend } } - return asyncMode ? processPacketAsync(p) : processPacketSync(p); + if (asyncMode) { + return processPacketAsync(p); + } else { + Packet fp = p; + mc.send(() -> processPacketSync(fp)); + return p; + } } @Override @@ -1085,6 +1112,10 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend // Synchronous packet processing // ///////////////////////////////////////////////////////// + // Even in sync mode, we send from another thread because mods may rely on that + private final ExecutorService syncSender = Executors.newSingleThreadExecutor(runnable -> + new Thread(runnable, "replaymod-sync-sender")); + /** * Sends all packets until the specified timestamp is reached (inclusive). * If the timestamp is smaller than the last packet sent, the replay is restarted from the beginning. @@ -1093,6 +1124,36 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend @Override public void sendPacketsTill(int timestamp) { Preconditions.checkState(!asyncMode, "This method cannot be used in async mode. Use jumpToTime(int) instead."); + + // Submit our target to the sender thread and track its progress + AtomicBoolean doneSending = new AtomicBoolean(); + syncSender.submit(() -> { + try { + doSendPacketsTill(timestamp); + } finally { + doneSending.set(true); + } + }); + + // Drain the task queue while we are sending (in case a mod blocks the io thread waiting for the main thread) + while (!doneSending.get()) { + executeTaskQueue(); + + // Wait until the sender thread has made progress + try { + //noinspection BusyWait + Thread.sleep(0, 100_000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return; + } + } + + // Everything has been sent, drain the queue one last time + executeTaskQueue(); + } + + private void doSendPacketsTill(int timestamp) { try { while (ctx == null && !terminate) { // Make sure channel is ready Thread.sleep(10); @@ -1112,7 +1173,7 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend loginPhase = true; startFromBeginning = false; nextPacket = null; - replayHandler.restartedReplay(); + ReplayMod.instance.runSync(replayHandler::restartedReplay); } if (replayIn == null) { @@ -1159,7 +1220,30 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend } } - protected Packet processPacketSync(Packet p) { + private void executeTaskQueue() { + //#if MC>=11400 + ((MCVer.MinecraftMethodAccessor) mc).replayModExecuteTaskQueue(); + //#else + //$$ java.util.Queue> scheduledTasks = ((MinecraftAccessor) mc).getScheduledTasks(); + //$$ + //$$ // Live-lock detection: if we already hold the lock, then the sender thread will never be able to queue its + //$$ // tasks + //$$ if (Thread.holdsLock(scheduledTasks)) { + //$$ throw new IllegalStateException("Task queue already locked. " + + //$$ "You may want to use `Scheduler.runLaterWithoutLock` to run while the lock is not taken."); + //$$ } + //$$ + //$$ //noinspection SynchronizationOnLocalVariableOrMethodParameter + //$$ synchronized (scheduledTasks) { + //$$ while (!scheduledTasks.isEmpty()) { + //$$ scheduledTasks.poll().run(); + //$$ } + //$$ } + //#endif + ReplayMod.instance.runTasks(); + } + + protected void processPacketSync(Packet p) { //#if MC>=10904 if (p instanceof UnloadChunkS2CPacket) { UnloadChunkS2CPacket packet = (UnloadChunkS2CPacket) p; @@ -1280,7 +1364,6 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend } } } - return p; // During synchronous playback everything is sent normally } private void forcePositionForVehicleAndSelf(Entity entity) { diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index 5f5d7462..0b613f4a 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -137,13 +137,13 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe LOGGER.info("Initialized quick replay sender in " + (System.currentTimeMillis() - start) + "ms"); } catch (Throwable e) { LOGGER.error("Initializing quick replay sender:", e); - mod.getCore().runLater(() -> { + mod.getCore().runLaterWithoutLock(() -> { mod.getCore().printWarningToChat("Error initializing quick replay sender: %s", e.getLocalizedMessage()); promise.setException(e); }); return; } - mod.getCore().runLater(() -> promise.set(null)); + mod.getCore().runLaterWithoutLock(() -> promise.set(null)); }).start(); return promise; } From abfb3d46f3abc478e4ff1f6ae85daa26b1ecc302 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 27 Feb 2022 13:28:10 +0100 Subject: [PATCH 05/28] Use per-version folder for pack.mcmeta Comments are not supported here, and depending on the exact environment, that may break stuff (seems to only happen on fabric in dev). --- src/main/resources/pack.mcmeta | 8 -------- versions/1.12.2/src/main/resources/pack.mcmeta | 6 ++++++ versions/1.9.4/src/main/resources/pack.mcmeta | 6 ++++++ 3 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 versions/1.12.2/src/main/resources/pack.mcmeta create mode 100644 versions/1.9.4/src/main/resources/pack.mcmeta diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index e491ce51..6afaebbc 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,14 +1,6 @@ { "pack": { "description": "ReplayMod resources", - //#if MC>=11400 "pack_format": 4 - //#else - //#if MC>=11002 - //$$ "pack_format": 2 - //#else - //$$ "pack_format": 1 - //#endif - //#endif } } diff --git a/versions/1.12.2/src/main/resources/pack.mcmeta b/versions/1.12.2/src/main/resources/pack.mcmeta new file mode 100644 index 00000000..5259b825 --- /dev/null +++ b/versions/1.12.2/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "ReplayMod resources", + "pack_format": 2 + } +} diff --git a/versions/1.9.4/src/main/resources/pack.mcmeta b/versions/1.9.4/src/main/resources/pack.mcmeta new file mode 100644 index 00000000..9a0e3411 --- /dev/null +++ b/versions/1.9.4/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "ReplayMod resources", + "pack_format": 1 + } +} From 6cc370a117756afbe2cb50c02a7da1101d83f83c Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 28 Feb 2022 15:57:35 +0100 Subject: [PATCH 06/28] Workaround Java breaking with symlinks (fixes #660) --- .../com/replaymod/core/SettingsRegistryBackend.java | 3 ++- .../replaymod/core/files/ReplayFoldersService.java | 10 ++++++---- src/main/java/com/replaymod/core/utils/Utils.java | 13 +++++++++++++ .../com/replaymod/editor/gui/MarkerProcessor.java | 1 - .../replaymod/recording/packet/PacketListener.java | 1 - 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/replaymod/core/SettingsRegistryBackend.java b/src/main/java/com/replaymod/core/SettingsRegistryBackend.java index eb996915..90f50d86 100644 --- a/src/main/java/com/replaymod/core/SettingsRegistryBackend.java +++ b/src/main/java/com/replaymod/core/SettingsRegistryBackend.java @@ -22,6 +22,7 @@ import java.nio.file.WatchService; import java.util.List; import java.util.Map; +import static com.replaymod.core.utils.Utils.ensureDirectoryExists; import static com.replaymod.core.versions.MCVer.getMinecraft; class SettingsRegistryBackend { @@ -179,7 +180,7 @@ class SettingsRegistryBackend { Gson gson = new GsonBuilder().setPrettyPrinting().create(); String config = gson.toJson(root); try { - Files.createDirectories(configFile.getParent()); + ensureDirectoryExists(configFile.getParent()); Files.write(configFile, config.getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { e.printStackTrace(); diff --git a/src/main/java/com/replaymod/core/files/ReplayFoldersService.java b/src/main/java/com/replaymod/core/files/ReplayFoldersService.java index 6587704d..2803b891 100644 --- a/src/main/java/com/replaymod/core/files/ReplayFoldersService.java +++ b/src/main/java/com/replaymod/core/files/ReplayFoldersService.java @@ -10,6 +10,8 @@ import java.net.URLDecoder; import java.nio.file.Files; import java.nio.file.Path; +import static com.replaymod.core.utils.Utils.ensureDirectoryExists; + public class ReplayFoldersService { private final Path mcDir = MinecraftClient.getInstance().runDirectory.toPath(); private final SettingsRegistry settings; @@ -19,14 +21,14 @@ public class ReplayFoldersService { } public Path getReplayFolder() throws IOException { - return Files.createDirectories(mcDir.resolve(settings.get(Setting.RECORDING_PATH))); + return ensureDirectoryExists(mcDir.resolve(settings.get(Setting.RECORDING_PATH))); } /** * Folder into which replay backups are saved before the MarkerProcessor is unleashed. */ public Path getRawReplayFolder() throws IOException { - return Files.createDirectories(getReplayFolder().resolve("raw")); + return ensureDirectoryExists(getReplayFolder().resolve("raw")); } /** @@ -34,7 +36,7 @@ public class ReplayFoldersService { * Distinct from the main folder, so they cannot be opened while they are still saving. */ public Path getRecordingFolder() throws IOException { - return Files.createDirectories(getReplayFolder().resolve("recording")); + return ensureDirectoryExists(getReplayFolder().resolve("recording")); } /** @@ -42,7 +44,7 @@ public class ReplayFoldersService { * Distinct from the recording folder cause people kept confusing them with recordings. */ public Path getCacheFolder() throws IOException { - Path path = Files.createDirectories(mcDir.resolve(settings.get(Setting.CACHE_PATH))); + Path path = ensureDirectoryExists(mcDir.resolve(settings.get(Setting.CACHE_PATH))); try { Files.setAttribute(path, "dos:hidden", true); } catch (UnsupportedOperationException ignored) { diff --git a/src/main/java/com/replaymod/core/utils/Utils.java b/src/main/java/com/replaymod/core/utils/Utils.java index 50c328c8..b082c276 100644 --- a/src/main/java/com/replaymod/core/utils/Utils.java +++ b/src/main/java/com/replaymod/core/utils/Utils.java @@ -57,6 +57,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileAttribute; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; @@ -356,4 +359,14 @@ public class Utils { configure.accept(instance); return instance; } + + /** + * Like {@link Files#createDirectories(Path, FileAttribute[])} but doesn't explode if it's a symlink. + */ + public static Path ensureDirectoryExists(Path path) throws IOException { + // Who in their right mind thought the default behavior of throwing when the target is a link to a directory + // was the preferred behavior?! Everyone has to fall for this at least once to learn it... + // https://bugs.openjdk.java.net/browse/JDK-8130464 + return Files.createDirectories(Files.exists(path) ? path.toRealPath() : path); + } } diff --git a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java index 0bce65f3..7231cecf 100644 --- a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java +++ b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java @@ -133,7 +133,6 @@ public class MarkerProcessor { for (int i = 1; Files.exists(inputPath); i++) { inputPath = inputPath.resolveSibling(replayName + "." + i + ".mcpr"); } - Files.createDirectories(inputPath.getParent()); Files.move(path, inputPath); try (ReplayFile inputReplayFile = mod.files.open(inputPath)) { diff --git a/src/main/java/com/replaymod/recording/packet/PacketListener.java b/src/main/java/com/replaymod/recording/packet/PacketListener.java index e37fefad..f99833a5 100644 --- a/src/main/java/com/replaymod/recording/packet/PacketListener.java +++ b/src/main/java/com/replaymod/recording/packet/PacketListener.java @@ -285,7 +285,6 @@ public class PacketListener extends ChannelInboundHandlerAdapter { for (int i = 1; Files.exists(rawPath); i++) { rawPath = rawPath.resolveSibling(replayName + "." + i + ".mcpr"); } - Files.createDirectories(rawPath.getParent()); replayFile.saveTo(rawPath.toFile()); replayFile.close(); From 4a5df2ad6d7b387db5ffc9e3632fb384d8e60a36 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 6 Mar 2022 13:36:30 +0100 Subject: [PATCH 07/28] Use `GameRenderer.renderHand` boolean field instead of mixin Should be more compatible with mods which replace the entire renderHand method (thereby making our mixin non-applicable), at least as long as these mods check `renderHand` as well (Iris explicitly does). This will become important once #638 is fixed. --- .../render/hooks/EntityRendererHandler.java | 7 ++++ .../render/mixin/GameRendererAccessor.java | 13 +++++++ .../mixin/Mixin_Omnidirectional_SkipHand.java | 34 ------------------- .../resources/mixins.render.replaymod.json | 2 +- 4 files changed, 21 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/replaymod/render/mixin/GameRendererAccessor.java delete mode 100644 src/main/java/com/replaymod/render/mixin/Mixin_Omnidirectional_SkipHand.java diff --git a/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java b/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java index 8dbd50f7..ee68b87e 100644 --- a/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java +++ b/src/main/java/com/replaymod/render/hooks/EntityRendererHandler.java @@ -8,6 +8,7 @@ import com.replaymod.render.Setting; import com.replaymod.render.capturer.CaptureData; import com.replaymod.render.capturer.RenderInfo; import com.replaymod.render.capturer.WorldRenderer; +import com.replaymod.render.mixin.GameRendererAccessor; import com.replaymod.replay.ReplayModReplay; import de.johni0702.minecraft.gui.utils.EventRegistrations; import net.minecraft.client.MinecraftClient; @@ -81,11 +82,16 @@ public class EntityRendererHandler extends EventRegistrations implements WorldRe //#endif if (mc.world != null && mc.player != null) { + GameRendererAccessor gameRenderer = (GameRendererAccessor) mc.gameRenderer; Screen orgScreen = mc.currentScreen; boolean orgPauseOnLostFocus = mc.options.pauseOnLostFocus; + boolean orgRenderHand = gameRenderer.getRenderHand(); try { mc.currentScreen = null; // do not want to render the current screen (that'd just be the progress gui) mc.options.pauseOnLostFocus = false; // do not want the pause menu to open if the window is unfocused + if (omnidirectional) { + gameRenderer.setRenderHand(false); // makes no sense, we wouldn't even know where to put it + } //#if MC>=11400 mc.gameRenderer.render(partialTicks, finishTimeNano, true); @@ -100,6 +106,7 @@ public class EntityRendererHandler extends EventRegistrations implements WorldRe } finally { mc.currentScreen = orgScreen; mc.options.pauseOnLostFocus = orgPauseOnLostFocus; + gameRenderer.setRenderHand(orgRenderHand); } } diff --git a/src/main/java/com/replaymod/render/mixin/GameRendererAccessor.java b/src/main/java/com/replaymod/render/mixin/GameRendererAccessor.java new file mode 100644 index 00000000..e74e16bc --- /dev/null +++ b/src/main/java/com/replaymod/render/mixin/GameRendererAccessor.java @@ -0,0 +1,13 @@ +package com.replaymod.render.mixin; + +import net.minecraft.client.render.GameRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(GameRenderer.class) +public interface GameRendererAccessor { + @Accessor + boolean getRenderHand(); + @Accessor + void setRenderHand(boolean value); +} diff --git a/src/main/java/com/replaymod/render/mixin/Mixin_Omnidirectional_SkipHand.java b/src/main/java/com/replaymod/render/mixin/Mixin_Omnidirectional_SkipHand.java deleted file mode 100644 index 939f6ec8..00000000 --- a/src/main/java/com/replaymod/render/mixin/Mixin_Omnidirectional_SkipHand.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.replaymod.render.mixin; - -import com.replaymod.render.hooks.EntityRendererHandler; -import net.minecraft.client.render.Camera; -import net.minecraft.client.render.GameRenderer; -import net.minecraft.client.util.math.MatrixStack; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(GameRenderer.class) -public abstract class Mixin_Omnidirectional_SkipHand implements EntityRendererHandler.IEntityRenderer { - @Inject(method = "renderHand", at = @At("HEAD"), cancellable = true) - private void replayModRender_renderSpectatorHand( - //#if MC>=11500 - MatrixStack matrixStack, - //#endif - //#if MC>=11400 - Camera camera, - //#endif - float partialTicks, - //#if MC<11400 - //$$ int renderPass, - //#endif - CallbackInfo ci - ) { - EntityRendererHandler handler = replayModRender_getHandler(); - if (handler != null && handler.omnidirectional) { - // No spectator hands during 360° view, we wouldn't even know where to put it - ci.cancel(); - } - } -} diff --git a/src/main/resources/mixins.render.replaymod.json b/src/main/resources/mixins.render.replaymod.json index 108c1484..ba783acd 100644 --- a/src/main/resources/mixins.render.replaymod.json +++ b/src/main/resources/mixins.render.replaymod.json @@ -16,7 +16,6 @@ "Mixin_Omnidirectional_Camera", "Mixin_Omnidirectional_DisableFrustumCulling", "Mixin_Omnidirectional_Rotation", - "Mixin_Omnidirectional_SkipHand", "Mixin_PreserveDepthDuringGuiRendering", "Mixin_SkipBlockOutlinesDuringRender", "Mixin_SkipHudDuringRender", @@ -39,6 +38,7 @@ "Mixin_PreserveDepthDuringHandRendering", "Mixin_WindowsWorkaroundForTinyEXRNatives", //#endif + "GameRendererAccessor", "MainWindowAccessor", "WorldRendererAccessor", //#if MC>=10904 From fb7900d80502b93c18188a0d3a4f03527a23f2a2 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 28 Feb 2022 16:29:57 +0100 Subject: [PATCH 08/28] Fix missing hand with Iris 1.1.3 and above (fixes #638) --- .../core/ReplayModMixinConfigPlugin.java | 10 ++++++ .../mixin/Mixin_ShowSpectatedHand_Iris.java | 35 +++++++++++++++++++ .../resources/mixins.replay.replaymod.json | 1 + 3 files changed, 46 insertions(+) create mode 100644 src/main/java/com/replaymod/replay/mixin/Mixin_ShowSpectatedHand_Iris.java diff --git a/src/main/java/com/replaymod/core/ReplayModMixinConfigPlugin.java b/src/main/java/com/replaymod/core/ReplayModMixinConfigPlugin.java index 08259e21..443a21e0 100644 --- a/src/main/java/com/replaymod/core/ReplayModMixinConfigPlugin.java +++ b/src/main/java/com/replaymod/core/ReplayModMixinConfigPlugin.java @@ -9,6 +9,10 @@ import java.io.IOException; import java.util.List; import java.util.Set; +//#if FABRIC +import net.fabricmc.loader.api.FabricLoader; +//#endif + //#if MC>=11400 import java.io.InputStream; //#else @@ -34,6 +38,9 @@ public class ReplayModMixinConfigPlugin implements IMixinConfigPlugin { private final Logger logger = LogManager.getLogger("replaymod/mixin"); private final boolean hasOF = hasClass("optifine.OptiFineForgeTweaker") || hasClass("me.modmuss50.optifabric.mod.Optifabric"); + //#if FABRIC + private final boolean hasIris = FabricLoader.getInstance().isModLoaded("iris"); + //#endif { logger.debug("hasOF: " + hasOF); @@ -49,6 +56,9 @@ public class ReplayModMixinConfigPlugin implements IMixinConfigPlugin { } if (mixinClassName.endsWith("_OF")) return hasOF; if (mixinClassName.endsWith("_NoOF")) return !hasOF; + //#if FABRIC + if (mixinClassName.endsWith("_Iris")) return hasIris; + //#endif return true; } diff --git a/src/main/java/com/replaymod/replay/mixin/Mixin_ShowSpectatedHand_Iris.java b/src/main/java/com/replaymod/replay/mixin/Mixin_ShowSpectatedHand_Iris.java new file mode 100644 index 00000000..48e8c8a3 --- /dev/null +++ b/src/main/java/com/replaymod/replay/mixin/Mixin_ShowSpectatedHand_Iris.java @@ -0,0 +1,35 @@ +//#if MC>=11400 +package com.replaymod.replay.mixin; + +import com.replaymod.replay.camera.CameraEntity; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.world.GameMode; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import static com.replaymod.core.versions.MCVer.getMinecraft; + +@Pseudo +@Mixin(targets = "net.coderbot.iris.pipeline.HandRenderer", remap = false) +public abstract class Mixin_ShowSpectatedHand_Iris { + @Redirect( + method = "*", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;getCurrentGameMode()Lnet/minecraft/world/GameMode;", + remap = true + ) + ) + private GameMode getGameMode(ClientPlayerInteractionManager interactionManager) { + ClientPlayerEntity camera = getMinecraft().player; + if (camera instanceof CameraEntity) { + // alternative doesn't really matter, the caller only checks for equality to SPECTATOR + return camera.isSpectator() ? GameMode.SPECTATOR : GameMode.SURVIVAL; + } + return interactionManager.getCurrentGameMode(); + } +} +//#endif diff --git a/src/main/resources/mixins.replay.replaymod.json b/src/main/resources/mixins.replay.replaymod.json index 356d50cc..f4205619 100644 --- a/src/main/resources/mixins.replay.replaymod.json +++ b/src/main/resources/mixins.replay.replaymod.json @@ -23,6 +23,7 @@ "ClientWorldAccessor", "EntityLivingBaseAccessor", //#if MC>=11400 + "Mixin_ShowSpectatedHand_Iris", "Mixin_ShowSpectatedHand_NoOF", "Mixin_ShowSpectatedHand_OF", //#else From fe67d7fa1418ffdb5858ceca63b7c46d19642bbd Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Mon, 28 Feb 2022 17:22:15 +0100 Subject: [PATCH 09/28] Link to our docs in the "Missing ffmpeg" screen (closes #663) --- .../com/replaymod/render/gui/GuiNoFfmpeg.java | 48 +++++++++++++++++++ .../replaymod/render/gui/GuiRenderQueue.java | 13 +---- .../render/gui/GuiRenderSettings.java | 13 +---- src/main/resources/assets/replaymod/lang | 2 +- 4 files changed, 51 insertions(+), 25 deletions(-) create mode 100644 src/main/java/com/replaymod/render/gui/GuiNoFfmpeg.java diff --git a/src/main/java/com/replaymod/render/gui/GuiNoFfmpeg.java b/src/main/java/com/replaymod/render/gui/GuiNoFfmpeg.java new file mode 100644 index 00000000..6261f861 --- /dev/null +++ b/src/main/java/com/replaymod/render/gui/GuiNoFfmpeg.java @@ -0,0 +1,48 @@ +package com.replaymod.render.gui; + +import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.container.GuiScreen; +import de.johni0702.minecraft.gui.element.GuiButton; +import de.johni0702.minecraft.gui.element.GuiLabel; +import de.johni0702.minecraft.gui.layout.HorizontalLayout; +import de.johni0702.minecraft.gui.layout.VerticalLayout; + +import java.net.URI; + +import static com.replaymod.core.versions.MCVer.openURL; +import static de.johni0702.minecraft.gui.versions.MCVer.setClipboardString; + +public class GuiNoFfmpeg extends GuiScreen { + + private static final String LINK = "https://www.replaymod.com/docs/#installing-ffmpeg"; + + private final GuiLabel message = new GuiLabel() + .setI18nText("replaymod.gui.rendering.error.message"); + private final GuiLabel link = new GuiLabel() + .setText(LINK); + private final GuiButton openLinkButton = new GuiButton() + .setI18nLabel("chat.link.open") + .setSize(100, 20) + .onClick(() -> openURL(URI.create(LINK))); + private final GuiButton copyToClipboardButton = new GuiButton() + .setI18nLabel("chat.copy") + .setSize(100, 20) + .onClick(() -> setClipboardString(LINK)); + private final GuiButton backButton = new GuiButton() + .setI18nLabel("gui.back") + .setSize(100, 20); + private final GuiPanel buttons = new GuiPanel() + .setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(4)) + .addElements(null, openLinkButton, copyToClipboardButton, backButton); + + { + setBackground(Background.DIRT); + setTitle(new GuiLabel().setI18nText("replaymod.gui.rendering.error.title")); + setLayout(new VerticalLayout(VerticalLayout.Alignment.CENTER).setSpacing(30)); + addElements(new VerticalLayout.Data(0.5), message, link, buttons); + } + + public GuiNoFfmpeg(Runnable goBack) { + backButton.onClick(goBack); + } +} diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java index db37e673..a1b8b6a2 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java @@ -37,7 +37,6 @@ import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadablePoint; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.NoticeScreen; import net.minecraft.util.crash.CrashReport; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.tuple.Pair; @@ -181,17 +180,7 @@ public class GuiRenderQueue extends AbstractGuiPopup implements videoRenderer.renderVideo(); } catch (FFmpegWriter.NoFFmpegException e) { LOGGER.error("Rendering video:", e); - NoticeScreen errorScreen = new NoticeScreen( - //#if MC>=11400 - container::display, - new TranslatableText("replaymod.gui.rendering.error.title"), - new TranslatableText("replaymod.gui.rendering.error.message") - //#else - //$$ I18n.format("replaymod.gui.rendering.error.title"), - //$$ I18n.format("replaymod.gui.rendering.error.message") - //#endif - ); - mc.openScreen(errorScreen); + mc.openScreen(new GuiNoFfmpeg(container::display).toMinecraft()); return; } catch (FFmpegWriter.FFmpegStartupException e) { int jobsToSkip = jobsDone; diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index 38d80a27..b8f67b17 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -30,7 +30,6 @@ import de.johni0702.minecraft.gui.utils.Utils; import de.johni0702.minecraft.gui.utils.lwjgl.Color; import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; -import net.minecraft.client.gui.screen.NoticeScreen; import net.minecraft.client.resource.language.I18n; import net.minecraft.util.crash.CrashReport; @@ -240,17 +239,7 @@ public class GuiRenderSettings extends AbstractGuiPopup { videoRenderer.renderVideo(); } catch (FFmpegWriter.NoFFmpegException e) { LOGGER.error("Rendering video:", e); - NoticeScreen errorScreen = new NoticeScreen( - //#if MC>=11400 - getScreen()::display, - new TranslatableText("replaymod.gui.rendering.error.title"), - new TranslatableText("replaymod.gui.rendering.error.message") - //#else - //$$ I18n.format("replaymod.gui.rendering.error.title"), - //$$ I18n.format("replaymod.gui.rendering.error.message") - //#endif - ); - getMinecraft().openScreen(errorScreen); + getMinecraft().openScreen(new GuiNoFfmpeg(getScreen()::display).toMinecraft()); } catch (FFmpegWriter.FFmpegStartupException e) { GuiExportFailed.tryToRecover(e, newSettings -> { // Update settings with fixed ffmpeg arguments diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index 54ab62bd..4b1698fc 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit 54ab62bdbb646c6b4f4b6d61a26183258e5a017d +Subproject commit 4b1698fcf805fea05d6c3f0ca6c14c63d7e731db From d9da2e135cb21c391f456e8829b2ea8f089fcb77 Mon Sep 17 00:00:00 2001 From: Jochem <29899660+JochCool@users.noreply.github.com> Date: Tue, 16 Nov 2021 00:10:21 +0100 Subject: [PATCH 10/28] Sync world border animation & movement to Replay speed Fixes an issue where the world border would move at normal speed when speeding up or slowing down the replay, and also at an incorrect speed when rendering the replay, causing it to get out of sync with the rest of the world. Co-authored-by: Jonas Herzig --- .../Mixin_UseReplayTime_ForMovement.java | 38 +++++++++++++++++++ .../Mixin_UseReplayTime_ForTexture.java | 29 ++++++++++++++ .../resources/mixins.replay.replaymod.json | 2 + 3 files changed, 69 insertions(+) create mode 100644 src/main/java/com/replaymod/replay/mixin/world_border/Mixin_UseReplayTime_ForMovement.java create mode 100644 src/main/java/com/replaymod/replay/mixin/world_border/Mixin_UseReplayTime_ForTexture.java diff --git a/src/main/java/com/replaymod/replay/mixin/world_border/Mixin_UseReplayTime_ForMovement.java b/src/main/java/com/replaymod/replay/mixin/world_border/Mixin_UseReplayTime_ForMovement.java new file mode 100644 index 00000000..1e8f9e2b --- /dev/null +++ b/src/main/java/com/replaymod/replay/mixin/world_border/Mixin_UseReplayTime_ForMovement.java @@ -0,0 +1,38 @@ +package com.replaymod.replay.mixin.world_border; + +import com.replaymod.core.versions.MCVer; +import com.replaymod.replay.ReplayHandler; +import com.replaymod.replay.ReplayModReplay; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +/** + * Normally Minecraft's world border movement is based off real time; + * this redirect ensures that it is synced with the time in the Replay instead. + */ +//#if MC>=11400 +// FIXME: preprocessor should be able to remap between fabric and forge +//#if FABRIC +@Mixin(targets = "net.minecraft.world.border.WorldBorder.MovingArea") +//#else +//$$ @Mixin(targets = "net.minecraft.world.border.WorldBorder.MovingBorderInfo") +//#endif +//#else +//$$ @Mixin(net.minecraft.world.border.WorldBorder.class) +//#endif +public class Mixin_UseReplayTime_ForMovement { + + //#if MC>=11400 + @Redirect(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Util;getMeasuringTimeMs()J")) + //#else + //$$ @Redirect(method = "*", at = @At(value = "INVOKE", target = "Ljava/lang/System;currentTimeMillis()J")) + //#endif + private long getWorldBorderTime() { + ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler(); + if (replayHandler != null) { + return replayHandler.getReplaySender().currentTimeStamp(); + } + return MCVer.milliTime(); + } +} diff --git a/src/main/java/com/replaymod/replay/mixin/world_border/Mixin_UseReplayTime_ForTexture.java b/src/main/java/com/replaymod/replay/mixin/world_border/Mixin_UseReplayTime_ForTexture.java new file mode 100644 index 00000000..9abf28c4 --- /dev/null +++ b/src/main/java/com/replaymod/replay/mixin/world_border/Mixin_UseReplayTime_ForTexture.java @@ -0,0 +1,29 @@ +package com.replaymod.replay.mixin.world_border; + +import com.replaymod.core.versions.MCVer; +import com.replaymod.replay.ReplayHandler; +import com.replaymod.replay.ReplayModReplay; +import net.minecraft.client.render.WorldRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +/** + * Normally Minecraft's world border texture animation is based off real time; + * this redirect ensures that it is synced with the time in the Replay instead. + */ +@Mixin(WorldRenderer.class) +public class Mixin_UseReplayTime_ForTexture { + //#if MC>=11400 + @Redirect(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Util;getMeasuringTimeMs()J")) + //#else + //$$ @Redirect(method = "*", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;getSystemTime()J")) + //#endif + private long getWorldBorderTime() { + ReplayHandler replayHandler = ReplayModReplay.instance.getReplayHandler(); + if (replayHandler != null) { + return replayHandler.getReplaySender().currentTimeStamp(); + } + return MCVer.milliTime(); + } +} diff --git a/src/main/resources/mixins.replay.replaymod.json b/src/main/resources/mixins.replay.replaymod.json index f4205619..02e6fe65 100644 --- a/src/main/resources/mixins.replay.replaymod.json +++ b/src/main/resources/mixins.replay.replaymod.json @@ -7,6 +7,8 @@ "client": [ "entity_tracking.Mixin_EntityExt", "entity_tracking.Mixin_FixPartialUpdates", + "world_border.Mixin_UseReplayTime_ForMovement", + "world_border.Mixin_UseReplayTime_ForTexture", "Mixin_FixNPCSkinCaching", //#if MC>=11800 //$$ "Mixin_FixEntityNotTracking", From 7615499cef5dd842bbaa91000901e8a0d1f7194a Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 2 Mar 2022 16:39:30 +0100 Subject: [PATCH 11/28] Add option to preserve alpha channel in export (closes #661) Default off cause it being on by default had confused quite a few people. --- .../advancedscreenshots/GuiCreateScreenshot.java | 2 +- src/main/java/com/replaymod/render/EXRWriter.java | 14 ++++++++++---- src/main/java/com/replaymod/render/PNGWriter.java | 7 +++++-- .../java/com/replaymod/render/RenderSettings.java | 10 ++++++++++ .../com/replaymod/render/gui/GuiExportFailed.java | 1 + .../replaymod/render/gui/GuiRenderSettings.java | 7 ++++++- .../replaymod/render/rendering/VideoRenderer.java | 4 ++-- src/main/resources/assets/replaymod/lang | 2 +- 8 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java b/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java index 62d316cf..cda0409d 100644 --- a/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java +++ b/src/main/java/com/replaymod/extras/advancedscreenshots/GuiCreateScreenshot.java @@ -40,7 +40,7 @@ public class GuiCreateScreenshot extends GuiRenderSettings implements Loadable { new GuiLabel().setI18nText("replaymod.gui.advancedscreenshots.resolution"), videoResolutionPanel, new GuiLabel().setI18nText("replaymod.gui.rendersettings.outputfile"), outputFileButton); - resetChildren(advancedPanel).addElements(null, nametagCheckbox, new GuiPanel().setLayout( + resetChildren(advancedPanel).addElements(null, nametagCheckbox, alphaCheckbox , new GuiPanel().setLayout( new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(15)) .addElements(new GridLayout.Data(0, 0.5), new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel, diff --git a/src/main/java/com/replaymod/render/EXRWriter.java b/src/main/java/com/replaymod/render/EXRWriter.java index 1b2348fe..0bf981d8 100644 --- a/src/main/java/com/replaymod/render/EXRWriter.java +++ b/src/main/java/com/replaymod/render/EXRWriter.java @@ -28,9 +28,11 @@ import static org.lwjgl.util.tinyexr.TinyEXR.*; public class EXRWriter implements FrameConsumer { private final Path outputFolder; + private final boolean keepAlpha; - public EXRWriter(Path outputFolder) throws IOException { + public EXRWriter(Path outputFolder, boolean keepAlpha) throws IOException { this.outputFolder = outputFolder; + this.keepAlpha = keepAlpha; Files.createDirectories(outputFolder); } @@ -92,11 +94,15 @@ public class EXRWriter implements FrameConsumer { bgrChannels[(i + 3) % 4] = channel; } } + + int alphaMask = keepAlpha ? 0 : 0xff; + for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { - for (FloatBuffer channel : bgrChannels) { - channel.put(((int) bgra.get() & 0xff) / 255f); - } + bgrChannels[0].put(((int) bgra.get() & 0xff) / 255f); // b + bgrChannels[1].put(((int) bgra.get() & 0xff) / 255f); // g + bgrChannels[2].put(((int) bgra.get() & 0xff) / 255f); // r + bgrChannels[3].put(((int) bgra.get() & 0xff | alphaMask) / 255f); // a } } if (depthFrame != null && depthChannel != null) { diff --git a/src/main/java/com/replaymod/render/PNGWriter.java b/src/main/java/com/replaymod/render/PNGWriter.java index 846e63be..525d154d 100644 --- a/src/main/java/com/replaymod/render/PNGWriter.java +++ b/src/main/java/com/replaymod/render/PNGWriter.java @@ -19,9 +19,11 @@ import java.util.Map; public class PNGWriter implements FrameConsumer { private final Path outputFolder; + private final boolean keepAlpha; - public PNGWriter(Path outputFolder) throws IOException { + public PNGWriter(Path outputFolder, boolean keepAlpha) throws IOException { this.outputFolder = outputFolder; + this.keepAlpha = keepAlpha; Files.createDirectories(outputFolder); } @@ -47,6 +49,7 @@ public class PNGWriter implements FrameConsumer { } private void withImage(BitmapFrame frame, IOConsumer consumer) throws IOException { + byte alphaMask = (byte) (keepAlpha ? 0 : 0xff); ByteBuffer buffer = frame.getByteBuffer(); ReadableDimension size = frame.getSize(); int width = size.getWidth(); @@ -58,7 +61,7 @@ public class PNGWriter implements FrameConsumer { byte g = buffer.get(); byte r = buffer.get(); byte a = buffer.get(); - image.setRGBA(x, y, r, g, b, a); + image.setRGBA(x, y, r, g, b, a | alphaMask); } } consumer.accept(image); diff --git a/src/main/java/com/replaymod/render/RenderSettings.java b/src/main/java/com/replaymod/render/RenderSettings.java index e83d4947..b358b718 100644 --- a/src/main/java/com/replaymod/render/RenderSettings.java +++ b/src/main/java/com/replaymod/render/RenderSettings.java @@ -151,6 +151,7 @@ public class RenderSettings { private final File outputFile; private final boolean renderNameTags; + private final boolean includeAlphaChannel; private final boolean stabilizeYaw; private final boolean stabilizePitch; private final boolean stabilizeRoll; @@ -187,6 +188,7 @@ public class RenderSettings { false, false, false, + false, null, 360, 180, @@ -209,6 +211,7 @@ public class RenderSettings { int bitRate, File outputFile, boolean renderNameTags, + boolean includeAlphaChannel, boolean stabilizeYaw, boolean stabilizePitch, boolean stabilizeRoll, @@ -231,6 +234,7 @@ public class RenderSettings { this.bitRate = bitRate; this.outputFile = outputFile; this.renderNameTags = renderNameTags; + this.includeAlphaChannel = includeAlphaChannel; this.stabilizeYaw = stabilizeYaw; this.stabilizePitch = stabilizePitch; this.stabilizeRoll = stabilizeRoll; @@ -256,6 +260,7 @@ public class RenderSettings { bitRate, outputFile, renderNameTags, + includeAlphaChannel, stabilizeYaw, stabilizePitch, stabilizeRoll, @@ -415,6 +420,10 @@ public class RenderSettings { return renderNameTags; } + public boolean isIncludeAlphaChannel() { + return includeAlphaChannel; + } + public boolean isStabilizeYaw() { return stabilizeYaw; } @@ -478,6 +487,7 @@ public class RenderSettings { ", bitRate=" + bitRate + ", outputFile=" + outputFile + ", renderNameTags=" + renderNameTags + + ", includeAlphaChannel=" + includeAlphaChannel + ", stabilizeYaw=" + stabilizeYaw + ", stabilizePitch=" + stabilizePitch + ", stabilizeRoll=" + stabilizeRoll + diff --git a/src/main/java/com/replaymod/render/gui/GuiExportFailed.java b/src/main/java/com/replaymod/render/gui/GuiExportFailed.java index de5e9c0e..56f9229f 100644 --- a/src/main/java/com/replaymod/render/gui/GuiExportFailed.java +++ b/src/main/java/com/replaymod/render/gui/GuiExportFailed.java @@ -90,6 +90,7 @@ public class GuiExportFailed extends GuiScreen { oldSettings.getBitRate(), oldSettings.getOutputFile(), oldSettings.isRenderNameTags(), + oldSettings.isIncludeAlphaChannel(), oldSettings.isStabilizeYaw(), oldSettings.isStabilizePitch(), oldSettings.isStabilizeRoll(), diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java index b8f67b17..170aa21a 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderSettings.java @@ -148,6 +148,9 @@ public class GuiRenderSettings extends AbstractGuiPopup { public final GuiCheckbox nametagCheckbox = new GuiCheckbox() .setI18nLabel("replaymod.gui.rendersettings.nametags"); + public final GuiCheckbox alphaCheckbox = new GuiCheckbox() + .setI18nLabel("replaymod.gui.rendersettings.includealpha"); + public final GuiPanel stabilizePanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(10)); public final GuiCheckbox stabilizeYaw = new GuiCheckbox(stabilizePanel) .setI18nLabel("replaymod.gui.yaw"); @@ -187,7 +190,7 @@ public class GuiRenderSettings extends AbstractGuiPopup { .setSize(200, 20).setValues(RenderSettings.AntiAliasing.values()).setSelected(RenderSettings.AntiAliasing.NONE); public final GuiPanel advancedPanel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(15)) - .addElements(null, nametagCheckbox, new GuiPanel().setLayout( + .addElements(null, nametagCheckbox, alphaCheckbox, new GuiPanel().setLayout( new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(15)) .addElements(new GridLayout.Data(0, 0.5), new GuiLabel().setI18nText("replaymod.gui.rendersettings.stabilizecamera"), stabilizePanel, @@ -522,6 +525,7 @@ public class GuiRenderSettings extends AbstractGuiPopup { } outputFileButton.setLabel(this.outputFile.getName()); nametagCheckbox.setChecked(settings.isRenderNameTags()); + alphaCheckbox.setChecked(settings.isIncludeAlphaChannel()); stabilizeYaw.setChecked(settings.isStabilizeYaw()); stabilizePitch.setChecked(settings.isStabilizePitch()); stabilizeRoll.setChecked(settings.isStabilizeRoll()); @@ -560,6 +564,7 @@ public class GuiRenderSettings extends AbstractGuiPopup { bitRateField.getInteger() << (10 * bitRateUnit.getSelected()), serialize && !userDefinedOutputFileName ? getParentFile(outputFile) : outputFile, nametagCheckbox.isChecked(), + alphaCheckbox.isChecked(), stabilizeYaw.isChecked() && (serialize || stabilizeYaw.isEnabled()), stabilizePitch.isChecked() && (serialize || stabilizePitch.isEnabled()), stabilizeRoll.isChecked() && (serialize || stabilizeRoll.isEnabled()), diff --git a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java index 9eb819d5..56d1daf6 100644 --- a/src/main/java/com/replaymod/render/rendering/VideoRenderer.java +++ b/src/main/java/com/replaymod/render/rendering/VideoRenderer.java @@ -136,12 +136,12 @@ public class VideoRenderer implements RenderInfo { FrameConsumer frameConsumer; if (settings.getEncodingPreset() == RenderSettings.EncodingPreset.EXR) { //#if MC>=11400 - frameConsumer = new EXRWriter(settings.getOutputFile().toPath()); + frameConsumer = new EXRWriter(settings.getOutputFile().toPath(), settings.isIncludeAlphaChannel()); //#else //$$ throw new UnsupportedOperationException("EXR requires LWJGL3"); //#endif } else if (settings.getEncodingPreset() == RenderSettings.EncodingPreset.PNG) { - frameConsumer = new PNGWriter(settings.getOutputFile().toPath()); + frameConsumer = new PNGWriter(settings.getOutputFile().toPath(), settings.isIncludeAlphaChannel()); } else { frameConsumer = new FFmpegWriter(this); } diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index 4b1698fc..188e85c4 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit 4b1698fcf805fea05d6c3f0ca6c14c63d7e731db +Subproject commit 188e85c4dbc9296c81231c37d7c18bb92560b2e7 From a8d05a2bca967a5f7620c652cc4e2802046bb53a Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 2 Mar 2022 16:44:31 +0100 Subject: [PATCH 12/28] Skip mod compat warning screen when in render queue (fixes #653) --- src/main/java/com/replaymod/render/gui/GuiRenderQueue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java index a1b8b6a2..86ca19dc 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java @@ -218,7 +218,7 @@ public class GuiRenderQueue extends AbstractGuiPopup implements ReplayFile replayFile = null; try { replayFile = mod.getCore().files.open(next.getKey().toPath()); - replayHandler = mod.startReplay(replayFile, true, false); + replayHandler = mod.startReplay(replayFile, false, false); } catch (IOException e) { Utils.error(LOGGER, container, CrashReport.create(e, "Opening replay"), () -> {}); container.display(); // Re-show the queue popup and the new error popup From e26480f0412d7e68c110458bd0887dfdb748c083 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 2 Mar 2022 16:48:36 +0100 Subject: [PATCH 13/28] Fix "Please wait" when jumping not being centered (fixes #646) --- src/main/java/com/replaymod/replay/ReplayHandler.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index adac3806..bb74e0dc 100644 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -603,6 +603,7 @@ public class ReplayHandler { // Render our please-wait-screen GuiScreen guiScreen = new GuiScreen(); guiScreen.setBackground(AbstractGuiScreen.Background.DIRT); + guiScreen.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER)); guiScreen.addElements(new HorizontalLayout.Data(0.5), new GuiLabel().setI18nText("replaymod.gui.pleasewait")); From 2e34a2abd91a448a6ed06c3dada8438e18644833 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 2 Mar 2022 16:54:59 +0100 Subject: [PATCH 14/28] Fix render timer breaking when system time changes (fixes #633) Unlike `currentTimeMillis`, `nanoTime` is guaranteed to be relative to a fixed point in time, regardless of system or wall-clock time. --- src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java b/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java index bc69f061..cd9c6268 100644 --- a/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java +++ b/src/main/java/com/replaymod/render/gui/GuiVideoRenderer.java @@ -141,7 +141,7 @@ public class GuiVideoRenderer extends GuiScreen implements Tickable { @Override public void tick() { - long current = System.currentTimeMillis(); + long current = System.nanoTime() / 1_000_000; //first, update the total render time (only if rendering is not paused and has already started) if(!renderer.isPaused() && renderer.getFramesDone() > 0 && prevTime > -1) { From b8cf7e6df4619278e552028524c32a6a9dd21af3 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 2 Mar 2022 17:58:10 +0100 Subject: [PATCH 15/28] Fix scrolling while replay paused on 1.12.2 and below (fixes #590) --- .../java/com/replaymod/core/mixin/MixinMinecraft.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/core/mixin/MixinMinecraft.java b/src/main/java/com/replaymod/core/mixin/MixinMinecraft.java index 62682744..754cd090 100644 --- a/src/main/java/com/replaymod/core/mixin/MixinMinecraft.java +++ b/src/main/java/com/replaymod/core/mixin/MixinMinecraft.java @@ -63,6 +63,7 @@ public abstract class MixinMinecraft PostRenderCallback.EVENT.invoker().postRender(); } //#else + //$$ @Shadow long systemTime; //#if MC>=10904 //$$ @Shadow protected abstract void runTickKeyboard() throws IOException; //$$ @Shadow protected abstract void runTickMouse() throws IOException; @@ -80,6 +81,8 @@ public abstract class MixinMinecraft //$$ public void replayModRunTickMouse() { //$$ try { //$$ runTickMouse(); + //$$ // Update last tick time (MC ignores inputs when there hasn't been a tick in 200ms) + //$$ systemTime = Minecraft.getSystemTime(); //$$ } catch (IOException e) { //$$ e.printStackTrace(); //$$ } @@ -94,7 +97,12 @@ public abstract class MixinMinecraft //$$ //$$ @Inject(method = "runTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;sendClickBlockToController(Z)V"), cancellable = true) //$$ private void doEarlyReturnFromRunTick(CallbackInfo ci) { - //$$ if (earlyReturn) ci.cancel(); + //$$ if (earlyReturn) { + //$$ ci.cancel(); + //$$ + //$$ // Update last tick time (MC ignores inputs when there hasn't been a tick in 200ms) + //$$ systemTime = Minecraft.getSystemTime(); + //$$ } //$$ } //#endif //$$ @Redirect( From d178c0980e8fe84ca01c7cc3d6095a6e6b420382 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 2 Mar 2022 18:10:52 +0100 Subject: [PATCH 16/28] Log when replays are opened (closes #565) --- src/main/java/com/replaymod/core/gui/RestoreReplayGui.java | 5 +++++ src/main/java/com/replaymod/editor/gui/GuiEditReplay.java | 6 ++++++ .../com/replaymod/replay/gui/screen/GuiReplayViewer.java | 4 +++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java b/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java index 3a9f13c0..c68a3210 100644 --- a/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java +++ b/src/main/java/com/replaymod/core/gui/RestoreReplayGui.java @@ -56,11 +56,16 @@ public class RestoreReplayGui extends AbstractGuiScreen { new GuiLabel().setI18nText("replaymod.gui.restorereplay1"), new GuiLabel().setI18nText("replaymod.gui.restorereplay2", Files.getNameWithoutExtension(file.getName())), new GuiLabel().setI18nText("replaymod.gui.restorereplay3")); + + LOGGER.info("Found partially saved replay, offering recovery: " + file); + yesButton.onClick(() -> { + LOGGER.info("Attempting recovery: " + file); recoverInBackground(); parent.display(); }); noButton.onClick(() -> { + LOGGER.info("Recovery rejected, marking for deletion: " + file); try { File tmp = new File(file.getParentFile(), file.getName() + ".tmp"); File deleted = new File(file.getParentFile(), file.getName() + ".del"); diff --git a/src/main/java/com/replaymod/editor/gui/GuiEditReplay.java b/src/main/java/com/replaymod/editor/gui/GuiEditReplay.java index 205d4059..0d5aefc8 100644 --- a/src/main/java/com/replaymod/editor/gui/GuiEditReplay.java +++ b/src/main/java/com/replaymod/editor/gui/GuiEditReplay.java @@ -21,6 +21,8 @@ import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.utils.lwjgl.Color; import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; import net.minecraft.util.crash.CrashReport; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.io.IOException; import java.nio.file.Path; @@ -31,6 +33,8 @@ import java.util.function.Consumer; import java.util.stream.Collectors; public class GuiEditReplay extends AbstractGuiPopup { + private static final Logger LOGGER = LogManager.getLogger(); + private final Path inputPath; private final EditTimeline timeline; @@ -60,6 +64,8 @@ public class GuiEditReplay extends AbstractGuiPopup { super(container); this.inputPath = inputPath; + LOGGER.info("Opening replay in editor: " + inputPath); + try (ReplayFile replayFile = ReplayMod.instance.files.open(inputPath)) { markers = replayFile.getMarkers().or(HashSet::new); timeline = new EditTimeline(new HashSet<>(markers), markers -> this.markers = markers); diff --git a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java index de4279aa..5525618e 100644 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -95,8 +95,10 @@ public class GuiReplayViewer extends GuiScreen { List selected = list.getSelected(); if (selected.size() == 1) { + File file = selected.get(0).file; + LOGGER.info("Opening replay in viewer: " + file); try { - mod.startReplay(selected.get(0).file); + mod.startReplay(file); } catch (IOException e) { e.printStackTrace(); } From f9c58a11652713866f3fd8a91800066d4e802e88 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Wed, 2 Mar 2022 18:40:46 +0100 Subject: [PATCH 17/28] Try raw file name before percent encoding it (closes #536) --- .../java/com/replaymod/core/utils/Utils.java | 43 ++++++++++++++++++- .../recording/gui/GuiSavingReplay.java | 4 +- .../handler/ConnectionEventHandler.java | 2 +- .../replay/gui/screen/GuiReplayViewer.java | 12 +++--- 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/replaymod/core/utils/Utils.java b/src/main/java/com/replaymod/core/utils/Utils.java index b082c276..688edc7c 100644 --- a/src/main/java/com/replaymod/core/utils/Utils.java +++ b/src/main/java/com/replaymod/core/utils/Utils.java @@ -55,10 +55,12 @@ import javax.net.ssl.TrustManagerFactory; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.nio.file.attribute.FileAttribute; import java.security.KeyManagementException; import java.security.KeyStore; @@ -169,8 +171,45 @@ public class Utils { private static final PercentEscaper REPLAY_NAME_ENCODER = new PercentEscaper(".-_ ", false); - public static String replayNameToFileName(String replayName) { - return REPLAY_NAME_ENCODER.escape(replayName) + ".mcpr"; + public static Path replayNameToPath(Path folder, String replayName) { + // If we can, prefer directly using the replay name as the file name + if (isUsable(folder, replayName + ".mcpr")) { + return folder.resolve(replayName + ".mcpr"); + } else { + // otherwise, fall back to percent encoding + return folder.resolve(REPLAY_NAME_ENCODER.escape(replayName) + ".mcpr"); + } + } + + /** + * Checks whether a given file name is actually usable with the file system / operating system at the given folder. + */ + private static boolean isUsable(Path folder, String fileName) { + Path path = folder.resolve(fileName); + if (Files.exists(path)) { + return true; // if it already exits, it's definitely usable + } + + // Otherwise, there's no sure way to know, so we just gotta try + try (OutputStream outputStream = Files.newOutputStream(path, StandardOpenOption.CREATE_NEW)) { + outputStream.flush(); + } catch (IOException e) { + return false; + } + + // Looking good, but now we gotta clean up that mess (and Anti-Virus / Cloud Sync are know to lock them) + int attempts = 0; + while (true) { + try { + Files.delete(path); + return true; + } catch (IOException e) { + if (attempts++ > 100) { + LOGGER.warn("Repeatedly failed to clean up temporary test file at " + path + ": ", e); + return false; // while we were able to use it, it's taken now and we can't get it back + } + } + } } public static String fileNameToReplayName(String fileName) { diff --git a/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java b/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java index 6fb66db9..9e33903a 100644 --- a/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java +++ b/src/main/java/com/replaymod/recording/gui/GuiSavingReplay.java @@ -155,9 +155,9 @@ public class GuiSavingReplay { try { Path replaysFolder = core.folders.getReplayFolder(); - Path newPath = replaysFolder.resolve(Utils.replayNameToFileName(newName)); + Path newPath = Utils.replayNameToPath(replaysFolder, newName); for (int i = 1; Files.exists(newPath); i++) { - newPath = replaysFolder.resolve(Utils.replayNameToFileName(newName + " (" + i + ")")); + newPath = Utils.replayNameToPath(replaysFolder, newName + " (" + i + ")"); } Files.move(path, newPath); } catch (IOException e) { diff --git a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java index 60a43a50..0211c8e0 100644 --- a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java @@ -127,7 +127,7 @@ public class ConnectionEventHandler { } String name = sdf.format(Calendar.getInstance().getTime()); - Path outputPath = core.folders.getRecordingFolder().resolve(Utils.replayNameToFileName(name)); + Path outputPath = Utils.replayNameToPath(core.folders.getRecordingFolder(), name); ReplayFile replayFile = core.files.open(outputPath); replayFile.writeModInfo(ModCompat.getInstalledNetworkMods()); diff --git a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java index 5525618e..9a2dec3c 100644 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -49,6 +49,8 @@ import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -132,8 +134,8 @@ public class GuiReplayViewer extends GuiScreen { public final GuiButton renameButton = new GuiButton().onClick(new Runnable() { @Override public void run() { - final File file = list.getSelected().get(0).file; - String name = Utils.fileNameToReplayName(file.getName()); + final Path path = list.getSelected().get(0).file.toPath(); + String name = Utils.fileNameToReplayName(path.getFileName().toString()); final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true).setText(name); final GuiYesNoPopup popup = GuiYesNoPopup.open(GuiReplayViewer.this, new GuiLabel().setI18nText("replaymod.gui.viewer.rename.name").setColor(Colors.BLACK), @@ -149,16 +151,16 @@ public class GuiReplayViewer extends GuiScreen { } }).onTextChanged(obj -> { popup.getYesButton().setEnabled(!nameField.getText().isEmpty() - && !new File(file.getParentFile(), Utils.replayNameToFileName(nameField.getText())).exists()); + && Files.notExists(Utils.replayNameToPath(path.getParent(), nameField.getText()))); }); popup.onAccept(() -> { // Sanitize their input String newName = nameField.getText().trim(); // This file is what they want - File targetFile = new File(file.getParentFile(), Utils.replayNameToFileName(newName)); + Path targetPath = Utils.replayNameToPath(path.getParent(), newName); try { // Finally, try to move it - FileUtils.moveFile(file, targetFile); + Files.move(path, targetPath); } catch (IOException e) { // We failed (might also be their OS) e.printStackTrace(); From 0356fef61d9d7128fd19febc7345bfe5a22456c2 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 5 Mar 2022 11:23:00 +0100 Subject: [PATCH 18/28] Fix crash when receiving resource pack on 1.8-1.12.2 (fixes #86) --- src/main/resources/mixins.recording.replaymod.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/resources/mixins.recording.replaymod.json b/src/main/resources/mixins.recording.replaymod.json index e6c9ee41..09545afc 100644 --- a/src/main/resources/mixins.recording.replaymod.json +++ b/src/main/resources/mixins.recording.replaymod.json @@ -11,8 +11,10 @@ "SPacketSpawnMobAccessor", "SPacketSpawnPlayerAccessor", "MixinServerInfo", - //#if MC>=11400 + //#if MC>=10800 "MixinDownloadingPackFinder", + //#endif + //#if MC>=11400 "MixinMouseHelper", //#endif //#if MC>=10904 From 8573a9b208f0574e2be6a0db92f400bdaa601f2d Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 5 Mar 2022 11:37:36 +0100 Subject: [PATCH 19/28] Prevent jumping while path is playing (closes #694) --- src/main/java/com/replaymod/replay/ReplayHandler.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index bb74e0dc..75370265 100644 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -534,6 +534,10 @@ public class ReplayHandler { } public void doJump(int targetTime, boolean retainCameraPosition) { + if (!getReplaySender().isAsyncMode()) { + return; // path playback, rendering, etc. -> no jumping allowed + } + //#if MC>=10904 if (getReplaySender() == quickReplaySender) { // Always round to full tick From 1efb17e144922338043d5eb752c8ac040acd4edb Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sat, 5 Mar 2022 13:25:31 +0100 Subject: [PATCH 20/28] Fix ODS rendering on Iris 1.1.3+ (fixes #693) --- build.gradle | 4 +++- .../render/capturer/IrisODSFrameCapturer.java | 12 ++++++++---- .../render/mixin/Mixin_LoadIrisOdsShaderPack.java | 5 ++--- src/main/resources/fabric.mod.json | 4 ++++ 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index b486fbaf..18be4489 100644 --- a/build.gradle +++ b/build.gradle @@ -362,7 +362,9 @@ dependencies { } if (mcVersion >= 11600) { - modCompileOnly 'com.github.IrisShaders:Iris:1.0.0' + modCompileOnly("com.github.IrisShaders:Iris:1.18.x~v1.2.0") { + transitive = false // we do not want to upgrade our libs, we only need this to compile our mixins + } } testImplementation 'junit:junit:4.11' diff --git a/src/main/java/com/replaymod/render/capturer/IrisODSFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/IrisODSFrameCapturer.java index a1d31055..2dd2d269 100644 --- a/src/main/java/com/replaymod/render/capturer/IrisODSFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/IrisODSFrameCapturer.java @@ -28,6 +28,7 @@ public class IrisODSFrameCapturer implements FrameCapturer { public static IrisODSFrameCapturer INSTANCE; private final CubicPboOpenGlFrameCapturer left, right; private final String prevShaderPack; + private final boolean prevShadersEnabled; private int direction; private boolean isLeftEye; @@ -67,13 +68,16 @@ public class IrisODSFrameCapturer implements FrameCapturer { right = new CubicStereoFrameCapturer(worldRenderer, fakeInfo, frameSize); INSTANCE = this; - prevShaderPack = Iris.getIrisConfig().getShaderPackName().orElse(null); - setShaderPack(SHADER_PACK_NAME); + IrisConfig irisConfig = Iris.getIrisConfig(); + prevShaderPack = irisConfig.getShaderPackName().orElse(null); + prevShadersEnabled = irisConfig.areShadersEnabled(); + setShaderPack(SHADER_PACK_NAME, true); } - private static void setShaderPack(String name) { + private static void setShaderPack(String name, boolean enabled) { IrisConfig irisConfig = Iris.getIrisConfig(); irisConfig.setShaderPackName(name); + irisConfig.setShadersEnabled(enabled); try { irisConfig.save(); Iris.reload(); @@ -121,7 +125,7 @@ public class IrisODSFrameCapturer implements FrameCapturer { left.close(); right.close(); INSTANCE = null; - setShaderPack(prevShaderPack); + setShaderPack(prevShaderPack, prevShadersEnabled); } private class CubicStereoFrameCapturer extends CubicPboOpenGlFrameCapturer { diff --git a/src/main/java/com/replaymod/render/mixin/Mixin_LoadIrisOdsShaderPack.java b/src/main/java/com/replaymod/render/mixin/Mixin_LoadIrisOdsShaderPack.java index d8756e7b..56d4564b 100644 --- a/src/main/java/com/replaymod/render/mixin/Mixin_LoadIrisOdsShaderPack.java +++ b/src/main/java/com/replaymod/render/mixin/Mixin_LoadIrisOdsShaderPack.java @@ -4,7 +4,6 @@ package com.replaymod.render.mixin; import com.replaymod.render.capturer.IrisODSFrameCapturer; import net.coderbot.iris.Iris; import net.fabricmc.loader.api.FabricLoader; -import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.injection.At; @@ -15,14 +14,14 @@ import java.nio.file.Path; @Pseudo @Mixin(value = Iris.class, remap = false) public class Mixin_LoadIrisOdsShaderPack { - @Redirect(method = "loadExternalShaderpack", at = @At(value = "FIELD", opcode = Opcodes.GETSTATIC, target = "Lnet/coderbot/iris/Iris;SHADERPACKS_DIRECTORY:Ljava/nio/file/Path;")) + @Redirect(method = "loadExternalShaderpack", at = @At(value = "INVOKE", target = "Lnet/coderbot/iris/Iris;getShaderpacksDirectory()Ljava/nio/file/Path;")) private static Path loadReplayModOdsPack(String name) { if (IrisODSFrameCapturer.INSTANCE != null && IrisODSFrameCapturer.SHADER_PACK_NAME.equals(name)) { return FabricLoader.getInstance().getModContainer("replaymod") .orElseThrow(() -> new RuntimeException("Failed to get mod container for ReplayMod")) .getRootPath(); } else { - return Iris.SHADERPACKS_DIRECTORY; + return Iris.getShaderpacksDirectory(); } } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 6eb336ce..73fe9d6e 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -47,6 +47,10 @@ "fabric-resource-loader-v0": "*" }, + "conflicts": { + "iris": "<1.1.3" + }, + "custom": { "mm:early_risers": [ "com.replaymod.core.ReplayModMMLauncher" From ee6b53b24771b440debb4c501f5ddc2e9ca68b53 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 6 Mar 2022 10:23:53 +0100 Subject: [PATCH 21/28] Fix incorrect FOV and aspect during 360 render on 1.16+ --- .../replaymod/render/mixin/Mixin_Omnidirectional_Camera.java | 2 +- versions/mapping-fabric-1.16.1-1.15.2.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/render/mixin/Mixin_Omnidirectional_Camera.java b/src/main/java/com/replaymod/render/mixin/Mixin_Omnidirectional_Camera.java index fb58ab87..8fa79479 100644 --- a/src/main/java/com/replaymod/render/mixin/Mixin_Omnidirectional_Camera.java +++ b/src/main/java/com/replaymod/render/mixin/Mixin_Omnidirectional_Camera.java @@ -9,7 +9,7 @@ import org.spongepowered.asm.mixin.injection.Redirect; @Mixin(GameRenderer.class) public abstract class Mixin_Omnidirectional_Camera implements EntityRendererHandler.IEntityRenderer { - @Redirect(method = "method_22973", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/math/Matrix4f;viewboxMatrix(DFFF)Lnet/minecraft/util/math/Matrix4f;")) + @Redirect(method = "getBasicProjectionMatrix", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/math/Matrix4f;viewboxMatrix(DFFF)Lnet/minecraft/util/math/Matrix4f;")) private Matrix4f replayModRender_perspective$0(double fovY, float aspect, float zNear, float zFar) { return replayModRender_perspective((float) fovY, aspect, zNear, zFar); } diff --git a/versions/mapping-fabric-1.16.1-1.15.2.txt b/versions/mapping-fabric-1.16.1-1.15.2.txt index 1f8df9e4..25f2e2db 100644 --- a/versions/mapping-fabric-1.16.1-1.15.2.txt +++ b/versions/mapping-fabric-1.16.1-1.15.2.txt @@ -1 +1,2 @@ net.minecraft.client.network.ClientPlayerEntity getUnderwaterVisibility() method_3140() +net.minecraft.client.render.GameRenderer getBasicProjectionMatrix() method_22973() From c146aa145ce2e34b25caf6428f06b24f3a53ec57 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 6 Mar 2022 10:30:29 +0100 Subject: [PATCH 22/28] Fix stereoscopic eye offset on 1.16+ The mapping between intermediary name on 1.15.2 and yarn on 1.16.1 has already been added in the previous commit. --- .../com/replaymod/render/mixin/Mixin_Stereoscopic_Camera.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/replaymod/render/mixin/Mixin_Stereoscopic_Camera.java b/src/main/java/com/replaymod/render/mixin/Mixin_Stereoscopic_Camera.java index 945ac107..885608e3 100644 --- a/src/main/java/com/replaymod/render/mixin/Mixin_Stereoscopic_Camera.java +++ b/src/main/java/com/replaymod/render/mixin/Mixin_Stereoscopic_Camera.java @@ -13,7 +13,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(GameRenderer.class) public abstract class Mixin_Stereoscopic_Camera implements EntityRendererHandler.IEntityRenderer { - @Inject(method = "method_22973", at = @At("RETURN"), cancellable = true) + @Inject(method = "getBasicProjectionMatrix", at = @At("RETURN"), cancellable = true) private void replayModRender_setupStereoscopicProjection(CallbackInfoReturnable ci) { if (replayModRender_getHandler() != null) { Matrix4f offset; From 74049dad775faa31e6f1cef059ac6a3829c38eba Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 6 Mar 2022 11:43:46 +0100 Subject: [PATCH 23/28] Update ReplayStudio 9c5dec8 Fix writing of empty chunk load packet on 1.8.x (fixes #636) 3d8573f Fix writing of respawn packet on 1.8.x 39a85f7 Fix exception when cache file has invalid gzip header (fixes #535) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 18be4489..cceffd06 100644 --- a/build.gradle +++ b/build.gradle @@ -338,7 +338,7 @@ dependencies { shadow 'com.github.ReplayMod.JavaBlend:2.79.0:a0696f8' - shadow "com.github.ReplayMod:ReplayStudio:70f59ef", shadeExclusions + shadow "com.github.ReplayMod:ReplayStudio:9c5dec8", shadeExclusions implementation(FABRIC ? dependencies.project(path: jGui.path, configuration: "namedElements") : jGui) { transitive = false // FG 1.2 puts all MC deps into the compile configuration and we don't want to shade those From 1fa027f36248debb0c4af692c52b11e568024d4b Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 6 Mar 2022 11:46:01 +0100 Subject: [PATCH 24/28] Enable Quick Mode for 1.8.x --- build.gradle | 2 +- docs/content.md | 4 +--- .../replaymod/replay/QuickReplaySender.java | 6 +++++- .../com/replaymod/replay/ReplayHandler.java | 18 +++++++++--------- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index cceffd06..99f3dd15 100644 --- a/build.gradle +++ b/build.gradle @@ -338,7 +338,7 @@ dependencies { shadow 'com.github.ReplayMod.JavaBlend:2.79.0:a0696f8' - shadow "com.github.ReplayMod:ReplayStudio:9c5dec8", shadeExclusions + shadow "com.github.ReplayMod:ReplayStudio:6d081f6", shadeExclusions implementation(FABRIC ? dependencies.project(path: jGui.path, configuration: "namedElements") : jGui) { transitive = false // FG 1.2 puts all MC deps into the compile configuration and we don't want to shade those diff --git a/docs/content.md b/docs/content.md index faea3ff2..0681006f 100755 --- a/docs/content.md +++ b/docs/content.md @@ -465,7 +465,7 @@ If you have a Replay in a dark setting (for example at nighttime, or in a cave) This works as a replacement for the **Night Vision Potion Effect**, without the side effect of a weird sky color. -## Quick Mode [quickmode] (Minecraft 1.9 and up) +## Quick Mode [quickmode] ![](img/quickmode-icon.jpg) In **Quick Mode**, this clock symbol is displayed in the lower right corner of the screen. @@ -473,8 +473,6 @@ When you first enable **Quick Mode** in a replay, an internal reference of certa As a side effect, certain features like particles and second skin layers will not be rendered in the preview. By default, **Quick Mode** is toggled with `Q`. -**Quick Mode** is available in ReplayMod for Minecraft 1.9.4 and up. - ## Player Overview [overview] ![](img/player-overview.jpg) The **Player Overview** Screen diff --git a/src/main/java/com/replaymod/replay/QuickReplaySender.java b/src/main/java/com/replaymod/replay/QuickReplaySender.java index 0b613f4a..6592b47d 100644 --- a/src/main/java/com/replaymod/replay/QuickReplaySender.java +++ b/src/main/java/com/replaymod/replay/QuickReplaySender.java @@ -1,4 +1,4 @@ -//#if MC>=10904 +//#if MC>=10800 package com.replaymod.replay; import com.google.common.util.concurrent.FutureCallback; @@ -78,7 +78,11 @@ public class QuickReplaySender extends ChannelHandlerAdapter implements ReplaySe wrappedBuf.writerIndex(size); PacketByteBuf packetByteBuf = new PacketByteBuf(wrappedBuf); + //#if MC>=10809 Packet mcPacket; + //#else + //$$ Packet mcPacket; + //#endif //#if MC>=11700 //$$ mcPacket = NetworkState.PLAY.getPacketHandler(NetworkSide.CLIENTBOUND, packet.getId(), packetByteBuf); //#elseif MC>=11500 diff --git a/src/main/java/com/replaymod/replay/ReplayHandler.java b/src/main/java/com/replaymod/replay/ReplayHandler.java index 75370265..99e90c36 100644 --- a/src/main/java/com/replaymod/replay/ReplayHandler.java +++ b/src/main/java/com/replaymod/replay/ReplayHandler.java @@ -69,7 +69,7 @@ import net.minecraft.entity.LivingEntity; //$$ import io.netty.channel.ChannelOutboundHandlerAdapter; //#endif -//#if MC<10904 +//#if MC<10800 //$$ import de.johni0702.minecraft.gui.element.GuiLabel; //$$ import de.johni0702.minecraft.gui.popup.GuiInfoPopup; //$$ import de.johni0702.minecraft.gui.utils.Colors; @@ -119,11 +119,11 @@ public class ReplayHandler { * Decodes and sends packets into channel. */ private final FullReplaySender fullReplaySender; - //#if MC>=10904 + //#if MC>=10800 private final QuickReplaySender quickReplaySender; private boolean quickMode = false; //#else - //$$ private static final String QUICK_MODE_MIN_MC = "1.9.4"; + //$$ private static final String QUICK_MODE_MIN_MC = "1.8"; //#endif /** @@ -160,7 +160,7 @@ public class ReplayHandler { markers = replayFile.getMarkers().or(Collections.emptySet()); fullReplaySender = new FullReplaySender(this, replayFile, false); - //#if MC>=10904 + //#if MC>=10800 quickReplaySender = new QuickReplaySender(ReplayModReplay.instance, replayFile); //#endif @@ -206,7 +206,7 @@ public class ReplayHandler { ReplayClosingCallback.EVENT.invoker().replayClosing(this); fullReplaySender.terminateReplay(); - //#if MC>=10904 + //#if MC>=10800 if (quickMode) { quickReplaySender.unregister(); } @@ -308,7 +308,7 @@ public class ReplayHandler { //$$ channel = new EmbeddedChannel(dummyHandler); //$$ channel.pipeline().remove(dummyHandler); //#endif - //#if MC>=10904 + //#if MC>=10800 channel.pipeline().addLast("ReplayModReplay_quickReplaySender", quickReplaySender); //#endif channel.pipeline().addLast("ReplayModReplay_replaySender", fullReplaySender); @@ -329,7 +329,7 @@ public class ReplayHandler { } public ReplaySender getReplaySender() { - //#if MC>=10904 + //#if MC>=10800 return quickMode ? quickReplaySender : fullReplaySender; //#else //$$ return fullReplaySender; @@ -340,7 +340,7 @@ public class ReplayHandler { return overlay; } - //#if MC>=10904 + //#if MC>=10800 public void ensureQuickModeInitialized(Runnable andThen) { if (Utils.ifMinimalModeDoPopup(overlay, () -> {})) return; ListenableFuture future = quickReplaySender.getInitializationPromise(); @@ -538,7 +538,7 @@ public class ReplayHandler { return; // path playback, rendering, etc. -> no jumping allowed } - //#if MC>=10904 + //#if MC>=10800 if (getReplaySender() == quickReplaySender) { // Always round to full tick targetTime = targetTime + targetTime % 50; From 4aa667fe044bf7c8b451f4eefa9032d81d720eee Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 6 Mar 2022 12:33:13 +0100 Subject: [PATCH 25/28] Port to MC 1.18.2 (closes #697) --- build.gradle | 9 +++++++-- jGui | 2 +- root.gradle.kts | 2 ++ settings.gradle.kts | 2 ++ .../replaymod/recording/mixin/MixinMouseHelper.java | 4 +++- .../replaymod/recording/mixin/MixinWorldClient.java | 11 ++++++++++- .../render/mixin/Mixin_ChromaKeyColorSky.java | 7 ++++++- .../java/com/replaymod/replay/FullReplaySender.java | 4 ++++ .../java/com/replaymod/replay/InputReplayTimer.java | 13 +++++++++++++ .../com/replaymod/replay/camera/CameraEntity.java | 12 +++++++++++- .../replaymod/replay/mixin/MixinGuiSpectator.java | 2 +- versions/1.18.2/.gitkeep | 0 12 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 versions/1.18.2/.gitkeep diff --git a/build.gradle b/build.gradle index 99f3dd15..e9ba988b 100644 --- a/build.gradle +++ b/build.gradle @@ -245,6 +245,7 @@ dependencies { 11701: '1.17.1', 11800: '1.18', 11801: '1.18.1', + 11802: '1.18.2', ][mcVersion] mappings 'net.fabricmc:yarn:' + [ 11404: '1.14.4+build.16', @@ -256,6 +257,7 @@ dependencies { 11701: '1.17.1+build.29:v2', 11800: '1.18+build.1:v2', 11801: '1.18.1+build.1:v2', + 11802: '1.18.2+build.1:v2', ][mcVersion] modImplementation 'net.fabricmc:fabric-loader:0.12.5' def fabricApiVersion = [ @@ -268,6 +270,7 @@ dependencies { 11701: '0.37.1+1.17', 11800: '0.43.1+1.18', 11801: '0.43.1+1.18', + 11802: '0.47.9+1.18.2', ][mcVersion] def fabricApiModules = [ "api-base", @@ -338,7 +341,7 @@ dependencies { shadow 'com.github.ReplayMod.JavaBlend:2.79.0:a0696f8' - shadow "com.github.ReplayMod:ReplayStudio:6d081f6", shadeExclusions + shadow "com.github.ReplayMod:ReplayStudio:b5539d1", shadeExclusions implementation(FABRIC ? dependencies.project(path: jGui.path, configuration: "namedElements") : jGui) { transitive = false // FG 1.2 puts all MC deps into the compile configuration and we don't want to shade those @@ -346,7 +349,9 @@ dependencies { shadow 'com.github.ReplayMod:lwjgl-utils:27dcd66' if (FABRIC) { - if (mcVersion >= 11800) { + if (mcVersion >= 11802) { + modImplementation 'com.terraformersmc:modmenu:3.1.0' + } else if (mcVersion >= 11800) { modImplementation 'com.terraformersmc:modmenu:3.0.0' } else if (mcVersion >= 11700) { modImplementation 'com.terraformersmc:modmenu:2.0.0-beta.7' diff --git a/jGui b/jGui index c79b62a7..5e41452b 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit c79b62a73e649fd6d16a1ffbd9e320555834bc46 +Subproject commit 5e41452b0e17700691efd38b8ce793b5200145c8 diff --git a/root.gradle.kts b/root.gradle.kts index f94b12fc..8ee13ad4 100755 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -189,6 +189,7 @@ val doRelease by tasks.registering { defaultTasks("bundleJar") preprocess { + val mc11802 = createNode("1.18.2", 11802, "yarn") val mc11801 = createNode("1.18.1", 11801, "yarn") val mc11701 = createNode("1.17.1", 11701, "yarn") val mc11700 = createNode("1.17", 11700, "yarn") @@ -208,6 +209,7 @@ preprocess { val mc10800 = createNode("1.8", 10800, "srg") val mc10710 = createNode("1.7.10", 10710, "srg") + mc11802.link(mc11801) mc11801.link(mc11701, file("versions/mapping-fabric-1.18.1-1.17.1.txt")) mc11701.link(mc11700) mc11700.link(mc11604, file("versions/mapping-fabric-1.17-1.16.4.txt")) diff --git a/settings.gradle.kts b/settings.gradle.kts index 2030c3cd..a77be868 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,6 +32,7 @@ val jGuiVersions = listOf( "1.17", "1.17.1", "1.18.1", + "1.18.2", ) val replayModVersions = listOf( // "1.7.10", @@ -52,6 +53,7 @@ val replayModVersions = listOf( "1.17", "1.17.1", "1.18.1", + "1.18.2", ) rootProject.buildFileName = "root.gradle.kts" diff --git a/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java b/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java index 01e9572f..c7e8f5d4 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinMouseHelper.java @@ -33,7 +33,9 @@ public abstract class MixinMouseHelper { long _p0, double _p1, double _p2, CallbackInfo ci, double _l1, - //#if MC>=11400 + //#if MC>=11802 + //$$ int yOffsetAccumulated + //#elseif MC>=11400 float yOffsetAccumulated //#else //$$ double yOffsetAccumulated diff --git a/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java b/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java index e5c79889..55bc21b2 100644 --- a/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java +++ b/src/main/java/com/replaymod/recording/mixin/MixinWorldClient.java @@ -16,6 +16,10 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +//#if MC>=11802 +//$$ import net.minecraft.util.registry.RegistryEntry; +//#endif + //#if MC>=11600 import net.minecraft.util.registry.RegistryKey; import net.minecraft.world.MutableWorldProperties; @@ -52,7 +56,12 @@ public abstract class MixinWorldClient extends World implements RecordingEventHa //#if MC<11602 //$$ RegistryKey registryKey2, //#endif - DimensionType dimensionType, Supplier profiler, boolean bl, boolean bl2, long l) { + //#if MC>=11802 + //$$ RegistryEntry dimensionType, + //#else + DimensionType dimensionType, + //#endif + Supplier profiler, boolean bl, boolean bl2, long l) { super(mutableWorldProperties, registryKey, //#if MC<11602 //$$ registryKey2, diff --git a/src/main/java/com/replaymod/render/mixin/Mixin_ChromaKeyColorSky.java b/src/main/java/com/replaymod/render/mixin/Mixin_ChromaKeyColorSky.java index bb60ab4e..28872496 100644 --- a/src/main/java/com/replaymod/render/mixin/Mixin_ChromaKeyColorSky.java +++ b/src/main/java/com/replaymod/render/mixin/Mixin_ChromaKeyColorSky.java @@ -21,7 +21,12 @@ public abstract class Mixin_ChromaKeyColorSky { @Shadow @Final private MinecraftClient client; //#if MC>=11800 - //$$ @Inject(method = "renderSky(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/util/math/Matrix4f;FLjava/lang/Runnable;)V", + //$$ @Inject( + //#if MC>=11802 + //$$ method = "renderSky(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/util/math/Matrix4f;FLnet/minecraft/client/render/Camera;ZLjava/lang/Runnable;)V", + //#else + //$$ method = "renderSky(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/util/math/Matrix4f;FLjava/lang/Runnable;)V", + //#endif //$$ at = @At(value = "INVOKE", target = "Ljava/lang/Runnable;run()V", remap = false, shift = At.Shift.AFTER), //$$ cancellable = true) //#elseif MC>=11400 || 10710>=MC diff --git a/src/main/java/com/replaymod/replay/FullReplaySender.java b/src/main/java/com/replaymod/replay/FullReplaySender.java index 2aee5e57..b44febe9 100644 --- a/src/main/java/com/replaymod/replay/FullReplaySender.java +++ b/src/main/java/com/replaymod/replay/FullReplaySender.java @@ -648,7 +648,11 @@ public class FullReplaySender extends ChannelDuplexHandler implements ReplaySend //#if MC>=11600 //#if MC>=11603 packet.getDimensionIds(), + //#if MC>=11800 + //$$ packet.registryManager(), + //#else (net.minecraft.util.registry.DynamicRegistryManager.Impl) packet.getRegistryManager(), + //#endif packet.getDimensionType(), //#else //$$ packet.method_29443(), diff --git a/src/main/java/com/replaymod/replay/InputReplayTimer.java b/src/main/java/com/replaymod/replay/InputReplayTimer.java index 394fdd50..0b3e8b4a 100644 --- a/src/main/java/com/replaymod/replay/InputReplayTimer.java +++ b/src/main/java/com/replaymod/replay/InputReplayTimer.java @@ -8,6 +8,10 @@ import com.replaymod.replay.camera.CameraEntity; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.RenderTickCounter; +//#if MC>=11802 +//$$ import net.minecraft.client.gui.screen.DownloadingTerrainScreen; +//#endif + //#if MC>=11400 import org.lwjgl.glfw.GLFW; //#else @@ -111,6 +115,15 @@ public class InputReplayTimer extends WrappedTimer { //#endif //$$ } //#endif + + //#if MC>=11802 + //$$ // As of 1.18.2, this screen always stays open for at least two seconds, and requires ticking to close. + //$$ // Thanks, but we'll have none of that (at least while in a replay). + //$$ if (mc.currentScreen instanceof DownloadingTerrainScreen) { + //$$ mc.currentScreen.close(); + //$$ } + //#endif + } //#if MC>=11600 return ticksThisFrame; diff --git a/src/main/java/com/replaymod/replay/camera/CameraEntity.java b/src/main/java/com/replaymod/replay/camera/CameraEntity.java index 5ae3fcec..35bd016c 100644 --- a/src/main/java/com/replaymod/replay/camera/CameraEntity.java +++ b/src/main/java/com/replaymod/replay/camera/CameraEntity.java @@ -31,6 +31,10 @@ import net.minecraft.stat.StatHandler; import net.minecraft.util.Identifier; import net.minecraft.util.math.Box; +//#if MC>=11802 +//$$ import net.minecraft.tag.TagKey; +//#endif + //#if FABRIC>=1 //#else //$$ import net.minecraftforge.client.event.EntityViewRenderEvent; @@ -354,7 +358,13 @@ public class CameraEntity //#if MC>=11400 @Override - public boolean isSubmergedIn(Tag fluid) { + public boolean isSubmergedIn( + //#if MC>=11802 + //$$ TagKey fluid + //#else + Tag fluid + //#endif + ) { return falseUnlessSpectating(entity -> entity.isSubmergedIn(fluid)); } diff --git a/src/main/java/com/replaymod/replay/mixin/MixinGuiSpectator.java b/src/main/java/com/replaymod/replay/mixin/MixinGuiSpectator.java index 9d938f7e..2257eceb 100644 --- a/src/main/java/com/replaymod/replay/mixin/MixinGuiSpectator.java +++ b/src/main/java/com/replaymod/replay/mixin/MixinGuiSpectator.java @@ -18,7 +18,7 @@ public abstract class MixinGuiSpectator { //$$ @Inject(method = "func_175260_a", at = @At("HEAD"), cancellable = true) //#endif public void isInReplay( - //#if MC>=11400 + //#if MC>=11400 && MC<11802 double i, //#else //$$ int i, diff --git a/versions/1.18.2/.gitkeep b/versions/1.18.2/.gitkeep new file mode 100644 index 00000000..e69de29b From 9df2035ca9ad82cfc1c0127a0d1aa190306e130b Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 6 Mar 2022 12:38:38 +0100 Subject: [PATCH 26/28] Update translations --- src/main/resources/assets/replaymod/lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index 188e85c4..7614b69e 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit 188e85c4dbc9296c81231c37d7c18bb92560b2e7 +Subproject commit 7614b69e249eb728d219c02c3c71effcc5fc00a2 From f2425a693a1c557725b27c276228c7cd7f3d3be1 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 6 Mar 2022 14:07:03 +0100 Subject: [PATCH 27/28] DRY (Iris)ODSFrameCapturer --- .../render/capturer/IrisODSFrameCapturer.java | 26 +------------------ .../render/capturer/ODSFrameCapturer.java | 26 +------------------ 2 files changed, 2 insertions(+), 50 deletions(-) diff --git a/src/main/java/com/replaymod/render/capturer/IrisODSFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/IrisODSFrameCapturer.java index 2dd2d269..0f581d99 100644 --- a/src/main/java/com/replaymod/render/capturer/IrisODSFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/IrisODSFrameCapturer.java @@ -1,7 +1,6 @@ //#if MC>=11600 package com.replaymod.render.capturer; -import com.mojang.blaze3d.platform.GlStateManager; import com.replaymod.render.RenderSettings; import com.replaymod.render.frame.CubicOpenGlFrame; import com.replaymod.render.frame.ODSOpenGlFrame; @@ -16,12 +15,6 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -import static com.replaymod.core.versions.MCVer.popMatrix; -import static com.replaymod.core.versions.MCVer.pushMatrix; -import static com.replaymod.core.versions.MCVer.resizeMainWindow; -import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; -import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; - public class IrisODSFrameCapturer implements FrameCapturer { public static final String SHADER_PACK_NAME = "assets/replaymod/iris/ods"; @@ -135,25 +128,8 @@ public class IrisODSFrameCapturer implements FrameCapturer { @Override protected OpenGlFrame renderFrame(int frameId, float partialTicks, CubicOpenGlFrameCapturer.Data captureData) { - resizeMainWindow(mc, getFrameWidth(), getFrameHeight()); - - pushMatrix(); - frameBuffer().beginWrite(true); - - GlStateManager.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT - //#if MC>=11400 - , false - //#endif - ); - GlStateManager.enableTexture(); - direction = captureData.ordinal(); - worldRenderer.renderWorld(partialTicks, null); - - frameBuffer().endWrite(); - popMatrix(); - - return captureFrame(frameId, captureData); + return super.renderFrame(frameId, partialTicks, null); } } } diff --git a/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java index 4df2fa18..c0ec8c3d 100644 --- a/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java @@ -1,6 +1,5 @@ package com.replaymod.render.capturer; -import com.mojang.blaze3d.platform.GlStateManager; import com.replaymod.render.rendering.Channel; import de.johni0702.minecraft.gui.utils.EventRegistrations; import com.replaymod.render.RenderSettings; @@ -20,12 +19,6 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -import static com.replaymod.core.versions.MCVer.popMatrix; -import static com.replaymod.core.versions.MCVer.pushMatrix; -import static com.replaymod.core.versions.MCVer.resizeMainWindow; -import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; -import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; - public class ODSFrameCapturer implements FrameCapturer { private static final Identifier vertexResource = new Identifier("replaymod", "shader/ods.vert"); private static final Identifier fragmentResource = new Identifier("replaymod", "shader/ods.frag"); @@ -166,25 +159,8 @@ public class ODSFrameCapturer implements FrameCapturer { @Override protected OpenGlFrame renderFrame(int frameId, float partialTicks, CubicOpenGlFrameCapturer.Data captureData) { - resizeMainWindow(mc, getFrameWidth(), getFrameHeight()); - - pushMatrix(); - frameBuffer().beginWrite(true); - - GlStateManager.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT - //#if MC>=11400 - , false - //#endif - ); - GlStateManager.enableTexture(); - directionVariable.set(captureData.ordinal()); - worldRenderer.renderWorld(partialTicks, null); - - frameBuffer().endWrite(); - popMatrix(); - - return captureFrame(frameId, captureData); + return super.renderFrame(frameId, partialTicks, null); } } } From c010a437fcca087c9ff828504915482d778e7d8b Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 6 Mar 2022 14:30:24 +0100 Subject: [PATCH 28/28] Orient camera for ODS in game instead of in vertex shader This makes it work more like regular 360 mode and is generally more compatible because it doesn't require the frustum culling workarounds which the GPU solution needs. In particular, this fixes frustum culling with newer sodium versions (which no longer use the vanilla intersection checking method) and fixes the clouds on modern Iris (which seems to be using a different shader program for clouds). --- .../render/capturer/IrisODSFrameCapturer.java | 2 +- .../render/capturer/ODSFrameCapturer.java | 2 +- ...Omnidirectional_DisableFrustumCulling.java | 37 ----------- .../ods/shaders/gbuffers_textured_lit.vsh | 65 ++++++++++++------- .../assets/replaymod/shader/ods.vert | 65 ++++++++++++------- .../resources/mixins.render.replaymod.json | 1 - 6 files changed, 84 insertions(+), 88 deletions(-) delete mode 100644 src/main/java/com/replaymod/render/mixin/Mixin_Omnidirectional_DisableFrustumCulling.java diff --git a/src/main/java/com/replaymod/render/capturer/IrisODSFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/IrisODSFrameCapturer.java index 0f581d99..6671d80a 100644 --- a/src/main/java/com/replaymod/render/capturer/IrisODSFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/IrisODSFrameCapturer.java @@ -129,7 +129,7 @@ public class IrisODSFrameCapturer implements FrameCapturer { @Override protected OpenGlFrame renderFrame(int frameId, float partialTicks, CubicOpenGlFrameCapturer.Data captureData) { direction = captureData.ordinal(); - return super.renderFrame(frameId, partialTicks, null); + return super.renderFrame(frameId, partialTicks, captureData); } } } diff --git a/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java b/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java index c0ec8c3d..925257fe 100644 --- a/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java +++ b/src/main/java/com/replaymod/render/capturer/ODSFrameCapturer.java @@ -160,7 +160,7 @@ public class ODSFrameCapturer implements FrameCapturer { @Override protected OpenGlFrame renderFrame(int frameId, float partialTicks, CubicOpenGlFrameCapturer.Data captureData) { directionVariable.set(captureData.ordinal()); - return super.renderFrame(frameId, partialTicks, null); + return super.renderFrame(frameId, partialTicks, captureData); } } } diff --git a/src/main/java/com/replaymod/render/mixin/Mixin_Omnidirectional_DisableFrustumCulling.java b/src/main/java/com/replaymod/render/mixin/Mixin_Omnidirectional_DisableFrustumCulling.java deleted file mode 100644 index f174e0a3..00000000 --- a/src/main/java/com/replaymod/render/mixin/Mixin_Omnidirectional_DisableFrustumCulling.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.replaymod.render.mixin; - -import com.replaymod.core.versions.MCVer; -import com.replaymod.render.hooks.EntityRendererHandler; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -//#if MC>=10800 -import net.minecraft.client.render.Frustum; -//#else -//$$ import net.minecraft.client.renderer.culling.Frustrum; -//#endif - -//#if MC>=10800 -@Mixin(Frustum.class) -//#else -//$$ @Mixin(Frustrum.class) -//#endif -public abstract class Mixin_Omnidirectional_DisableFrustumCulling { - //#if MC>=11500 - @Inject(method = "isAnyCornerVisible", at = @At("HEAD"), cancellable = true) - //#else - //$$ @Inject(method = "intersects", at = @At("HEAD"), cancellable = true) - //#endif - public void intersects(CallbackInfoReturnable ci) { - EntityRendererHandler handler = ((EntityRendererHandler.IEntityRenderer) MCVer.getMinecraft().gameRenderer).replayModRender_getHandler(); - if (handler != null && handler.omnidirectional) { - // Note the following used to be true but for simplicity non-ODS omnidirectional is the same now too. - // Normally the camera is always facing the direction of the omnidirectional image face that is currently - // getting rendered. With ODS however, the camera is always facing forwards and the turning happens in the - // vertex shader (non-trivial due to stereo). As such, all chunks need to be rendered all the time for ODS. - ci.setReturnValue(true); - } - } -} diff --git a/src/main/resources/assets/replaymod/iris/ods/shaders/gbuffers_textured_lit.vsh b/src/main/resources/assets/replaymod/iris/ods/shaders/gbuffers_textured_lit.vsh index 3b53c589..68e7e0fb 100644 --- a/src/main/resources/assets/replaymod/iris/ods/shaders/gbuffers_textured_lit.vsh +++ b/src/main/resources/assets/replaymod/iris/ods/shaders/gbuffers_textured_lit.vsh @@ -9,10 +9,49 @@ uniform int direction; const float eyeDistance = 0.14; +void orient(vec4 position, int orientation) { + float z; + if (orientation == 0) { // LEFT + z = position.z; + position.z = position.x; + position.x = -z; + } else if (orientation == 1) { // RIGHT + z = position.z; + position.z = -position.x; + position.x = z; + } else if (orientation == 2) { // FRONT + // No changes required + } else if (orientation == 3) { // BACK + position.x = -position.x; + position.z = -position.z; + } else if (orientation == 4) { // TOP + z = position.z; + position.z = -position.y; + position.y = z; + } else if (orientation == 5) { // BOTTOM + z = position.z; + position.z = position.y; + position.y = -z; + } +} + +void orientInverse(vec4 position, int orientation) { + if (orientation < 2) { + orient(position, 1 - orientation); // LEFT and RIGHT flip + } else if (orientation < 4) { + orient(position, orientation); // FRONT and BACK are their own inverses + } else { + orient(position, (1 - (orientation - 4)) + 4); // TOP and BOTTOM flip + } +} + void main() { // Transform to view space vec4 position = gl_ModelViewMatrix * gl_Vertex; + // Undo the camera rotation, so we always apply our stereo effect looking in the same direction + orientInverse(position, direction); + // Distort for ODS // O := The origin // P := The current vertex/point @@ -34,30 +73,8 @@ void main() { // Calculate the vector between O and T and finally move the vertex by that vector position -= vec4(distTO * sin(angOT), 0, distTO * cos(angOT), 0); - // Rotate for different cubic views - float z; - if (direction == 0) { // LEFT - z = position.z; - position.z = position.x; - position.x = -z; - } else if (direction == 1) { // RIGHT - z = position.z; - position.z = -position.x; - position.x = z; - } else if (direction == 2) { // FRONT - // No changes required - } else if (direction == 3) { // BACK - position.x = -position.x; - position.z = -position.z; - } else if (direction == 4) { // TOP - z = position.z; - position.z = -position.y; - position.y = z; - } else if (direction == 5) { // BOTTOM - z = position.z; - position.z = position.y; - position.y = -z; - } + // Rotate back into the correct cubic view + orient(position, direction); // Transform to screen space gl_Position = gl_ProjectionMatrix * position; diff --git a/src/main/resources/assets/replaymod/shader/ods.vert b/src/main/resources/assets/replaymod/shader/ods.vert index 43160450..6a654a6b 100644 --- a/src/main/resources/assets/replaymod/shader/ods.vert +++ b/src/main/resources/assets/replaymod/shader/ods.vert @@ -14,10 +14,49 @@ uniform int direction; const float eyeDistance = 0.14; +void orient(vec4 position, int orientation) { + float z; + if (orientation == 0) { // LEFT + z = position.z; + position.z = position.x; + position.x = -z; + } else if (orientation == 1) { // RIGHT + z = position.z; + position.z = -position.x; + position.x = z; + } else if (orientation == 2) { // FRONT + // No changes required + } else if (orientation == 3) { // BACK + position.x = -position.x; + position.z = -position.z; + } else if (orientation == 4) { // TOP + z = position.z; + position.z = -position.y; + position.y = z; + } else if (orientation == 5) { // BOTTOM + z = position.z; + position.z = position.y; + position.y = -z; + } +} + +void orientInverse(vec4 position, int orientation) { + if (orientation < 2) { + orient(position, 1 - orientation); // LEFT and RIGHT flip + } else if (orientation < 4) { + orient(position, orientation); // FRONT and BACK are their own inverses + } else { + orient(position, (1 - (orientation - 4)) + 4); // TOP and BOTTOM flip + } +} + void main() { // Transform to view space vec4 position = gl_ModelViewMatrix * gl_Vertex; + // Undo the camera rotation, so we always apply our stereo effect looking in the same direction + orientInverse(position, direction); + // Distort for ODS // O := The origin // P := The current vertex/point @@ -39,30 +78,8 @@ void main() { // Calculate the vector between O and T and finally move the vertex by that vector position -= vec4(distTO * sin(angOT), 0, distTO * cos(angOT), 0); - // Rotate for different cubic views - float z; - if (direction == 0) { // LEFT - z = position.z; - position.z = position.x; - position.x = -z; - } else if (direction == 1) { // RIGHT - z = position.z; - position.z = -position.x; - position.x = z; - } else if (direction == 2) { // FRONT - // No changes required - } else if (direction == 3) { // BACK - position.x = -position.x; - position.z = -position.z; - } else if (direction == 4) { // TOP - z = position.z; - position.z = -position.y; - position.y = z; - } else if (direction == 5) { // BOTTOM - z = position.z; - position.z = position.y; - position.y = -z; - } + // Rotate back into the correct cubic view + orient(position, direction); // Transform to screen space gl_Position = gl_ProjectionMatrix * position; diff --git a/src/main/resources/mixins.render.replaymod.json b/src/main/resources/mixins.render.replaymod.json index ba783acd..092aa958 100644 --- a/src/main/resources/mixins.render.replaymod.json +++ b/src/main/resources/mixins.render.replaymod.json @@ -14,7 +14,6 @@ "Mixin_HideNameTags", "Mixin_HideNameTags_LivingEntity", "Mixin_Omnidirectional_Camera", - "Mixin_Omnidirectional_DisableFrustumCulling", "Mixin_Omnidirectional_Rotation", "Mixin_PreserveDepthDuringGuiRendering", "Mixin_SkipBlockOutlinesDuringRender",