From eb6e7b79d06431c8e490692144bb51422c526178 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Tue, 12 Mar 2019 13:25:05 +0100 Subject: [PATCH] Add start/stop/pause/resume recording functionality Using automatically applied, ReplayStudio-based post processing. --- jGui | 2 +- .../java/com/replaymod/core/ReplayMod.java | 8 + .../core/gui/GuiBackgroundProcesses.java | 135 ++++++++++++++ .../replaymod/editor/gui/MarkerProcessor.java | 173 ++++++++++++++++++ .../recording/ReplayModRecording.java | 6 +- .../java/com/replaymod/recording/Setting.java | 4 +- .../recording/gui/GuiRecordingControls.java | 110 +++++++++++ .../recording/gui/GuiRecordingOverlay.java | 8 +- .../handler/ConnectionEventHandler.java | 30 +-- .../recording/packet/PacketListener.java | 72 ++++++-- src/main/resources/assets/replaymod/lang | 2 +- 11 files changed, 513 insertions(+), 37 deletions(-) create mode 100644 src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java create mode 100644 src/main/java/com/replaymod/editor/gui/MarkerProcessor.java create mode 100644 src/main/java/com/replaymod/recording/gui/GuiRecordingControls.java diff --git a/jGui b/jGui index 31fecfa0..63aec9fd 160000 --- a/jGui +++ b/jGui @@ -1 +1 @@ -Subproject commit 31fecfa0d62ab5c689d301a24a5584c3537f8f16 +Subproject commit 63aec9fd9a7acf4dad8b128ee86e70ea05fa4f5c diff --git a/src/main/java/com/replaymod/core/ReplayMod.java b/src/main/java/com/replaymod/core/ReplayMod.java index ed41c6aa..5efd7fea 100755 --- a/src/main/java/com/replaymod/core/ReplayMod.java +++ b/src/main/java/com/replaymod/core/ReplayMod.java @@ -2,6 +2,7 @@ package com.replaymod.core; import com.google.common.io.Files; import com.google.common.util.concurrent.ListenableFutureTask; +import com.replaymod.core.gui.GuiBackgroundProcesses; import com.replaymod.core.gui.GuiReplaySettings; import com.replaymod.core.gui.RestoreReplayGui; import com.replaymod.core.handler.MainMenuHandler; @@ -152,6 +153,8 @@ public class ReplayMod implements Module { private final List modules = new ArrayList<>(); + private final GuiBackgroundProcesses backgroundProcesses = new GuiBackgroundProcesses(); + public ReplayMod() { I18n.setI18n(net.minecraft.client.resources.I18n::format); @@ -285,6 +288,7 @@ public class ReplayMod implements Module { //#endif FML_BUS.register(keyBindingRegistry); + FORGE_BUS.register(backgroundProcesses); // 1.7.10 crashes when render distance > 16 //#if MC>=10800 @@ -455,4 +459,8 @@ public class ReplayMod implements Module { mc.ingameGUI.getChatGUI().printChatMessage(text); } } + + public GuiBackgroundProcesses getBackgroundProcesses() { + return backgroundProcesses; + } } diff --git a/src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java b/src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java new file mode 100644 index 00000000..d0754278 --- /dev/null +++ b/src/main/java/com/replaymod/core/gui/GuiBackgroundProcesses.java @@ -0,0 +1,135 @@ +package com.replaymod.core.gui; + +import de.johni0702.minecraft.gui.GuiRenderer; +import de.johni0702.minecraft.gui.RenderInfo; +import de.johni0702.minecraft.gui.container.AbstractGuiContainer; +import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.container.GuiScreen; +import de.johni0702.minecraft.gui.container.VanillaGuiScreen; +import de.johni0702.minecraft.gui.element.GuiElement; +import de.johni0702.minecraft.gui.layout.CustomLayout; +import de.johni0702.minecraft.gui.layout.VerticalLayout; +import de.johni0702.minecraft.gui.utils.lwjgl.Dimension; +import de.johni0702.minecraft.gui.utils.lwjgl.ReadableDimension; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.common.MinecraftForge; + +//#if MC>=10800 +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif +//#else +//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; +//#endif + +import static com.replaymod.core.versions.MCVer.getGui; +import static com.replaymod.core.versions.MCVer.getMinecraft; + +public class GuiBackgroundProcesses { + private GuiPanel panel = new GuiPanel().setLayout(new VerticalLayout().setSpacing(10)); + + public void register() { + MinecraftForge.EVENT_BUS.register(this); + } + + public void unregister() { + MinecraftForge.EVENT_BUS.unregister(this); + } + + @SubscribeEvent + public void onGuiInit(GuiScreenEvent.InitGuiEvent.Post event) { + if (getGui(event) != getMinecraft().currentScreen) return; // people tend to construct GuiScreens without opening them + + VanillaGuiScreen.setup(getGui(event)).setLayout(new CustomLayout() { + @Override + protected void layout(GuiScreen container, int width, int height) { + pos(panel, width - 5 - width(panel), 5); + } + }).addElements(null, panel); + } + + public void addProcess(GuiElement element) { + panel.addElements(new VerticalLayout.Data(1.0), + new Element(element)); + } + + public void removeProcess(GuiElement element) { + for (GuiElement child : panel.getChildren()) { + if (((Element) child).inner == element) { + panel.removeElement(child); + return; + } + } + } + + private static class Element extends AbstractGuiContainer { + private GuiElement inner; + + Element(GuiElement inner) { + this.inner = inner; + addElements(null, inner); + + setLayout(new CustomLayout() { + @Override + protected void layout(Element container, int width, int height) { + pos(inner, 10, 10); + size(inner, width - 20, height - 20); + } + }); + } + + @Override + public ReadableDimension getMinSize() { + ReadableDimension minSize = inner.getMinSize(); + return new Dimension(minSize.getWidth() + 20, minSize.getHeight() + 20); + } + + @Override + public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) { + final int u0 = 0; + final int v0 = 39; + renderer.bindTexture(TEXTURE); + + int w = size.getWidth(); + int h = size.getHeight(); + + // Corners + renderer.drawTexturedRect(0, 0, u0, v0, 5, 5); // Top left + renderer.drawTexturedRect(w - 5, 0, u0 + 12, v0, 5, 5); // Top right + renderer.drawTexturedRect(0, h - 5, u0, v0 + 12, 5, 5); // Bottom left + renderer.drawTexturedRect(w - 5, h - 5, u0 + 12, v0 + 12, 5, 5); // Bottom right + + // Top and bottom edge + for (int x = 5; x < w - 5; x += 5) { + int rx = Math.min(5, w - 5 - x); + renderer.drawTexturedRect(x, 0, u0 + 6, v0, rx, 5); // Top + renderer.drawTexturedRect(x, h - 5, u0 + 6, v0 + 12, rx, 5); // Bottom + } + + // Left and right edge + for (int y = 5; y < h - 5; y += 5) { + int ry = Math.min(5, h - 5 - y); + renderer.drawTexturedRect(0, y, u0, v0 + 6, 5, ry); // Left + renderer.drawTexturedRect(w - 5, y, u0 + 12, v0 + 6, 5, ry); // Right + } + + // Center + for (int x = 5; x < w - 5; x += 5) { + for (int y = 5; y < h - 5; y += 5) { + int rx = Math.min(5, w - 5 - x); + int ry = Math.min(5, h - 5 - y); + renderer.drawTexturedRect(x, y, u0 + 6, v0 + 6, rx, ry); + } + } + + super.draw(renderer, size, renderInfo); + } + + @Override + protected Element getThis() { + return this; + } + } +} diff --git a/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java new file mode 100644 index 00000000..a71da532 --- /dev/null +++ b/src/main/java/com/replaymod/editor/gui/MarkerProcessor.java @@ -0,0 +1,173 @@ +package com.replaymod.editor.gui; + +import com.replaymod.replaystudio.PacketData; +import com.replaymod.replaystudio.Studio; +import com.replaymod.replaystudio.data.Marker; +import com.replaymod.replaystudio.filter.SquashFilter; +import com.replaymod.replaystudio.filter.StreamFilter; +import com.replaymod.replaystudio.io.ReplayInputStream; +import com.replaymod.replaystudio.io.ReplayOutputStream; +import com.replaymod.replaystudio.replay.ReplayMetaData; +import com.replaymod.replaystudio.replay.ZipReplayFile; +import com.replaymod.replaystudio.stream.IteratorStream; +import com.replaymod.replaystudio.studio.ReplayStudio; +import com.replaymod.replaystudio.util.Utils; +import org.apache.commons.io.FilenameUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class MarkerProcessor { + /** + * Signals the start of a section which is to be cut from the replay. + */ + public static final String MARKER_NAME_START_CUT = "_RM_START_CUT"; + /** + * Signals the end of a section which is to be cut from the replay. + */ + public static final String MARKER_NAME_END_CUT = "_RM_END_CUT"; + /** + * Signals the point at which a replay is to be split into multiple replays. + * If it is found in a section which is to be cut, that section will be cut from both replays. + */ + public static final String MARKER_NAME_SPLIT = "_RM_SPLIT"; + + private static boolean hasWork(Path path) throws IOException { + try (ZipReplayFile inputReplayFile = new ZipReplayFile(new ReplayStudio(), path.toFile())) { + return inputReplayFile.getMarkers().or(HashSet::new).stream().anyMatch(m -> m.getName().startsWith("_RM_")); + } + } + + public static void apply(Path path) throws IOException { + if (!hasWork(path)) { + return; + } + + String replayName = FilenameUtils.getBaseName(path.getFileName().toString()); + int splitCounter = 0; + + Studio studio = new ReplayStudio(); + SquashFilter squashFilter = new SquashFilter(); + squashFilter.init(studio, null); + + Path inputPath = path.resolveSibling("raw").resolve(path.getFileName()); + Files.createDirectories(inputPath.getParent()); + Files.move(path, inputPath); + + try (ZipReplayFile inputReplayFile = new ZipReplayFile(studio, inputPath.toFile())) { + List markers = inputReplayFile.getMarkers().or(HashSet::new) + .stream().sorted(Comparator.comparing(Marker::getTime)).collect(Collectors.toList()); + Iterator markerIterator = markers.iterator(); + boolean anySplit = markers.stream().anyMatch(m -> m.getName().equals(MARKER_NAME_SPLIT)); + + ReplayInputStream replayInputStream = inputReplayFile.getPacketData(studio, true); + int timeOffset = 0; + SquashFilter cutFilter = null; + int startCutOffset = 0; + PacketData nextPacket = replayInputStream.readPacket(); + Marker nextMarker = markerIterator.next(); + + while (nextPacket != null) { + try (ZipReplayFile outputReplayFile = new ZipReplayFile(studio, null, + (anySplit ? path.resolveSibling(replayName + "_" + splitCounter + ".mcpr") : path).toFile())) { + long duration = 0; + Set outputMarkers = new HashSet<>(); + ReplayMetaData metaData = inputReplayFile.getMetaData(); + metaData.setDate(metaData.getDate() + timeOffset); + + try (ReplayOutputStream replayOutputStream = outputReplayFile.writePacketData(true)) { + if (cutFilter != null) { + cutFilter = squashFilter; + } else if (splitCounter > 0) { + List packets = new ArrayList<>(); + squashFilter.onEnd( + new IteratorStream(packets.listIterator(), (StreamFilter) null), + timeOffset + ); + for (PacketData packet : packets) { + replayOutputStream.write(packet.getTime() - timeOffset, packet.getPacket()); + } + } + + while (nextPacket != null) { + if (nextMarker != null && nextPacket.getTime() > nextMarker.getTime()) { + if (MARKER_NAME_START_CUT.equals(nextMarker.getName())) { + startCutOffset = nextMarker.getTime(); + cutFilter = new SquashFilter(); + } else if (MARKER_NAME_END_CUT.equals(nextMarker.getName())) { + timeOffset += nextMarker.getTime() - startCutOffset; + if (cutFilter != null) { + List packets = new ArrayList<>(); + cutFilter.onEnd( + new IteratorStream(packets.listIterator(), (StreamFilter) null), + nextMarker.getTime() + ); + for (PacketData packet : packets) { + replayOutputStream.write(packet.getTime() - timeOffset, packet.getPacket()); + } + cutFilter = null; + } + } else if (MARKER_NAME_SPLIT.equals(nextMarker.getName())) { + splitCounter++; + timeOffset = nextMarker.getTime(); + startCutOffset = timeOffset; + nextMarker = markerIterator.hasNext() ? markerIterator.next() : null; + break; + } else { + nextMarker.setTime(nextMarker.getTime() - timeOffset); + outputMarkers.add(nextMarker); + } + nextMarker = markerIterator.hasNext() ? markerIterator.next() : null; + continue; + } + + squashFilter.onPacket(null, nextPacket); + if (cutFilter != null) { + if (cutFilter != squashFilter) { + cutFilter.onPacket(null, nextPacket); + } + } else { + replayOutputStream.write(nextPacket.getTime() - timeOffset, nextPacket.getPacket()); + duration = nextPacket.getTime() - timeOffset; + } + nextPacket = replayInputStream.readPacket(); + } + } + + metaData.setDuration((int) duration); + outputReplayFile.writeMetaData(metaData); + + outputReplayFile.writeMarkers(outputMarkers); + + outputReplayFile.writeModInfo(inputReplayFile.getModInfo()); + + // We could filter these but ReplayStudio doesn't yet provide a nice method for determining + // which ones are used. + Map resourcePackIndex = inputReplayFile.getResourcePackIndex(); + if (resourcePackIndex != null) { + outputReplayFile.writeResourcePackIndex(resourcePackIndex); + for (String hash : resourcePackIndex.values()) { + try (InputStream in = inputReplayFile.getResourcePack(hash).get(); + OutputStream out = outputReplayFile.writeResourcePack(hash)) { + Utils.copy(in, out); + } + } + } + + outputReplayFile.save(); + } + } + } + } +} diff --git a/src/main/java/com/replaymod/recording/ReplayModRecording.java b/src/main/java/com/replaymod/recording/ReplayModRecording.java index d949a66c..f8137d65 100644 --- a/src/main/java/com/replaymod/recording/ReplayModRecording.java +++ b/src/main/java/com/replaymod/recording/ReplayModRecording.java @@ -60,7 +60,7 @@ public class ReplayModRecording implements Module { public void run() { PacketListener packetListener = connectionEventHandler.getPacketListener(); if (packetListener != null) { - packetListener.addMarker(); + packetListener.addMarker(null); core.printInfoToChat("replaymod.chat.addedmarker"); } } @@ -92,4 +92,8 @@ public class ReplayModRecording implements Module { //#endif connectionEventHandler.onConnectedToServerEvent(networkManager); } + + public ConnectionEventHandler getConnectionEventHandler() { + return connectionEventHandler; + } } diff --git a/src/main/java/com/replaymod/recording/Setting.java b/src/main/java/com/replaymod/recording/Setting.java index e3742d8b..27d2d69c 100644 --- a/src/main/java/com/replaymod/recording/Setting.java +++ b/src/main/java/com/replaymod/recording/Setting.java @@ -6,12 +6,14 @@ public final class Setting extends SettingsRegistry.SettingKeys { public static final Setting RECORD_SINGLEPLAYER = make("recordSingleplayer", "recordsingleplayer", true); public static final Setting RECORD_SERVER = make("recordServer", "recordserver", true); public static final Setting INDICATOR = make("indicator", "indicator", true); + public static final Setting AUTO_START_RECORDING = make("autoStartRecording", "autostartrecording", true); + public static final Setting AUTO_POST_PROCESS = make("autoPostProcess", null, true); private static Setting make(String key, String displayName, T defaultValue) { return new Setting<>(key, displayName, defaultValue); } public Setting(String key, String displayString, T defaultValue) { - super("recording", key, "replaymod.gui.settings." + displayString, defaultValue); + super("recording", key, displayString == null ? null : "replaymod.gui.settings." + displayString, defaultValue); } } diff --git a/src/main/java/com/replaymod/recording/gui/GuiRecordingControls.java b/src/main/java/com/replaymod/recording/gui/GuiRecordingControls.java new file mode 100644 index 00000000..8eb81b62 --- /dev/null +++ b/src/main/java/com/replaymod/recording/gui/GuiRecordingControls.java @@ -0,0 +1,110 @@ +package com.replaymod.recording.gui; + +import com.replaymod.core.ReplayMod; +import com.replaymod.editor.gui.MarkerProcessor; +import com.replaymod.recording.Setting; +import com.replaymod.recording.packet.PacketListener; +import de.johni0702.minecraft.gui.container.GuiPanel; +import de.johni0702.minecraft.gui.container.GuiScreen; +import de.johni0702.minecraft.gui.container.VanillaGuiScreen; +import de.johni0702.minecraft.gui.element.GuiButton; +import de.johni0702.minecraft.gui.layout.CustomLayout; +import de.johni0702.minecraft.gui.layout.HorizontalLayout; +import net.minecraft.client.gui.GuiIngameMenu; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.common.MinecraftForge; + +//#if MC>=10800 +//#if MC>=11300 +import net.minecraftforge.eventbus.api.SubscribeEvent; +//#else +//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +//#endif +//#else +//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; +//#endif + +import static com.replaymod.core.versions.MCVer.getGui; + +public class GuiRecordingControls { + private ReplayMod core; + private PacketListener packetListener; + private boolean paused; + private boolean stopped; + + private GuiPanel panel = new GuiPanel().setLayout(new HorizontalLayout().setSpacing(4)); + + private GuiButton buttonPauseResume = new GuiButton(panel).onClick(() -> { + if (paused) { + packetListener.addMarker(MarkerProcessor.MARKER_NAME_END_CUT); + } else { + packetListener.addMarker(MarkerProcessor.MARKER_NAME_START_CUT); + } + paused = !paused; + updateState(); + }).setSize(98, 20); + + private GuiButton buttonStartStop = new GuiButton(panel).onClick(() -> { + if (stopped) { + paused = false; + packetListener.addMarker(MarkerProcessor.MARKER_NAME_END_CUT); + core.printInfoToChat("replaymod.chat.recordingstarted"); + } else { + int timestamp = (int) packetListener.getCurrentDuration(); + if (!paused) { + packetListener.addMarker(MarkerProcessor.MARKER_NAME_START_CUT, timestamp); + } + packetListener.addMarker(MarkerProcessor.MARKER_NAME_SPLIT, timestamp + 1); + } + stopped = !stopped; + updateState(); + }).setSize(98, 20); + + public GuiRecordingControls(ReplayMod core, PacketListener packetListener) { + this.core = core; + this.packetListener = packetListener; + + paused = stopped = !core.getSettingsRegistry().get(Setting.AUTO_START_RECORDING); + + updateState(); + } + + public void register() { + MinecraftForge.EVENT_BUS.register(this); + } + + public void unregister() { + MinecraftForge.EVENT_BUS.unregister(this); + } + + @SubscribeEvent + public void onGuiInit(GuiScreenEvent.InitGuiEvent.Post event) { + if (getGui(event) instanceof GuiIngameMenu) { + show((GuiIngameMenu) getGui(event)); + } + } + + private void updateState() { + buttonPauseResume.setI18nLabel("replaymod.gui.recording." + (paused ? "resume" : "pause")); + buttonStartStop.setI18nLabel("replaymod.gui.recording." + (stopped ? "start" : "stop")); + + buttonPauseResume.setEnabled(!stopped); + } + + public void show(GuiIngameMenu gui) { + VanillaGuiScreen.setup(gui).setLayout(new CustomLayout() { + @Override + protected void layout(GuiScreen container, int width, int height) { + pos(panel, width / 2 - 100, height / 4 + 128); + } + }).addElements(null, panel); + } + + public boolean isPaused() { + return paused; + } + + public boolean isStopped() { + return stopped; + } +} diff --git a/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java b/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java index 50e0f033..a0237d75 100644 --- a/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java +++ b/src/main/java/com/replaymod/recording/gui/GuiRecordingOverlay.java @@ -31,10 +31,12 @@ import static com.replaymod.core.versions.MCVer.*; public class GuiRecordingOverlay { private final Minecraft mc; private final SettingsRegistry settingsRegistry; + private final GuiRecordingControls guiControls; - public GuiRecordingOverlay(Minecraft mc, SettingsRegistry settingsRegistry) { + public GuiRecordingOverlay(Minecraft mc, SettingsRegistry settingsRegistry, GuiRecordingControls guiControls) { this.mc = mc; this.settingsRegistry = settingsRegistry; + this.guiControls = guiControls; } public void register() { @@ -52,9 +54,11 @@ public class GuiRecordingOverlay { @SubscribeEvent public void renderRecordingIndicator(RenderGameOverlayEvent.Post event) { if (getType(event) != RenderGameOverlayEvent.ElementType.ALL) return; + if (guiControls.isStopped()) return; if (settingsRegistry.get(Setting.INDICATOR)) { FontRenderer fontRenderer = getFontRenderer(mc); - fontRenderer.drawString(I18n.format("replaymod.gui.recording").toUpperCase(), 30, 18 - (fontRenderer.FONT_HEIGHT / 2), 0xffffffff); + String text = guiControls.isPaused() ? I18n.format("replaymod.gui.paused") : I18n.format("replaymod.gui.recording"); + fontRenderer.drawString(text.toUpperCase(), 30, 18 - (fontRenderer.FONT_HEIGHT / 2), 0xffffffff); bindTexture(TEXTURE); resetColor(); enableAlpha(); diff --git a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java index f93f7572..cd1cd4e9 100755 --- a/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java +++ b/src/main/java/com/replaymod/recording/handler/ConnectionEventHandler.java @@ -3,7 +3,9 @@ package com.replaymod.recording.handler; import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.ModCompat; import com.replaymod.core.utils.Utils; +import com.replaymod.editor.gui.MarkerProcessor; import com.replaymod.recording.Setting; +import com.replaymod.recording.gui.GuiRecordingControls; import com.replaymod.recording.gui.GuiRecordingOverlay; import com.replaymod.recording.packet.PacketListener; import com.replaymod.replaystudio.replay.ReplayFile; @@ -17,16 +19,9 @@ import org.apache.logging.log4j.Logger; //#if MC>=10800 //#if MC>=11300 import net.minecraft.world.dimension.DimensionType; -import net.minecraftforge.eventbus.api.SubscribeEvent; -//#else -//$$ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -//$$ import net.minecraftforge.fml.common.network.FMLNetworkEvent.ClientDisconnectionFromServerEvent; //#endif import static com.replaymod.core.versions.MCVer.WorldType_DEBUG_ALL_BLOCK_STATES; -//#else -//$$ import cpw.mods.fml.common.eventhandler.SubscribeEvent; -//$$ import cpw.mods.fml.common.network.FMLNetworkEvent.ClientDisconnectionFromServerEvent; //#endif import java.io.File; @@ -51,6 +46,7 @@ public class ConnectionEventHandler { private RecordingEventHandler recordingEventHandler; private PacketListener packetListener; private GuiRecordingOverlay guiOverlay; + private GuiRecordingControls guiControls; public ConnectionEventHandler(Logger logger, ReplayMod core) { this.logger = logger; @@ -111,26 +107,33 @@ public class ConnectionEventHandler { metaData.setGenerator("ReplayMod v" + ReplayMod.instance.getVersion()); metaData.setDate(System.currentTimeMillis()); metaData.setMcVersion(ReplayMod.getMinecraftVersion()); - packetListener = new PacketListener(replayFile, metaData); + packetListener = new PacketListener(core, currentFile.toPath(), replayFile, metaData); networkManager.channel().pipeline().addBefore(packetHandlerKey, "replay_recorder", packetListener); recordingEventHandler = new RecordingEventHandler(packetListener); recordingEventHandler.register(); - guiOverlay = new GuiRecordingOverlay(mc, core.getSettingsRegistry()); + guiControls = new GuiRecordingControls(core, packetListener); + guiControls.register(); + + guiOverlay = new GuiRecordingOverlay(mc, core.getSettingsRegistry(), guiControls); guiOverlay.register(); - core.printInfoToChat("replaymod.chat.recordingstarted"); + if (core.getSettingsRegistry().get(Setting.AUTO_START_RECORDING)) { + core.printInfoToChat("replaymod.chat.recordingstarted"); + } else { + packetListener.addMarker(MarkerProcessor.MARKER_NAME_START_CUT, 0); + } } catch (Throwable e) { e.printStackTrace(); core.printWarningToChat("replaymod.chat.recordingfailed"); } } - /* FIXME event not (yet?) in 1.13 - @SubscribeEvent - public void onDisconnectedFromServerEvent(ClientDisconnectionFromServerEvent event) { + public void reset() { if (packetListener != null) { + guiControls.unregister(); + guiControls = null; guiOverlay.unregister(); guiOverlay = null; recordingEventHandler.unregister(); @@ -138,7 +141,6 @@ public class ConnectionEventHandler { packetListener = null; } } - */ public PacketListener getPacketListener() { return packetListener; diff --git a/src/main/java/com/replaymod/recording/packet/PacketListener.java b/src/main/java/com/replaymod/recording/packet/PacketListener.java index 2ead09fb..44e9851f 100755 --- a/src/main/java/com/replaymod/recording/packet/PacketListener.java +++ b/src/main/java/com/replaymod/recording/packet/PacketListener.java @@ -1,9 +1,16 @@ package com.replaymod.recording.packet; +import com.replaymod.core.ReplayMod; import com.replaymod.core.utils.Restrictions; +import com.replaymod.editor.gui.MarkerProcessor; +import com.replaymod.recording.ReplayModRecording; +import com.replaymod.recording.Setting; +import com.replaymod.recording.handler.ConnectionEventHandler; import com.replaymod.replaystudio.data.Marker; import com.replaymod.replaystudio.replay.ReplayFile; import com.replaymod.replaystudio.replay.ReplayMetaData; +import de.johni0702.minecraft.gui.element.GuiLabel; +import de.johni0702.minecraft.gui.utils.Colors; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; @@ -42,6 +49,7 @@ import net.minecraft.network.EnumPacketDirection; import java.io.DataOutputStream; import java.io.IOException; +import java.nio.file.Path; import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -58,6 +66,8 @@ public class PacketListener extends ChannelInboundHandlerAdapter { private static final Minecraft mc = getMinecraft(); private static final Logger logger = LogManager.getLogger(); + private final ReplayMod core; + private final Path outputPath; private final ReplayFile replayFile; private final ResourcePackRecorder resourcePackRecorder; @@ -85,7 +95,9 @@ public class PacketListener extends ChannelInboundHandlerAdapter { */ private final AtomicInteger lastSaveMetaDataId = new AtomicInteger(); - public PacketListener(ReplayFile replayFile, ReplayMetaData metaData) throws IOException { + public PacketListener(ReplayMod core, Path outputPath, ReplayFile replayFile, ReplayMetaData metaData) throws IOException { + this.core = core; + this.outputPath = outputPath; this.replayFile = replayFile; this.metaData = metaData; this.resourcePackRecorder = new ResourcePackRecorder(replayFile); @@ -174,21 +186,39 @@ public class PacketListener extends ChannelInboundHandlerAdapter { metaData.setDuration((int) lastSentPacket); saveMetaData(); - saveService.shutdown(); - try { - saveService.awaitTermination(10, TimeUnit.SECONDS); - } catch (InterruptedException e) { - logger.error("Waiting for save service termination:", e); - } - - synchronized (replayFile) { - try { - replayFile.save(); - replayFile.close(); - } catch (IOException e) { - logger.error("Saving replay file:", e); + core.runLater(() -> { + ConnectionEventHandler connectionEventHandler = ReplayModRecording.instance.getConnectionEventHandler(); + if (connectionEventHandler.getPacketListener() == this) { + connectionEventHandler.reset(); } - } + }); + + GuiLabel savingLabel = new GuiLabel().setI18nText("replaymod.gui.replaysaving.title").setColor(Colors.BLACK); + new Thread(() -> { + core.runLater(() -> core.getBackgroundProcesses().addProcess(savingLabel)); + + saveService.shutdown(); + try { + saveService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + logger.error("Waiting for save service termination:", e); + } + + synchronized (replayFile) { + try { + replayFile.save(); + replayFile.close(); + + if (core.getSettingsRegistry().get(Setting.AUTO_POST_PROCESS)) { + MarkerProcessor.apply(outputPath); + } + } catch (IOException e) { + logger.error("Saving replay file:", e); + } + } + + core.runLater(() -> core.getBackgroundProcesses().removeProcess(savingLabel)); + }).start(); } @Override @@ -376,11 +406,15 @@ public class PacketListener extends ChannelInboundHandlerAdapter { return array; } - public void addMarker() { + public void addMarker(String name) { + addMarker(name, (int) getCurrentDuration()); + } + + public void addMarker(String name, int timestamp) { Entity view = getRenderViewEntity(mc); - int timestamp = (int) (System.currentTimeMillis() - startTime); Marker marker = new Marker(); + marker.setName(name); marker.setTime(timestamp); marker.setX(view.posX); marker.setY(view.posY); @@ -401,6 +435,10 @@ public class PacketListener extends ChannelInboundHandlerAdapter { }); } + public long getCurrentDuration() { + return lastSentPacket; + } + public void setServerWasPaused() { this.serverWasPaused = true; } diff --git a/src/main/resources/assets/replaymod/lang b/src/main/resources/assets/replaymod/lang index 552d02cd..967405aa 160000 --- a/src/main/resources/assets/replaymod/lang +++ b/src/main/resources/assets/replaymod/lang @@ -1 +1 @@ -Subproject commit 552d02cdc7e67ddfaeecb2bd60b5a5d1632e5130 +Subproject commit 967405aafad5e7613c3eb9760d8eab03ad4e1567