Merge branch '1.8' into 1.9.4
d9b796dMixin'd the Shader's mods ShadersRender class to only render hands if the Forge event isn't cancelled6bbff3bUpdated Mixin to 0.6.4-SNAPSHOT and set mixin target level6f3ac71Add CustomMainMenu compatibility info to docsb0a1cdfSearch for ffmpeg executable in common locations to simplify setupd465891Fixed GuiEditPositionKeyframe's width to fit on all screen sizes Centered all rows of GuiEditPositionKeyframe's VerticalLayout1b099f9Add render queue03534c6Update docs4388d69Only try to create the downloads folder if it does not yet exist7ad19aaAdd mod version and accepted MC version directly to the @Mod annotation655bea0Replace @Mod.Instance with direct static references458dad6Merge pull request #4 from ReplayMod/1.8-editoreb5ccb2Add Replay Editor (for now, trimming only)6498d62Fix server game data snapshot being restored in singleplayer (fixes #44)2c898a8Prevent rendering without sufficient keyframes (fixes #45)5d520a5Fix deserialization of chroma key color (fixes #43)c681742Use caching maven proxy for faster and more stable drone buildsfbf165eAdded Anti-Aliasing to the Render Settings
This commit is contained in:
41
src/main/java/com/replaymod/editor/ReplayModEditor.java
Normal file
41
src/main/java/com/replaymod/editor/ReplayModEditor.java
Normal file
@@ -0,0 +1,41 @@
|
||||
package com.replaymod.editor;
|
||||
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.editor.handler.GuiHandler;
|
||||
import com.replaymod.online.Setting;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
|
||||
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@Mod(modid = ReplayModEditor.MOD_ID,
|
||||
version = "@MOD_VERSION@",
|
||||
acceptedMinecraftVersions = "@MC_VERSION@",
|
||||
useMetadata = true)
|
||||
public class ReplayModEditor {
|
||||
public static final String MOD_ID = "replaymod-editor";
|
||||
|
||||
@Mod.Instance(MOD_ID)
|
||||
public static ReplayModEditor instance;
|
||||
|
||||
private ReplayMod core;
|
||||
|
||||
public static Logger LOGGER;
|
||||
|
||||
@Mod.EventHandler
|
||||
public void preInit(FMLPreInitializationEvent event) {
|
||||
ReplayModEditor.LOGGER = event.getModLog();
|
||||
core = ReplayMod.instance;
|
||||
|
||||
core.getSettingsRegistry().register(Setting.class);
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
public void init(FMLInitializationEvent event) {
|
||||
new GuiHandler(this).register();
|
||||
}
|
||||
|
||||
public ReplayMod getCore() {
|
||||
return core;
|
||||
}
|
||||
}
|
||||
360
src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java
Normal file
360
src/main/java/com/replaymod/editor/gui/GuiReplayEditor.java
Normal file
@@ -0,0 +1,360 @@
|
||||
package com.replaymod.editor.gui;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.core.utils.Utils;
|
||||
import com.replaymod.replaystudio.PacketData;
|
||||
import com.replaymod.replaystudio.Studio;
|
||||
import com.replaymod.replaystudio.filter.StreamFilter;
|
||||
import com.replaymod.replaystudio.io.ReplayInputStream;
|
||||
import com.replaymod.replaystudio.io.ReplayOutputStream;
|
||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
||||
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
||||
import com.replaymod.replaystudio.stream.PacketStream;
|
||||
import com.replaymod.replaystudio.studio.ReplayStudio;
|
||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||
import de.johni0702.minecraft.gui.RenderInfo;
|
||||
import de.johni0702.minecraft.gui.container.GuiContainer;
|
||||
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.element.GuiTextField;
|
||||
import de.johni0702.minecraft.gui.element.GuiTooltip;
|
||||
import de.johni0702.minecraft.gui.element.advanced.GuiProgressBar;
|
||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||
import de.johni0702.minecraft.gui.layout.GridLayout;
|
||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
||||
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
||||
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
|
||||
import de.johni0702.minecraft.gui.popup.GuiInfoPopup;
|
||||
import de.johni0702.minecraft.gui.popup.GuiYesNoPopup;
|
||||
import de.johni0702.minecraft.gui.utils.Colors;
|
||||
import net.minecraft.crash.CrashReport;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static com.replaymod.editor.ReplayModEditor.LOGGER;
|
||||
|
||||
public class GuiReplayEditor extends GuiScreen {
|
||||
private final ReplayMod mod;
|
||||
|
||||
private volatile ProcessingStage processingStage;
|
||||
private volatile double progress;
|
||||
private GuiProcessingProgressPopup popup;
|
||||
|
||||
public GuiButton currentTabButton;
|
||||
public GuiPanel currentTabPanel;
|
||||
|
||||
public final GuiPanel tabButtons = new GuiPanel(this).setLayout(new GridLayout().setSpacingX(5));
|
||||
public final List<GuiPanel> tabPanels = new ArrayList<>();
|
||||
|
||||
public final GuiButton saveButton = new GuiButton().setI18nLabel("replaymod.gui.save").setSize(100, 20);
|
||||
public final GuiButton backButton = new GuiButton().setI18nLabel("replaymod.gui.back").setSize(100, 20);
|
||||
|
||||
public final GuiPanel buttonPanel = new GuiPanel(this).setLayout(new HorizontalLayout().setSpacing(5))
|
||||
.addElements(null, saveButton, backButton);
|
||||
|
||||
public GuiReplayEditor(GuiScreen parent, ReplayMod mod) {
|
||||
this.mod = mod;
|
||||
backButton.onClick(parent::display);
|
||||
|
||||
setTitle(new GuiLabel().setI18nText("replaymod.gui.replayeditor"));
|
||||
|
||||
makeTab("trim", () -> new GuiTrimPanel(this));
|
||||
makeTab("connect", GuiPanel::new).setDisabled(); // Not yet implemented
|
||||
makeTab("modify", GuiPanel::new).setDisabled(); // Not yet implemented
|
||||
|
||||
setLayout(new CustomLayout<GuiScreen>() {
|
||||
@Override
|
||||
protected void layout(GuiScreen container, int width, int height) {
|
||||
// Move all inactive panels aside
|
||||
tabPanels.forEach(e -> pos(e, Integer.MIN_VALUE, Integer.MIN_VALUE));
|
||||
|
||||
pos(buttonPanel, width - 10 - width(buttonPanel), height - 10 - height(buttonPanel));
|
||||
|
||||
if (currentTabPanel != null) {
|
||||
pos(currentTabPanel, 10, 50);
|
||||
size(currentTabPanel, width - 20, y(buttonPanel) - 10 - y(currentTabPanel));
|
||||
}
|
||||
|
||||
pos(tabButtons, 10, 20);
|
||||
size(tabButtons, width - 20, 20);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public GuiButton makeTab(String name, Supplier<GuiPanel> panel) {
|
||||
GuiButton button = new GuiButton().setI18nLabel("replaymod.gui.editor." + name + ".title")
|
||||
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.editor." + name + ".description"))
|
||||
.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||
button.onClick(() -> {
|
||||
// Remove previous tab
|
||||
if (currentTabButton != null) {
|
||||
currentTabButton.setEnabled();
|
||||
removeElement(currentTabPanel);
|
||||
}
|
||||
|
||||
// Activate new tab
|
||||
currentTabButton = button.setDisabled();
|
||||
addElements(null, currentTabPanel = panel.get());
|
||||
});
|
||||
// Add new tab button to tabs panel
|
||||
tabButtons.addElements(null, button);
|
||||
((GridLayout) tabButtons.getLayout()).setColumns(tabButtons.getChildren().size());
|
||||
return button;
|
||||
}
|
||||
|
||||
public void save(File inputFile, PacketStream.FilterInfo...filters) {
|
||||
save(FilenameUtils.getBaseName(inputFile.getName()), (outputFile) -> {
|
||||
Studio studio = new ReplayStudio();
|
||||
try (ReplayFile outputReplay = new ZipReplayFile(studio, inputFile, outputFile);
|
||||
ReplayOutputStream out = outputReplay.writePacketData()) {
|
||||
// The input replay file MUST be closed before saving the output file
|
||||
try (ReplayFile inputReplay = new ZipReplayFile(studio, inputFile);
|
||||
ReplayInputStream in = inputReplay.getPacketData()) {
|
||||
ReplayMetaData metaData = inputReplay.getMetaData();
|
||||
PacketStream stream = studio.createReplayStream(in, true);
|
||||
|
||||
stream.addFilter(new ProgressFilter(metaData.getDuration()));
|
||||
for (PacketStream.FilterInfo info : filters) {
|
||||
stream.addFilter(info.getFilter(), info.getFrom(), info.getTo());
|
||||
LOGGER.debug("Added filter {}", info);
|
||||
}
|
||||
|
||||
LOGGER.info("Built pipeline: {}", stream);
|
||||
stream.start();
|
||||
|
||||
long lastTimestamp = 0;
|
||||
|
||||
PacketData data;
|
||||
while ((data = stream.next()) != null) {
|
||||
out.write(data);
|
||||
lastTimestamp = data.getTime();
|
||||
}
|
||||
|
||||
for (PacketData d : stream.end()) {
|
||||
out.write(d);
|
||||
lastTimestamp = d.getTime();
|
||||
}
|
||||
|
||||
updateProgress(ProcessingStage.FINALIZING, 0);
|
||||
|
||||
// Update duration of new replay
|
||||
metaData.setDuration((int) lastTimestamp);
|
||||
outputReplay.writeMetaData(metaData);
|
||||
}
|
||||
out.close();
|
||||
outputReplay.save();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void save(String sourceName, Consumer<File> runnable) {
|
||||
GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true).setText(sourceName);
|
||||
GuiYesNoPopup popup = GuiYesNoPopup.open(this,
|
||||
nameField
|
||||
).setYesI18nLabel("replaymod.gui.editor.savemode.override").setNoI18nLabel("replaymod.gui.cancel");
|
||||
nameField.onEnter(() -> {
|
||||
if (popup.getYesButton().isEnabled()) {
|
||||
popup.getYesButton().onClick();
|
||||
}
|
||||
}).onTextChanged(obj -> {
|
||||
popup.getYesButton().setEnabled(!nameField.getText().isEmpty());
|
||||
popup.setYesI18nLabel("replaymod.gui.editor.savemode." +
|
||||
(nameField.getText().equals(sourceName) ? "override" : "newfile"));
|
||||
});
|
||||
Futures.addCallback(popup.getFuture(), new FutureCallback<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Boolean result) {
|
||||
if (result == Boolean.TRUE) {
|
||||
// Sanitize their input
|
||||
String name = nameField.getText().trim().replace("[^a-zA-Z0-9\\.\\- ]", "_");
|
||||
// This file is what they want
|
||||
File replayFolder;
|
||||
try {
|
||||
replayFolder = mod.getReplayFolder();
|
||||
} catch (IOException e) {
|
||||
Utils.error(LOGGER, GuiReplayEditor.this, CrashReport.makeCrashReport(e, "Getting replay folder"), null);
|
||||
return;
|
||||
}
|
||||
File targetFile = new File(replayFolder, name + ".mcpr");
|
||||
if (targetFile.exists()) {
|
||||
LOGGER.trace("Selected file already exists, asking for confirmation");
|
||||
Futures.addCallback(GuiYesNoPopup.open(GuiReplayEditor.this,
|
||||
new GuiLabel().setColor(Colors.BLACK)
|
||||
.setI18nText("replaymod.gui.replaymodified.warning1", name),
|
||||
new GuiLabel().setColor(Colors.BLACK)
|
||||
.setI18nText("replaymod.gui.replaymodified.warning2"))
|
||||
.setYesI18nLabel("gui.yes").setNoI18nLabel("gui.no")
|
||||
.getFuture(), new FutureCallback<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Boolean result) {
|
||||
if (result == Boolean.TRUE) {
|
||||
LOGGER.trace("Saving output in {}", targetFile);
|
||||
runEdit(() -> runnable.accept(targetFile));
|
||||
} else {
|
||||
LOGGER.trace("Not overwriting file {}, reopening naming dialog", targetFile);
|
||||
save(sourceName, runnable);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
LOGGER.error("During save file confirmation dialog:", t);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
LOGGER.trace("Saving output in {}", targetFile);
|
||||
runEdit(() -> runnable.accept(targetFile));
|
||||
}
|
||||
} else {
|
||||
LOGGER.trace("Saving cancelled");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
LOGGER.error("During save file dialog:", t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void runEdit(Runnable runnable) {
|
||||
popup = new GuiProcessingProgressPopup(this);
|
||||
processingStage = ProcessingStage.INITIALIZING;
|
||||
progress = 0;
|
||||
new Thread(() -> {
|
||||
LOGGER.info("Starting editing of replay");
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Throwable t) {
|
||||
CrashReport crashReport = CrashReport.makeCrashReport(t, "Editing replay file");
|
||||
mod.runLater(() -> Utils.error(LOGGER, this, crashReport, popup::close));
|
||||
return;
|
||||
}
|
||||
LOGGER.info("Successfully finished editing");
|
||||
mod.runLater(() -> {
|
||||
popup.close();
|
||||
GuiInfoPopup.open(this, "replaymod.gui.editor.progress.status.finished");
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
|
||||
public void updateProgress(ProcessingStage stage, double progress) {
|
||||
if (this.processingStage != stage) {
|
||||
LOGGER.trace("Changing state from {} to {}", this.processingStage, stage);
|
||||
}
|
||||
this.processingStage = stage;
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
public ReplayMod getMod() {
|
||||
return mod;
|
||||
}
|
||||
|
||||
public enum ProcessingStage {
|
||||
INITIALIZING("initializing", 0.01),
|
||||
WORKING("writing.raw", 0.89),
|
||||
FINALIZING("writing.final", 0.1),
|
||||
DONE("finished", 0);
|
||||
|
||||
private final String i18nKey;
|
||||
private final double progressFraction;
|
||||
|
||||
ProcessingStage(String i18nKey, double progressFraction) {
|
||||
this.i18nKey = "replaymod.gui.editor.progress.status." + i18nKey;
|
||||
this.progressFraction = progressFraction;
|
||||
}
|
||||
|
||||
public double getOverallProgress(double progress) {
|
||||
progress *= progressFraction;
|
||||
for (ProcessingStage processingStage : values()) {
|
||||
if (processingStage == this) break;
|
||||
progress += processingStage.progressFraction;
|
||||
}
|
||||
return progress;
|
||||
}
|
||||
}
|
||||
|
||||
private class ProgressFilter implements StreamFilter {
|
||||
private final long total;
|
||||
|
||||
public ProgressFilter(long total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "progress";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Studio studio, JsonObject config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(PacketStream stream) {
|
||||
updateProgress(ProcessingStage.WORKING, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPacket(PacketStream stream, PacketData data) {
|
||||
updateProgress(ProcessingStage.WORKING, (double) data.getTime() / total);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnd(PacketStream stream, long timestamp) {}
|
||||
}
|
||||
|
||||
private class GuiProcessingProgressPopup extends AbstractGuiPopup<GuiProcessingProgressPopup> {
|
||||
private final GuiProgressBar progressBar = new GuiProgressBar().setSize(300, 20);
|
||||
|
||||
{
|
||||
popup.setLayout(new VerticalLayout().setSpacing(10));
|
||||
popup.addElements(new VerticalLayout.Data(0.5),
|
||||
new GuiLabel().setI18nText("replaymod.gui.editor.progress.title").setColor(Colors.BLACK),
|
||||
new GuiLabel().setI18nText("replaymod.gui.editor.progress.pleasewait").setColor(Colors.BLACK),
|
||||
progressBar);
|
||||
}
|
||||
|
||||
public GuiProcessingProgressPopup(GuiContainer container) {
|
||||
super(container);
|
||||
open();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
||||
// Update progress bar
|
||||
progressBar.setI18nLabel(processingStage.i18nKey);
|
||||
progressBar.setProgress((float) processingStage.getOverallProgress(progress));
|
||||
|
||||
super.draw(renderer, size, renderInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
super.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GuiProcessingProgressPopup getThis() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
198
src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java
Normal file
198
src/main/java/com/replaymod/editor/gui/GuiTrimPanel.java
Normal file
@@ -0,0 +1,198 @@
|
||||
package com.replaymod.editor.gui;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.replaymod.core.utils.Utils;
|
||||
import com.replaymod.replaystudio.data.Marker;
|
||||
import com.replaymod.replaystudio.filter.ChangeTimestampFilter;
|
||||
import com.replaymod.replaystudio.filter.RemoveFilter;
|
||||
import com.replaymod.replaystudio.filter.SquashFilter;
|
||||
import com.replaymod.replaystudio.replay.ReplayFile;
|
||||
import com.replaymod.replaystudio.replay.ReplayMetaData;
|
||||
import com.replaymod.replaystudio.replay.ZipReplayFile;
|
||||
import com.replaymod.replaystudio.stream.PacketStream;
|
||||
import com.replaymod.replaystudio.studio.ReplayStudio;
|
||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||
import de.johni0702.minecraft.gui.element.GuiLabel;
|
||||
import de.johni0702.minecraft.gui.element.GuiNumberField;
|
||||
import de.johni0702.minecraft.gui.element.advanced.GuiDropdownMenu;
|
||||
import de.johni0702.minecraft.gui.layout.GridLayout;
|
||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
||||
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.crash.CrashReport;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOCase;
|
||||
import org.apache.commons.io.filefilter.SuffixFileFilter;
|
||||
import org.lwjgl.util.Dimension;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.replaymod.editor.ReplayModEditor.LOGGER;
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
public class GuiTrimPanel extends GuiPanel {
|
||||
// Special value indicating no replay files were found
|
||||
private static final File NO_REPLAY = new File(".");
|
||||
// Special value for the initial "Select Marker Keyframe" entry
|
||||
private static final Marker NO_MARKER = new Marker(); static {NO_MARKER.setTime(-1);}
|
||||
|
||||
private final GuiReplayEditor gui;
|
||||
|
||||
public final GuiDropdownMenu<File> inputReplays = new GuiDropdownMenu<File>(this)
|
||||
.setMinSize(new Dimension(200, 20)).onSelection(i -> updateSelectedReplay())
|
||||
.setToString(f -> f == NO_REPLAY ? "" : FilenameUtils.getBaseName(f.getName()));
|
||||
|
||||
public final GuiNumberField startHour = new GuiNumberField().setSize(20, 20).setMaxLength(2);
|
||||
public final GuiNumberField startMin = new GuiNumberField().setSize(20, 20).setMaxLength(2);
|
||||
public final GuiNumberField startSec = new GuiNumberField().setSize(20, 20).setMaxLength(2);
|
||||
public final GuiNumberField startMilli = new GuiNumberField().setSize(40, 20).setMaxLength(4);
|
||||
public final GuiDropdownMenu<Marker> startMarker = new GuiDropdownMenu<>();
|
||||
|
||||
public final GuiNumberField endHour = new GuiNumberField().setSize(20, 20).setMaxLength(2);
|
||||
public final GuiNumberField endMin = new GuiNumberField().setSize(20, 20).setMaxLength(2);
|
||||
public final GuiNumberField endSec = new GuiNumberField().setSize(20, 20).setMaxLength(2);
|
||||
public final GuiNumberField endMilli = new GuiNumberField().setSize(40, 20).setMaxLength(4);
|
||||
public final GuiDropdownMenu<Marker> endMarker = new GuiDropdownMenu<>();
|
||||
|
||||
public final GuiPanel timePanel = new GuiPanel(this)
|
||||
.setLayout(new GridLayout().setCellsEqualSize(false).setSpacingX(20).setSpacingY(5).setColumns(3))
|
||||
.addElements(new GridLayout.Data(0, 0.5),
|
||||
new GuiLabel().setI18nText("replaymod.gui.start"),
|
||||
new GuiPanel().setLayout(new HorizontalLayout().setSpacing(2)).addElements(
|
||||
new HorizontalLayout.Data(0.5),
|
||||
startHour, new GuiLabel().setI18nText("replaymod.gui.hours"),
|
||||
startMin, new GuiLabel().setI18nText("replaymod.gui.minutes"),
|
||||
startSec, new GuiLabel().setI18nText("replaymod.gui.seconds"),
|
||||
startMilli, new GuiLabel().setI18nText("replaymod.gui.milliseconds")
|
||||
), startMarker,
|
||||
new GuiLabel().setI18nText("replaymod.gui.end"),
|
||||
new GuiPanel().setLayout(new HorizontalLayout().setSpacing(2)).addElements(
|
||||
new HorizontalLayout.Data(0.5),
|
||||
endHour, new GuiLabel().setI18nText("replaymod.gui.hours"),
|
||||
endMin, new GuiLabel().setI18nText("replaymod.gui.minutes"),
|
||||
endSec, new GuiLabel().setI18nText("replaymod.gui.seconds"),
|
||||
endMilli, new GuiLabel().setI18nText("replaymod.gui.milliseconds")
|
||||
), endMarker);
|
||||
|
||||
public GuiTrimPanel(GuiReplayEditor gui) {
|
||||
this.gui = gui;
|
||||
|
||||
setLayout(new VerticalLayout().setSpacing(10));
|
||||
|
||||
Function<Marker, String> toString = m -> {
|
||||
if (m == NO_MARKER) {
|
||||
return I18n.format("replaymod.gui.editor.trim.marker");
|
||||
} else {
|
||||
return ofNullable(m.getName()).orElse(I18n.format("replaymod.gui.ingame.unnamedmarker"))
|
||||
+ " (" + Utils.convertSecondsToShortString(m.getTime() / 1000) + ")";
|
||||
}
|
||||
};
|
||||
startMarker.setToString(toString).setMinSize(new Dimension(100, 20)).onSelection(i ->
|
||||
onSelectedMarkerChanged(startMarker, startHour, startMin, startSec, startMilli));
|
||||
endMarker.setToString(toString).setMinSize(new Dimension(100, 20)).onSelection(i ->
|
||||
onSelectedMarkerChanged(endMarker, endHour, endMin, endSec, endMilli));
|
||||
|
||||
File[] files = null;
|
||||
try {
|
||||
File folder = gui.getMod().getReplayFolder();
|
||||
files = folder.listFiles((FileFilter) new SuffixFileFilter(".mcpr", IOCase.INSENSITIVE));
|
||||
} catch (IOException e) {
|
||||
Utils.error(LOGGER, gui, CrashReport.makeCrashReport(e, "Listing available replays"), gui.backButton::onClick);
|
||||
}
|
||||
if (files == null) {
|
||||
LOGGER.warn("Replay file list is null, has the replay folder been deleted?");
|
||||
files = new File[0];
|
||||
} else {
|
||||
LOGGER.debug("Found {} replays in the replay folder", files.length);
|
||||
}
|
||||
if (files.length == 0) {
|
||||
inputReplays.setValues(NO_REPLAY);
|
||||
} else {
|
||||
inputReplays.setValues(files);
|
||||
}
|
||||
|
||||
updateSelectedReplay();
|
||||
updateReadyState();
|
||||
|
||||
gui.saveButton.onClick(() -> {
|
||||
// Read start and end times
|
||||
int start = ((startHour.getInteger() * 60 + startMin.getInteger()) * 60 + startSec.getInteger()) * 1000 + startMilli.getInteger();
|
||||
int end = ((endHour.getInteger() * 60 + endMin.getInteger()) * 60 + endSec.getInteger()) * 1000 + endMilli.getInteger();
|
||||
// Configure offset of change timestamp filter
|
||||
ChangeTimestampFilter ctf = new ChangeTimestampFilter();
|
||||
JsonObject config = new JsonObject();
|
||||
config.addProperty("offset", -start);
|
||||
ctf.init(null, config);
|
||||
// Pass filters to save dialog
|
||||
gui.save(inputReplays.getSelectedValue(),
|
||||
new PacketStream.FilterInfo(new SquashFilter(), -1, start),
|
||||
new PacketStream.FilterInfo(ctf, start, end),
|
||||
new PacketStream.FilterInfo(new RemoveFilter(), end, -1)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private void onSelectedMarkerChanged(GuiDropdownMenu<Marker> dropdown, GuiNumberField hour, GuiNumberField min,
|
||||
GuiNumberField sec, GuiNumberField milli) {
|
||||
Marker marker = dropdown.getSelectedValue();
|
||||
if (marker != NO_MARKER) {
|
||||
int time = marker.getTime();
|
||||
setTime(time, hour, min, sec, milli);
|
||||
}
|
||||
}
|
||||
|
||||
private void setTime(int time, GuiNumberField hour, GuiNumberField min,
|
||||
GuiNumberField sec, GuiNumberField milli) {
|
||||
milli.setValue(time % 1000); time /= 1000;
|
||||
sec.setValue(time % 60); time /= 60;
|
||||
min.setValue(time % 60); time /= 60;
|
||||
hour.setValue(time);
|
||||
}
|
||||
|
||||
private void updateSelectedReplay() {
|
||||
File file = inputReplays.getSelectedValue();
|
||||
// Load markers and meta data from replay file
|
||||
ReplayMetaData metaData;
|
||||
Set<Marker> markers;
|
||||
if (file == NO_REPLAY) {
|
||||
metaData = new ReplayMetaData();
|
||||
markers = Collections.emptySet();
|
||||
} else {
|
||||
try (ReplayFile replayFile = new ZipReplayFile(new ReplayStudio(), file)) {
|
||||
metaData = replayFile.getMetaData();
|
||||
markers = replayFile.getMarkers().or(Collections.emptySet());
|
||||
LOGGER.debug("Loaded {} markers from replay {}", markers.size(), file);
|
||||
} catch (Exception e) {
|
||||
Utils.error(LOGGER, gui, CrashReport.makeCrashReport(e, "Reading markers from replay"), null);
|
||||
metaData = new ReplayMetaData();
|
||||
markers = Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
// Sort and add initial "Select Event Marker" element
|
||||
List<Marker> markerList = new ArrayList<>(markers);
|
||||
markerList.sort(Comparator.comparing(Marker::getTime));
|
||||
markerList.add(0, NO_MARKER);
|
||||
|
||||
// Set marker dropdown values
|
||||
Marker[] markerArray = markerList.stream().toArray(Marker[]::new);
|
||||
startMarker.setValues(markerArray).setSelected(0);
|
||||
endMarker.setValues(markerArray).setSelected(0);
|
||||
|
||||
// Set start and end time values
|
||||
setTime(0, startHour, startMin, startSec, startMilli);
|
||||
setTime(metaData.getDuration(), endHour, endMin, endSec, endMilli);
|
||||
}
|
||||
|
||||
private void updateReadyState() {
|
||||
gui.saveButton.setEnabled(inputReplays.getSelectedValue() != NO_REPLAY);
|
||||
}
|
||||
}
|
||||
48
src/main/java/com/replaymod/editor/handler/GuiHandler.java
Normal file
48
src/main/java/com/replaymod/editor/handler/GuiHandler.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package com.replaymod.editor.handler;
|
||||
|
||||
import com.replaymod.editor.ReplayModEditor;
|
||||
import com.replaymod.editor.gui.GuiReplayEditor;
|
||||
import de.johni0702.minecraft.gui.container.GuiScreen;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.gui.GuiMainMenu;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraftforge.client.event.GuiScreenEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
|
||||
public class GuiHandler {
|
||||
private static final int BUTTON_REPLAY_EDITOR = 17890237;
|
||||
|
||||
private final ReplayModEditor mod;
|
||||
|
||||
public GuiHandler(ReplayModEditor mod) {
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
public void register() {
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void injectIntoMainMenu(GuiScreenEvent.InitGuiEvent event) {
|
||||
if (!(event.getGui() instanceof GuiMainMenu)) {
|
||||
return;
|
||||
}
|
||||
|
||||
GuiButton button = new GuiButton(BUTTON_REPLAY_EDITOR, event.getGui().width / 2 + 2,
|
||||
event.getGui().height / 4 + 10 + 3 * 24, I18n.format("replaymod.gui.replayeditor"));
|
||||
button.width = button.width / 2 - 2;
|
||||
event.getButtonList().add(button);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onButton(GuiScreenEvent.ActionPerformedEvent.Pre event) {
|
||||
if(!event.getButton().enabled) return;
|
||||
|
||||
if (event.getGui() instanceof GuiMainMenu) {
|
||||
if (event.getButton().id == BUTTON_REPLAY_EDITOR) {
|
||||
new GuiReplayEditor(GuiScreen.wrap(event.getGui()), mod.getCore()).display();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user