Add start/stop/pause/resume recording functionality

Using automatically applied, ReplayStudio-based post processing.
This commit is contained in:
Jonas Herzig
2019-03-12 13:25:05 +01:00
parent fe68f76bd1
commit eb6e7b79d0
11 changed files with 513 additions and 37 deletions

View File

@@ -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;
}
}

View File

@@ -6,12 +6,14 @@ public final class Setting<T> extends SettingsRegistry.SettingKeys<T> {
public static final Setting<Boolean> RECORD_SINGLEPLAYER = make("recordSingleplayer", "recordsingleplayer", true);
public static final Setting<Boolean> RECORD_SERVER = make("recordServer", "recordserver", true);
public static final Setting<Boolean> INDICATOR = make("indicator", "indicator", true);
public static final Setting<Boolean> AUTO_START_RECORDING = make("autoStartRecording", "autostartrecording", true);
public static final Setting<Boolean> AUTO_POST_PROCESS = make("autoPostProcess", null, true);
private static <T> Setting<T> 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);
}
}

View File

@@ -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<GuiScreen>() {
@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;
}
}

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;
}