Merge branch '1.8' into 1.9.4

0db1b9e Fix opening a replay multiple times by quickly double clicking (fixes #25)
9ff767b Fix spectated player having the arm model of the camera player
d82cef3 Make sure video folder exists before rendering (fixes #24)
cc65b26 Replace custom update system with forge update checker
c8b71dc Add tooltips to ingame buttons
b240f8a Fix camera path always playing from the beginning regardless of ctrl key
4805e5e Fix crash when setting two keyframes to the same time via the edit keyframe gui
18f8303 Fix crash when moving the last keyframe
fcd4c07 Add error popup when trying to play path with reversed time keyframes Update jGui
9c605ba Update jGui Fix handling of ESC key in popups (fixes #17)
721fdc4 Allow moving the keyframe timeline cursor by dragging (fixes #18)
1851e97 Allow deletion of selected keyframes by pressing the DELETE key (fixes #19)
d547098 Fix markers not being removed
b2c7faa Fix marker not being deselected when clicking anywhere else
4e6b387 Update jGui Fix popups being drawn outside of the actually visible element (fixes #21)
21bbeb2 Fix bitrate field always being active regardless of selected preset (fixes #20)
b5a6d8b Fix camera player head being visible while in third person
dd0537b Center star between resolution fields in render settings gui
e35bfca Increase spacing between rows in the render settings gui
6d0cdfb Update jGui (fixes #15)
This commit is contained in:
johni0702
2016-11-10 21:02:55 +01:00
20 changed files with 251 additions and 110 deletions

2
jGui

Submodule jGui updated: 46e47874a4...7849fb3bb7

View File

@@ -34,6 +34,7 @@ import java.util.List;
@Mod(modid = ReplayMod.MOD_ID, @Mod(modid = ReplayMod.MOD_ID,
useMetadata = true, useMetadata = true,
updateJSON = "https://raw.githubusercontent.com/ReplayMod/ReplayMod/master/versions.json",
guiFactory = "com.replaymod.core.gui.GuiFactory") guiFactory = "com.replaymod.core.gui.GuiFactory")
public class ReplayMod { public class ReplayMod {

View File

@@ -29,8 +29,7 @@ public class ReplayModExtras {
FullBrightness.class, FullBrightness.class,
HotkeyButtons.class, HotkeyButtons.class,
LocalizationExtra.class, LocalizationExtra.class,
OpenEyeExtra.class, OpenEyeExtra.class
VersionChecker.class
); );
private Logger logger; private Logger logger;

View File

@@ -1,66 +0,0 @@
package com.replaymod.extras;
import com.replaymod.core.ReplayMod;
import com.replaymod.online.ReplayModOnline;
import net.minecraft.client.gui.Gui;
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.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import java.awt.*;
import java.util.List;
public class VersionChecker implements Extra {
@Mod.Instance(ReplayModOnline.MOD_ID)
private static ReplayModOnline module;
@Override
public void register(ReplayMod mod) throws Exception {
final String currentVersion = mod.getVersion();
new Thread(new Runnable() {
@Override
public void run() {
try {
boolean upToDate = module.getApiClient().isVersionUpToDate(currentVersion);
if (!upToDate) {
MinecraftForge.EVENT_BUS.register(VersionChecker.this);
}
} catch(Exception e) {
e.printStackTrace();
}
}
}, "ReplayMod-VersionChecker").start();
}
@SubscribeEvent
public void onDrawScreen(GuiScreenEvent.DrawScreenEvent.Post event) {
if (!(event.getGui() instanceof GuiMainMenu)) {
return;
}
int width = Math.max(100, event.getGui().width / 2 - 100 - 10);
@SuppressWarnings("unchecked") List<String> lines =
event.getGui().mc.fontRendererObj.listFormattedStringToWidth(I18n.format("replaymod.gui.outdated"), width);
int maxLineWidth = 0;
for(String line : lines) {
int lineWidth = event.getGui().mc.fontRendererObj.getStringWidth(line);
if(lineWidth > maxLineWidth) {
maxLineWidth = lineWidth;
}
}
Gui.drawRect(2, 77, 5 + maxLineWidth + 3, 80 + (lines.size() * 10), 0x80FF0000);
int i = 0;
for(String line : lines) {
event.getGui().mc.fontRendererObj.drawStringWithShadow(line, 5, 80 + (i * 10), Color.WHITE.getRGB());
i++;
}
}
}

View File

@@ -11,15 +11,18 @@ import de.johni0702.minecraft.gui.element.GuiLabel;
import de.johni0702.minecraft.gui.element.GuiTextField; import de.johni0702.minecraft.gui.element.GuiTextField;
import de.johni0702.minecraft.gui.element.GuiToggleButton; import de.johni0702.minecraft.gui.element.GuiToggleButton;
import de.johni0702.minecraft.gui.element.advanced.GuiDropdownMenu; import de.johni0702.minecraft.gui.element.advanced.GuiDropdownMenu;
import de.johni0702.minecraft.gui.function.Typeable;
import de.johni0702.minecraft.gui.layout.GridLayout; import de.johni0702.minecraft.gui.layout.GridLayout;
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Colors;
import net.minecraft.client.resources.I18n; import net.minecraft.client.resources.I18n;
import org.lwjgl.input.Keyboard;
import org.lwjgl.util.ReadablePoint;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class GuiReplayCenterSearch extends AbstractGuiPopup<GuiReplayCenterSearch> { public class GuiReplayCenterSearch extends AbstractGuiPopup<GuiReplayCenterSearch> implements Typeable {
private final GuiReplayCenter replayCenter; private final GuiReplayCenter replayCenter;
private final ApiClient apiClient; private final ApiClient apiClient;
public final GuiLabel title = new GuiLabel().setI18nText("replaymod.gui.center.search.filters").setColor(Colors.BLACK); public final GuiLabel title = new GuiLabel().setI18nText("replaymod.gui.center.search.filters").setColor(Colors.BLACK);
@@ -100,4 +103,13 @@ public class GuiReplayCenterSearch extends AbstractGuiPopup<GuiReplayCenterSearc
protected GuiReplayCenterSearch getThis() { protected GuiReplayCenterSearch getThis() {
return this; return this;
} }
@Override
public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) {
if (keyCode == Keyboard.KEY_ESCAPE) {
cancelButton.onClick();
return true;
}
return false;
}
} }

View File

@@ -24,6 +24,7 @@ public abstract class AbstractTimelinePlayer {
private final Minecraft mc = Minecraft.getMinecraft(); private final Minecraft mc = Minecraft.getMinecraft();
private final ReplayHandler replayHandler; private final ReplayHandler replayHandler;
private Timeline timeline; private Timeline timeline;
protected long startOffset;
private long lastTime; private long lastTime;
private long lastTimestamp; private long lastTimestamp;
private ListenableFuture<Void> future; private ListenableFuture<Void> future;
@@ -33,6 +34,11 @@ public abstract class AbstractTimelinePlayer {
this.replayHandler = replayHandler; this.replayHandler = replayHandler;
} }
public ListenableFuture<Void> start(Timeline timeline, long from) {
startOffset = from;
return start(timeline);
}
public ListenableFuture<Void> start(Timeline timeline) { public ListenableFuture<Void> start(Timeline timeline) {
this.timeline = timeline; this.timeline = timeline;

View File

@@ -46,6 +46,6 @@ public class RealtimeTimelinePlayer extends AbstractTimelinePlayer {
@Override @Override
public long getTimePassed() { public long getTimePassed() {
return firstFrame ? 0 : System.currentTimeMillis() - startTime; return startOffset + (firstFrame ? 0 : System.currentTimeMillis() - startTime);
} }
} }

View File

@@ -8,6 +8,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.crash.CrashReport; import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory; import net.minecraft.crash.CrashReportCategory;
import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.CommandLine;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.TeeOutputStream; import org.apache.commons.io.output.TeeOutputStream;
import org.lwjgl.util.ReadableDimension; import org.lwjgl.util.ReadableDimension;
@@ -34,6 +35,7 @@ public class VideoWriter implements FrameConsumer<RGBFrame> {
this.settings = settings; this.settings = settings;
File outputFolder = settings.getOutputFile().getParentFile(); File outputFolder = settings.getOutputFile().getParentFile();
FileUtils.forceMkdir(outputFolder);
String fileName = settings.getOutputFile().getName(); String fileName = settings.getOutputFile().getName();
commandArgs = settings.getExportArguments() commandArgs = settings.getExportArguments()

View File

@@ -91,7 +91,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
}).setSize(122, 20).setSteps(110); }).setSize(122, 20).setSteps(110);
public final GuiPanel videoResolutionPanel = new GuiPanel() public final GuiPanel videoResolutionPanel = new GuiPanel()
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(2)) .setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(2))
.addElements(null, videoWidth, new GuiLabel().setText("*"), videoHeight); .addElements(new HorizontalLayout.Data(0.5), videoWidth, new GuiLabel().setText("*"), videoHeight);
public final GuiNumberField bitRateField = new GuiNumberField().setValue(10).setSize(50, 20); public final GuiNumberField bitRateField = new GuiNumberField().setValue(10).setSize(50, 20);
public final GuiDropdownMenu<String> bitRateUnit = new GuiDropdownMenu<String>() public final GuiDropdownMenu<String> bitRateUnit = new GuiDropdownMenu<String>()
@@ -130,7 +130,7 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
new GuiPanel().addElements(null, bitRateField, bitRateUnit).setLayout(new HorizontalLayout()), new GuiPanel().addElements(null, bitRateField, bitRateUnit).setLayout(new HorizontalLayout()),
frameRateSlider).setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(3)), frameRateSlider).setLayout(new HorizontalLayout(HorizontalLayout.Alignment.RIGHT).setSpacing(3)),
new GuiLabel().setI18nText("replaymod.gui.rendersettings.outputfile"), outputFileButton) new GuiLabel().setI18nText("replaymod.gui.rendersettings.outputfile"), outputFileButton)
.setLayout(new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(3)); .setLayout(new GridLayout().setCellsEqualSize(false).setColumns(2).setSpacingX(5).setSpacingY(5));
public final GuiCheckbox nametagCheckbox = new GuiCheckbox() public final GuiCheckbox nametagCheckbox = new GuiCheckbox()
.setI18nLabel("replaymod.gui.rendersettings.nametags"); .setI18nLabel("replaymod.gui.rendersettings.nametags");
@@ -284,6 +284,15 @@ public class GuiRenderSettings extends GuiScreen implements Closeable {
videoHeight.setTextColor(Colors.RED); videoHeight.setTextColor(Colors.RED);
} }
// Enable/Disable bitrate input field and dropdown
if (encodingPresetDropdown.getSelectedValue().hasBitrateSetting()) {
bitRateField.setEnabled();
bitRateUnit.setEnabled();
} else {
bitRateField.setDisabled();
bitRateUnit.setDisabled();
}
// Enable/Disable camera stabilization checkboxes // Enable/Disable camera stabilization checkboxes
switch (renderMethodDropdown.getSelectedValue()) { switch (renderMethodDropdown.getSelectedValue()) {
case CUBIC: case CUBIC:

View File

@@ -62,7 +62,7 @@ public class ReplayHandler {
*/ */
private boolean suppressCameraMovements; private boolean suppressCameraMovements;
private final Set<Marker> markers; private final List<Marker> markers;
private final GuiReplayOverlay overlay; private final GuiReplayOverlay overlay;
@@ -81,7 +81,7 @@ public class ReplayHandler {
MinecraftForge.EVENT_BUS.post(new ReplayOpenEvent.Pre(this)); MinecraftForge.EVENT_BUS.post(new ReplayOpenEvent.Pre(this));
markers = new HashSet<>(replayFile.getMarkers().or(Collections.<Marker>emptySet())); markers = new ArrayList<>(replayFile.getMarkers().or(Collections.emptySet()));
replaySender = new ReplaySender(this, replayFile, asyncMode); replaySender = new ReplaySender(this, replayFile, asyncMode);
@@ -188,9 +188,9 @@ public class ReplayHandler {
/** /**
* Returns all markers. * Returns all markers.
* When changed, {@link #saveMarkers()} should be called to save the changes. * When changed, {@link #saveMarkers()} should be called to save the changes.
* @return Set of markers * @return Collection of markers in no particular order
*/ */
public Set<Marker> getMarkers() { public Collection<Marker> getMarkers() {
return markers; return markers;
} }
@@ -199,7 +199,7 @@ public class ReplayHandler {
*/ */
public void saveMarkers() { public void saveMarkers() {
try { try {
replayFile.writeMarkers(markers); replayFile.writeMarkers(new HashSet<>(markers));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }

View File

@@ -10,6 +10,7 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import net.minecraft.block.material.Material; import net.minecraft.block.material.Material;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.AbstractClientPlayer;
import net.minecraft.client.entity.EntityPlayerSP; import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.settings.KeyBinding; import net.minecraft.client.settings.KeyBinding;
@@ -237,6 +238,14 @@ public class CameraEntity extends EntityPlayerSP {
return ReplayModReplay.instance.getReplayHandler().isCameraView(); // Make sure we're treated as spectator return ReplayModReplay.instance.getReplayHandler().isCameraView(); // Make sure we're treated as spectator
} }
@Override
public boolean shouldRenderInPass(int pass) {
// Never render the camera
// This is necessary to hide the player head in third person mode and to not
// cause any unwanted shadows when rendering with shaders.
return false;
}
@Override @Override
public ResourceLocation getLocationSkin() { public ResourceLocation getLocationSkin() {
Entity view = mc.getRenderViewEntity(); Entity view = mc.getRenderViewEntity();
@@ -246,6 +255,15 @@ public class CameraEntity extends EntityPlayerSP {
return super.getLocationSkin(); return super.getLocationSkin();
} }
@Override
public String getSkinType() {
Entity view = mc.getRenderViewEntity();
if (view != this && view instanceof AbstractClientPlayer) {
return ((AbstractClientPlayer) view).getSkinType();
}
return super.getSkinType();
}
@Override @Override
public float getSwingProgress(float renderPartialTicks) { public float getSwingProgress(float renderPartialTicks) {
Entity view = mc.getRenderViewEntity(); Entity view = mc.getRenderViewEntity();

View File

@@ -5,14 +5,17 @@ import com.replaymod.replay.ReplayHandler;
import de.johni0702.minecraft.gui.container.GuiContainer; import de.johni0702.minecraft.gui.container.GuiContainer;
import de.johni0702.minecraft.gui.container.GuiPanel; import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.element.*; import de.johni0702.minecraft.gui.element.*;
import de.johni0702.minecraft.gui.function.Typeable;
import de.johni0702.minecraft.gui.layout.GridLayout; import de.johni0702.minecraft.gui.layout.GridLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Colors;
import com.replaymod.replaystudio.data.Marker; import com.replaymod.replaystudio.data.Marker;
import org.lwjgl.input.Keyboard;
import org.lwjgl.util.ReadablePoint;
public class GuiEditMarkerPopup extends AbstractGuiPopup<GuiEditMarkerPopup> { public class GuiEditMarkerPopup extends AbstractGuiPopup<GuiEditMarkerPopup> implements Typeable {
private final ReplayHandler replayHandler; private final ReplayHandler replayHandler;
private final Marker marker; private final Marker marker;
@@ -107,4 +110,13 @@ public class GuiEditMarkerPopup extends AbstractGuiPopup<GuiEditMarkerPopup> {
protected GuiEditMarkerPopup getThis() { protected GuiEditMarkerPopup getThis() {
return this; return this;
} }
@Override
public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) {
if (keyCode == Keyboard.KEY_ESCAPE) {
cancelButton.onClick();
return true;
}
return false;
}
} }

View File

@@ -133,6 +133,8 @@ public class GuiMarkerTimeline extends AbstractGuiTimeline<GuiMarkerTimeline> im
replayHandler.doJump(marker.getTime(), false); replayHandler.doJump(marker.getTime(), false);
} }
return true; return true;
} else {
selectedMarker = null;
} }
return super.mouseClick(position, button); return super.mouseClick(position, button);
} }

View File

@@ -7,8 +7,10 @@ import de.johni0702.minecraft.gui.GuiRenderer;
import de.johni0702.minecraft.gui.RenderInfo; import de.johni0702.minecraft.gui.RenderInfo;
import de.johni0702.minecraft.gui.container.AbstractGuiOverlay; import de.johni0702.minecraft.gui.container.AbstractGuiOverlay;
import de.johni0702.minecraft.gui.container.GuiPanel; import de.johni0702.minecraft.gui.container.GuiPanel;
import de.johni0702.minecraft.gui.element.GuiElement;
import de.johni0702.minecraft.gui.element.GuiSlider; import de.johni0702.minecraft.gui.element.GuiSlider;
import de.johni0702.minecraft.gui.element.GuiTexturedButton; import de.johni0702.minecraft.gui.element.GuiTexturedButton;
import de.johni0702.minecraft.gui.element.GuiTooltip;
import de.johni0702.minecraft.gui.element.advanced.IGuiTimeline; import de.johni0702.minecraft.gui.element.advanced.IGuiTimeline;
import de.johni0702.minecraft.gui.layout.CustomLayout; import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout;
@@ -27,8 +29,20 @@ public class GuiReplayOverlay extends AbstractGuiOverlay<GuiReplayOverlay> {
public final GuiPanel topPanel = new GuiPanel(this) public final GuiPanel topPanel = new GuiPanel(this)
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.LEFT).setSpacing(5)); .setLayout(new HorizontalLayout(HorizontalLayout.Alignment.LEFT).setSpacing(5));
public final GuiTexturedButton playPauseButton = new GuiTexturedButton().setSize(20, 20) public final GuiTexturedButton playPauseButton = new GuiTexturedButton() {
.setTexture(ReplayMod.TEXTURE, TEXTURE_SIZE); @Override
public GuiElement getTooltip(RenderInfo renderInfo) {
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
if (tooltip != null) {
if (getTextureNormal().getY() == 0) { // Play button
tooltip.setI18nText("replaymod.gui.ingame.menu.unpause");
} else { // Pause button
tooltip.setI18nText("replaymod.gui.ingame.menu.pause");
}
}
return tooltip;
}
}.setSize(20, 20).setTexture(ReplayMod.TEXTURE, TEXTURE_SIZE).setTooltip(new GuiTooltip());
public final GuiSlider speedSlider = new GuiSlider().setSize(100, 20).setSteps(37); // 0.0 is not included public final GuiSlider speedSlider = new GuiSlider().setSize(100, 20).setSteps(37); // 0.0 is not included
public final GuiMarkerTimeline timeline; public final GuiMarkerTimeline timeline;

View File

@@ -101,6 +101,8 @@ public class GuiReplayViewer extends GuiScreen {
}).onSelectionDoubleClicked(() -> { }).onSelectionDoubleClicked(() -> {
if (this.loadButton.isEnabled()) { if (this.loadButton.isEnabled()) {
this.loadButton.onClick(); this.loadButton.onClick();
// Disable load button to prevent the player from opening the replay twice at the same time
this.loadButton.setDisabled();
} }
}).setDrawShadow(true).setDrawSlider(true); }).setDrawShadow(true).setDrawSlider(true);

View File

@@ -13,16 +13,20 @@ import de.johni0702.minecraft.gui.element.GuiButton;
import de.johni0702.minecraft.gui.element.GuiLabel; import de.johni0702.minecraft.gui.element.GuiLabel;
import de.johni0702.minecraft.gui.element.GuiNumberField; import de.johni0702.minecraft.gui.element.GuiNumberField;
import de.johni0702.minecraft.gui.element.IGuiLabel; import de.johni0702.minecraft.gui.element.IGuiLabel;
import de.johni0702.minecraft.gui.function.Typeable;
import de.johni0702.minecraft.gui.layout.GridLayout; import de.johni0702.minecraft.gui.layout.GridLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; import de.johni0702.minecraft.gui.popup.AbstractGuiPopup;
import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Colors;
import de.johni0702.minecraft.gui.utils.Consumer;
import org.apache.commons.lang3.tuple.Triple; import org.apache.commons.lang3.tuple.Triple;
import org.lwjgl.input.Keyboard;
import org.lwjgl.util.ReadablePoint;
import static de.johni0702.minecraft.gui.utils.Utils.link; import static de.johni0702.minecraft.gui.utils.Utils.link;
public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends AbstractGuiPopup<T> { public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends AbstractGuiPopup<T> implements Typeable {
private static GuiNumberField newGuiNumberField() { private static GuiNumberField newGuiNumberField() {
return new GuiNumberField().setPrecision(0).setValidateOnFocusChange(true); return new GuiNumberField().setPrecision(0).setValidateOnFocusChange(true);
} }
@@ -68,9 +72,10 @@ public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends Abst
this.path = path; this.path = path;
long time = keyframe.getTime(); long time = keyframe.getTime();
timeMinField.setValue(time / 1000 / 60); Consumer<String> updateSaveButtonState = s -> saveButton.setEnabled(canSave());
timeSecField.setValue(time / 1000 % 60); timeMinField.setValue(time / 1000 / 60).onTextChanged(updateSaveButtonState);
timeMSecField.setValue(time % 1000); timeSecField.setValue(time / 1000 % 60).onTextChanged(updateSaveButtonState);
timeMSecField.setValue(time % 1000).onTextChanged(updateSaveButtonState);
title.setI18nText("replaymod.gui.editkeyframe.title." + type); title.setI18nText("replaymod.gui.editkeyframe.title." + type);
saveButton.onClick(() -> { saveButton.onClick(() -> {
@@ -84,6 +89,23 @@ public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends Abst
}); });
} }
private boolean canSave() {
long newTime = (timeMinField.getInteger() * 60 + timeSecField.getInteger()) * 1000 + timeMSecField.getInteger();
if (newTime != keyframe.getTime() && path.getKeyframe(newTime) != null) {
return false;
}
return true;
}
@Override
public boolean typeKey(ReadablePoint mousePosition, int keyCode, char keyChar, boolean ctrlDown, boolean shiftDown) {
if (keyCode == Keyboard.KEY_ESCAPE) {
cancelButton.onClick();
return true;
}
return false;
}
@Override @Override
public void open() { public void open() {
super.open(); super.open();

View File

@@ -121,10 +121,31 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline
|| !segment.getInterpolator().getKeyframeProperties().contains(SpectatorProperty.PROPERTY)) { || !segment.getInterpolator().getKeyframeProperties().contains(SpectatorProperty.PROPERTY)) {
continue; // Not a spectator segment continue; // Not a spectator segment
} }
drawQuadOnSegment(renderer, visibleWidth, segment, BORDER_TOP + 1, 0xFF0088FF);
}
// Draw red quads on time path segments that would require time going backwards
for (PathSegment segment : mod.getCurrentTimeline().getPaths().get(GuiPathing.TIME_PATH).getSegments()) {
long startTimestamp = segment.getStartKeyframe().getValue(TimestampProperty.PROPERTY).orElseThrow(IllegalStateException::new);
long endTimestamp = segment.getEndKeyframe().getValue(TimestampProperty.PROPERTY).orElseThrow(IllegalStateException::new);
if (endTimestamp >= startTimestamp) {
continue; // All is fine, time is not moving backwards
}
drawQuadOnSegment(renderer, visibleWidth, segment, BORDER_TOP + KEYFRAME_SIZE + 1, 0xFFFF0000);
}
super.drawTimelineCursor(renderer, size);
}
private void drawQuadOnSegment(GuiRenderer renderer, int visibleWidth, PathSegment segment, int y, int color) {
int startTime = getOffset();
int visibleTime = (int) (getZoom() * getLength());
int endTime = getOffset() + visibleTime;
long startFrameTime = segment.getStartKeyframe().getTime(); long startFrameTime = segment.getStartKeyframe().getTime();
long endFrameTime = segment.getEndKeyframe().getTime(); long endFrameTime = segment.getEndKeyframe().getTime();
if (startFrameTime >= endTime || endFrameTime <= startTime) { if (startFrameTime >= endTime || endFrameTime <= startTime) {
continue; // Segment out of display range return; // Segment out of display range
} }
double relativeStart = startFrameTime - startTime; double relativeStart = startFrameTime - startTime;
@@ -132,13 +153,10 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline
int startX = BORDER_LEFT + Math.max(0, (int) (relativeStart / visibleTime * visibleWidth) + KEYFRAME_SIZE / 2 + 1); int startX = BORDER_LEFT + Math.max(0, (int) (relativeStart / visibleTime * visibleWidth) + KEYFRAME_SIZE / 2 + 1);
int endX = BORDER_LEFT + Math.min(visibleWidth, (int) (relativeEnd / visibleTime * visibleWidth) - KEYFRAME_SIZE / 2); int endX = BORDER_LEFT + Math.min(visibleWidth, (int) (relativeEnd / visibleTime * visibleWidth) - KEYFRAME_SIZE / 2);
if (startX < endX) { if (startX < endX) {
renderer.drawRect(startX + 1, BORDER_TOP + 1, endX - startX - 2, KEYFRAME_SIZE - 2, 0xFF0088FF); renderer.drawRect(startX + 1, y, endX - startX - 2, KEYFRAME_SIZE - 2, color);
} }
} }
super.drawTimelineCursor(renderer, size);
}
/** /**
* Returns the keyframe at the specified position. * Returns the keyframe at the specified position.
* @param position The raw position * @param position The raw position
@@ -243,6 +261,15 @@ public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline
@Override @Override
public boolean mouseDrag(ReadablePoint position, int button, long timeSinceLastCall) { public boolean mouseDrag(ReadablePoint position, int button, long timeSinceLastCall) {
if (!dragging) { if (!dragging) {
if (button == 0) {
// Left click, the user might try to move the cursor by clicking and holding
int time = getTimeAt(position.getX(), position.getY());
if (time != -1) {
// and they are still on the timeline, so update the time appropriately
setCursorPosition(time);
return true;
}
}
return false; return false;
} }

View File

@@ -38,6 +38,7 @@ import de.johni0702.minecraft.gui.layout.CustomLayout;
import de.johni0702.minecraft.gui.layout.HorizontalLayout; import de.johni0702.minecraft.gui.layout.HorizontalLayout;
import de.johni0702.minecraft.gui.layout.VerticalLayout; import de.johni0702.minecraft.gui.layout.VerticalLayout;
import de.johni0702.minecraft.gui.popup.AbstractGuiPopup; 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.popup.GuiYesNoPopup;
import de.johni0702.minecraft.gui.utils.Colors; import de.johni0702.minecraft.gui.utils.Colors;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
@@ -69,22 +70,69 @@ public class GuiPathing {
private static final Logger logger = LogManager.getLogger(); private static final Logger logger = LogManager.getLogger();
public final GuiTexturedButton playPauseButton = new GuiTexturedButton().setSize(20, 20) public final GuiTexturedButton playPauseButton = new GuiTexturedButton() {
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE); @Override
public GuiElement getTooltip(RenderInfo renderInfo) {
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
if (tooltip != null) {
if (player.isActive()) {
tooltip.setI18nText("replaymod.gui.ingame.menu.pausepath");
} else if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)) {
tooltip.setI18nText("replaymod.gui.ingame.menu.playpathfromstart");
} else {
tooltip.setI18nText("replaymod.gui.ingame.menu.playpath");
}
}
return tooltip;
}
}.setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTooltip(new GuiTooltip());
public final GuiTexturedButton renderButton = new GuiTexturedButton().onClick(new Runnable() { public final GuiTexturedButton renderButton = new GuiTexturedButton().onClick(new Runnable() {
@Override @Override
public void run() { public void run() {
preparePathsForPlayback(); if (!preparePathsForPlayback()) return;
new GuiRenderSettings(replayHandler, mod.getCurrentTimeline()).display(); new GuiRenderSettings(replayHandler, mod.getCurrentTimeline()).display();
} }
}).setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 0); }).setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 0)
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.renderpath"));
public final GuiTexturedButton positionKeyframeButton = new GuiTexturedButton().setSize(20, 20) public final GuiTexturedButton positionKeyframeButton = new GuiTexturedButton() {
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE); @Override
public GuiElement getTooltip(RenderInfo renderInfo) {
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
if (tooltip != null) {
if (getTextureNormal().getY() == 40) { // Add keyframe
if (getTextureNormal().getX() == 0) { // Position
tooltip.setI18nText("replaymod.gui.ingame.menu.addposkeyframe");
} else { // Spectator
tooltip.setI18nText("replaymod.gui.ingame.menu.addspeckeyframe");
}
} else { // Remove keyframe
if (getTextureNormal().getX() == 0) { // Position
tooltip.setI18nText("replaymod.gui.ingame.menu.removeposkeyframe");
} else { // Spectator
tooltip.setI18nText("replaymod.gui.ingame.menu.removespeckeyframe");
}
}
}
return tooltip;
}
}.setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTooltip(new GuiTooltip());
public final GuiTexturedButton timeKeyframeButton = new GuiTexturedButton().setSize(20, 20) public final GuiTexturedButton timeKeyframeButton = new GuiTexturedButton() {
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE); @Override
public GuiElement getTooltip(RenderInfo renderInfo) {
GuiTooltip tooltip = (GuiTooltip) super.getTooltip(renderInfo);
if (tooltip != null) {
if (getTextureNormal().getY() == 80) { // Add time keyframe
tooltip.setI18nText("replaymod.gui.ingame.menu.addtimekeyframe");
} else { // Remove time keyframe
tooltip.setI18nText("replaymod.gui.ingame.menu.removetimekeyframe");
}
}
return tooltip;
}
}.setSize(20, 20).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTooltip(new GuiTooltip());
public final GuiKeyframeTimeline timeline = new GuiKeyframeTimeline(this){ public final GuiKeyframeTimeline timeline = new GuiKeyframeTimeline(this){
@Override @Override
@@ -113,14 +161,16 @@ public class GuiPathing {
public void run() { public void run() {
zoomTimeline(2d / 3d); zoomTimeline(2d / 3d);
} }
}).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 20); }).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 20)
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.zoomin"));
public final GuiTexturedButton zoomOutButton = new GuiTexturedButton().setSize(9, 9).onClick(new Runnable() { public final GuiTexturedButton zoomOutButton = new GuiTexturedButton().setSize(9, 9).onClick(new Runnable() {
@Override @Override
public void run() { public void run() {
zoomTimeline(3d / 2d); zoomTimeline(3d / 2d);
} }
}).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 30); }).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 30)
.setTooltip(new GuiTooltip().setI18nText("replaymod.gui.ingame.menu.zoomout"));
public final GuiPanel zoomButtonPanel = new GuiPanel() public final GuiPanel zoomButtonPanel = new GuiPanel()
.setLayout(new VerticalLayout(VerticalLayout.Alignment.CENTER).setSpacing(2)) .setLayout(new VerticalLayout(VerticalLayout.Alignment.CENTER).setSpacing(2))
@@ -231,10 +281,12 @@ public class GuiPathing {
Timeline timeline = mod.getCurrentTimeline(); Timeline timeline = mod.getCurrentTimeline();
Path timePath = timeline.getPaths().get(TIME_PATH); Path timePath = timeline.getPaths().get(TIME_PATH);
preparePathsForPlayback(); if (!preparePathsForPlayback()) return;
timePath.setActive(!Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)); timePath.setActive(!Keyboard.isKeyDown(Keyboard.KEY_LSHIFT));
ListenableFuture<Void> future = player.start(timeline); // Start from cursor time unless the control key is pressed (then start from beginning)
int startTime = Keyboard.isKeyDown(Keyboard.KEY_LCONTROL)? 0 : GuiPathing.this.timeline.getCursorPosition();
ListenableFuture<Void> future = player.start(timeline, startTime);
overlay.setCloseable(false); overlay.setCloseable(false);
overlay.setMouseVisible(true); overlay.setMouseVisible(true);
Futures.addCallback(future, new FutureCallback<Void>() { Futures.addCallback(future, new FutureCallback<Void>() {
@@ -403,6 +455,15 @@ public class GuiPathing {
}); });
}); });
core.getKeyBindingRegistry().registerRaw(Keyboard.KEY_DELETE, () -> {
if (!overlay.isVisible()) {
return;
}
if (mod.getSelectedKeyframe() != null) {
updateKeyframe(mod.getSelectedKeyframe().getValue(TimestampProperty.PROPERTY).isPresent());
}
});
// Start loading entity tracker // Start loading entity tracker
entityTrackerFuture = SettableFuture.create(); entityTrackerFuture = SettableFuture.create();
new Thread(() -> { new Thread(() -> {
@@ -429,10 +490,27 @@ public class GuiPathing {
}).start(); }).start();
} }
private void preparePathsForPlayback() { private boolean preparePathsForPlayback() {
Timeline timeline = mod.getCurrentTimeline(); Timeline timeline = mod.getCurrentTimeline();
timeline.getPaths().get(TIME_PATH).updateAll(); timeline.getPaths().get(TIME_PATH).updateAll();
timeline.getPaths().get(POSITION_PATH).updateAll(); timeline.getPaths().get(POSITION_PATH).updateAll();
// Make sure time keyframes's values are monotonically increasing
int lastTime = 0;
for (Keyframe keyframe : timeline.getPaths().get(TIME_PATH).getKeyframes()) {
int time = keyframe.getValue(TimestampProperty.PROPERTY).orElseThrow(IllegalStateException::new);
if (time < lastTime) {
// We are going backwards in time
GuiInfoPopup.open(replayHandler.getOverlay(),
"replaymod.error.negativetime1",
"replaymod.error.negativetime2",
"replaymod.error.negativetime3");
return false;
}
lastTime = time;
}
return true;
} }
public void zoomTimeline(double factor) { public void zoomTimeline(double factor) {

View File

@@ -485,6 +485,9 @@ replaymod.gui.objects=Custom Objects
#Errors #Errors
replaymod.error.unknownrestriction1=This replay cannot be played with your current version. replaymod.error.unknownrestriction1=This replay cannot be played with your current version.
replaymod.error.unknownrestriction2=It tried to enforce %s which is unknown. replaymod.error.unknownrestriction2=It tried to enforce %s which is unknown.
replaymod.error.negativetime1=Some of your time keyframes are out of order.
replaymod.error.negativetime2=Going backwards in time is not supported.
replaymod.error.negativetime3=The invalid parts are marked in red.
#Replay Mod Incompatibility Warning #Replay Mod Incompatibility Warning
replaymod.gui.modwarning.title=Incompatibility detected replaymod.gui.modwarning.title=Incompatibility detected