diff --git a/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java b/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java index 57896bf9..a495399e 100644 --- a/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java +++ b/src/main/java/com/replaymod/pathing/player/AbstractTimelinePlayer.java @@ -30,6 +30,7 @@ public abstract class AbstractTimelinePlayer extends EventRegistrations { private final ReplayHandler replayHandler; private Timeline timeline; protected long startOffset; + private boolean wasAsyncMode; private long lastTime; private long lastTimestamp; private ListenableFuture future; @@ -69,6 +70,7 @@ public abstract class AbstractTimelinePlayer extends EventRegistrations { }.max(iter).getTime(); } + wasAsyncMode = replayHandler.getReplaySender().isAsyncMode(); replayHandler.getReplaySender().setSyncModeAndWait(); register(); lastTime = 0; @@ -103,7 +105,9 @@ public abstract class AbstractTimelinePlayer extends EventRegistrations { MinecraftAccessor mcA = (MinecraftAccessor) mc; mcA.setTimer(((ReplayTimer) mcA.getTimer()).getWrapped()); replayHandler.getReplaySender().setReplaySpeed(0); - replayHandler.getReplaySender().setAsyncMode(true); + if (wasAsyncMode) { + replayHandler.getReplaySender().setAsyncMode(true); + } unregister(); return; } diff --git a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java index afa997b6..642a1f6e 100644 --- a/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java +++ b/src/main/java/com/replaymod/render/gui/GuiRenderQueue.java @@ -10,7 +10,12 @@ import com.replaymod.render.VideoWriter; import com.replaymod.render.rendering.VideoRenderer; import com.replaymod.render.utils.RenderJob; import com.replaymod.replay.ReplayHandler; +import com.replaymod.replay.ReplayModReplay; +import com.replaymod.replay.ReplaySender; import com.replaymod.replaystudio.pathing.path.Timeline; +import com.replaymod.replaystudio.replay.ZipReplayFile; +import com.replaymod.replaystudio.studio.ReplayStudio; +import com.replaymod.replaystudio.us.myles.ViaVersion.api.Pair; import de.johni0702.minecraft.gui.GuiRenderer; import de.johni0702.minecraft.gui.RenderInfo; import de.johni0702.minecraft.gui.container.AbstractGuiClickableContainer; @@ -30,11 +35,16 @@ import de.johni0702.minecraft.gui.utils.Colors; 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 java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.function.Supplier; @@ -141,15 +151,17 @@ public class GuiRenderQueue extends AbstractGuiPopup implements for (Entry entry : selectedEntries) { renderQueue.add(entry.job); } - ReplayMod.instance.runLaterWithoutLock(() -> processQueue(renderQueue)); + ReplayMod.instance.runLaterWithoutLock(() -> processQueue(container, replayHandler, renderQueue, () -> {})); }); updateButtons(); } - private void processQueue(Iterable queue) { + private static void processQueue(AbstractGuiScreen container, ReplayHandler replayHandler, Iterable queue, Runnable done) { + MinecraftClient mc = MCVer.getMinecraft(); + // Close all GUIs (so settings in GuiRenderSettings are saved) - getMinecraft().openScreen(null); + mc.openScreen(null); // Start rendering int jobsDone = 0; for (RenderJob renderJob : queue) { @@ -169,7 +181,7 @@ public class GuiRenderQueue extends AbstractGuiPopup implements //$$ I18n.format("replaymod.gui.rendering.error.message") //#endif ); - getMinecraft().openScreen(errorScreen); + mc.openScreen(errorScreen); return; } catch (VideoWriter.FFmpegStartupException e) { int jobsToSkip = jobsDone; @@ -177,16 +189,74 @@ public class GuiRenderQueue extends AbstractGuiPopup implements // Update current job with fixed ffmpeg arguments renderJob.setSettings(newSettings); // Restart queue, skipping the already completed jobs - ReplayMod.instance.runLaterWithoutLock(() -> processQueue(Iterables.skip(queue, jobsToSkip))); + ReplayMod.instance.runLaterWithoutLock(() -> processQueue(container, replayHandler, Iterables.skip(queue, jobsToSkip), done)); }); return; } catch (Throwable t) { - Utils.error(LOGGER, this, CrashReport.create(t, "Rendering video"), () -> {}); + Utils.error(LOGGER, container, CrashReport.create(t, "Rendering video"), () -> {}); container.display(); // Re-show the queue popup and the new error popup return; } jobsDone++; } + done.run(); + } + + public static void processMultipleReplays( + AbstractGuiScreen container, + ReplayModReplay mod, + Iterator>> queue, + Runnable done + ) { + if (!queue.hasNext()) { + done.run(); + return; + } + Pair> next = queue.next(); + + LOGGER.info("Opening replay {} for {} render jobs", next.getKey(), next.getValue().size()); + ReplayHandler replayHandler; + ZipReplayFile replayFile = null; + try { + replayFile = new ZipReplayFile(new ReplayStudio(), next.getKey()); + replayHandler = mod.startReplay(replayFile, true, 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 + IOUtils.closeQuietly(replayFile); + return; + } + if (replayHandler == null) { + LOGGER.warn("Replay failed to open (missing mods?), skipping.."); + IOUtils.closeQuietly(replayFile); + processMultipleReplays(container, mod, queue, done); + return; + } + ReplaySender replaySender = replayHandler.getReplaySender(); + + MinecraftClient mc = mod.getCore().getMinecraft(); + int jumpTo = 1000; + while (mc.world == null && jumpTo < replayHandler.getReplayDuration()) { + replaySender.sendPacketsTill(jumpTo); + jumpTo += 1000; + } + if (mc.world == null) { + LOGGER.warn("Replay failed to load world (corrupted?), skipping.."); + IOUtils.closeQuietly(replayFile); + processMultipleReplays(container, mod, queue, done); + return; + } + + processQueue(container, replayHandler, next.getValue(), () -> { + try { + replayHandler.endReplay(); + } catch (IOException e) { + Utils.error(LOGGER, container, CrashReport.create(e, "Closing replay"), () -> {}); + container.display(); // Re-show the queue popup and the new error popup + return; + } + processMultipleReplays(container, mod, queue, done); + }); } private GuiRenderSettings addButtonClicked() { 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 f45ada93..e65466f2 100644 --- a/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java +++ b/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java @@ -4,7 +4,9 @@ import com.google.common.base.Supplier; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.SettableFuture; +import com.replaymod.render.gui.GuiRenderQueue; import com.replaymod.render.utils.RenderJob; +import com.replaymod.replaystudio.us.myles.ViaVersion.api.Pair; import de.johni0702.minecraft.gui.GuiRenderer; import de.johni0702.minecraft.gui.RenderInfo; import de.johni0702.minecraft.gui.versions.Image; @@ -57,9 +59,12 @@ import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import static com.replaymod.replay.ReplayModReplay.LOGGER; import static de.johni0702.minecraft.gui.versions.MCVer.getFontRenderer; @@ -80,18 +85,37 @@ public class GuiReplayViewer extends GuiScreen { }); public final GuiButton loadButton = new GuiButton().onClick(new Runnable() { + private boolean loading = false; + @Override public void run() { - try { - mod.startReplay(list.getSelected().get(0).file); - } catch (IOException e) { - e.printStackTrace(); - } finally { - // Disable load button to prevent the player from opening the replay twice at the same time - loadButton.setDisabled(); + // Prevent the player from opening the replay twice at the same time + if (loading) { + return; + } + loading = true; + loadButton.setDisabled(); // visual hint + + List selected = list.getSelected(); + if (selected.size() == 1) { + try { + mod.startReplay(selected.get(0).file); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + Iterator>> replays = selected.stream() + .filter(it -> !it.renderQueue.isEmpty()) + .map(it -> new Pair<>(it.file, it.renderQueue)) + .iterator(); + GuiRenderQueue.processMultipleReplays(GuiReplayViewer.this, mod, replays, () -> { + loading = false; + updateButtons(); + display(); + }); } } - }).setSize(150, 20).setI18nLabel("replaymod.gui.load").setDisabled(); + }).setSize(150, 20); public final GuiButton folderButton = new GuiButton().onClick(new Runnable() { @Override @@ -207,7 +231,7 @@ public class GuiReplayViewer extends GuiScreen { }).setSize(73, 20).setI18nLabel("replaymod.gui.cancel"); public final List replaySpecificButtons = new ArrayList<>(); - { replaySpecificButtons.addAll(Arrays.asList(loadButton, renameButton)); } + { replaySpecificButtons.addAll(Arrays.asList(renameButton)); } public final GuiPanel editorButton = new GuiPanel(); public final GuiPanel upperButtonPanel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(5)) @@ -239,6 +263,8 @@ public class GuiReplayViewer extends GuiScreen { pos(settingsButton, width - width(settingsButton) - 5, 5); } }); + + updateButtons(); } public ReplayModReplay getMod() { @@ -251,8 +277,16 @@ public class GuiReplayViewer extends GuiScreen { replaySpecificButtons.forEach(b -> b.setEnabled(count == 1)); deleteButton.setEnabled(count > 0); - if (count == 1 && selected.get(0).incompatible) { - loadButton.setDisabled(); + if (count > 1) { + Set jobs = selected.stream().flatMap(entry -> entry.renderQueue.stream()).collect(Collectors.toSet()); + String[] tooltipLines = jobs.stream().map(RenderJob::getName).toArray(String[]::new); + loadButton.setI18nLabel("replaymod.gui.viewer.bulkrender", jobs.size()); + loadButton.setTooltip(new GuiTooltip().setText(tooltipLines)); + loadButton.setEnabled(!jobs.isEmpty()); + } else { + loadButton.setI18nLabel("replaymod.gui.load"); + loadButton.setTooltip(null); + loadButton.setEnabled(count == 1 && !selected.get(0).incompatible); } } diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index a30b45e9..8c08d804 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit a30b45e9b83e06d77d4a39015d27eb18f94c468e +Subproject commit 8c08d8049ed164e763dacbcd4e1a3db59d45b30e