Merge branch '1.8' into 1.8.9
91573d9Update jGui, ReplayStudio and Translationsd71358bAdd confirmation dialog before overwriting path presets (fixes #65)1a55983Move addCallback method from integration test Utils to main Utils18c5bcdBe more cooperative with other mods on the ingame menu (fixes #42)c054fe8Register keys for simplepathing only once (fixes #63)f13297cFix default interpolator handling in keyframe gui and after loading (fixes #64)391f304Show error popup if entity tracker fails to load0e0eaaaFix entity tracker being set even if it failed loading (fixes #50)a34bbbcFix NPE when spectating a player without a camera entity60879fbChange local class to be an inner class because Srg2Source breaks with itdfafbecLoad translations from github repo, only reload language not all resource packs8a6ec51Add missing tooltips to player overview and various missing chat messages5677fc5Re-add warning to replay editor748a91eFix missing tooltips on fav/like/dislike buttons in replay centeree24866Fix upload replay button not being updated when name/tags changedba085cMove translations into separate repo3f0e3e7Fix NPE when receiving Replay|Restrict messages (fixes #16 GH)40e1d85Fix crash when saving the last keyframe in the edit gui (fixes #61)03aada1Update Mixin to 0.6.8 (fixes #9 GH)0c1dc65Fix interpolator being lost when moving keyframe to the end (fixes #62)fcbbbc9Add integration test. Run with ./gradlew runIntegrationTestfe6ded0Fix movement of keyframe via GuiEditKeyframe not updating selected keyframef58fa8fFix Login GUI not respecting other GUIs opened on startup4fc3a31Fix OpenEye being installed into working dir instead of mcDataDir
This commit is contained in:
@@ -14,6 +14,7 @@ import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
@Mod(modid = ReplayModSimplePathing.MOD_ID,
|
||||
version = "@MOD_VERSION@",
|
||||
@@ -22,6 +23,9 @@ import org.apache.logging.log4j.Logger;
|
||||
public class ReplayModSimplePathing {
|
||||
public static final String MOD_ID = "replaymod-simplepathing";
|
||||
|
||||
@Mod.Instance(MOD_ID)
|
||||
public static ReplayModSimplePathing instance;
|
||||
|
||||
private ReplayMod core;
|
||||
|
||||
public static Logger LOGGER;
|
||||
@@ -39,11 +43,24 @@ public class ReplayModSimplePathing {
|
||||
|
||||
PathPreview pathPreview = new PathPreview(this);
|
||||
pathPreview.register();
|
||||
|
||||
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.keyframerepository", Keyboard.KEY_X, () -> {
|
||||
if (guiPathing != null) guiPathing.keyframeRepoButtonPressed();
|
||||
});
|
||||
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.clearkeyframes", Keyboard.KEY_C, () -> {
|
||||
if (guiPathing != null) guiPathing.clearKeyframesButtonPressed();
|
||||
});
|
||||
core.getKeyBindingRegistry().registerRepeatedKeyBinding("replaymod.input.synctimeline", Keyboard.KEY_V, () -> {
|
||||
if (guiPathing != null) guiPathing.syncTimeButtonPressed();
|
||||
});
|
||||
core.getKeyBindingRegistry().registerRaw(Keyboard.KEY_DELETE, () -> {
|
||||
if (guiPathing != null) guiPathing.deleteButtonPressed();
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void postReplayOpen(ReplayOpenEvent.Post event) {
|
||||
currentTimeline = new SPTimeline();
|
||||
clearCurrentTimeline();
|
||||
guiPathing = new GuiPathing(core, this, event.getReplayHandler());
|
||||
}
|
||||
|
||||
@@ -82,11 +99,11 @@ public class ReplayModSimplePathing {
|
||||
public void setCurrentTimeline(SPTimeline newTimeline) {
|
||||
selectedPath = null;
|
||||
currentTimeline = newTimeline;
|
||||
updateDefaultInterpolatorType();
|
||||
}
|
||||
|
||||
public void clearCurrentTimeline() {
|
||||
setCurrentTimeline(new SPTimeline());
|
||||
updateDefaultInterpolatorType();
|
||||
}
|
||||
|
||||
public SPTimeline getCurrentTimeline() {
|
||||
|
||||
@@ -71,7 +71,7 @@ public class SPTimeline implements PathingRegistry {
|
||||
|
||||
@Getter
|
||||
private EntityPositionTracker entityTracker;
|
||||
private InterpolatorType defaultInterpolatorType = InterpolatorType.fromString("invalid string returns default");
|
||||
private InterpolatorType defaultInterpolatorType;
|
||||
|
||||
public SPTimeline() {
|
||||
this(createInitialTimeline());
|
||||
@@ -340,13 +340,15 @@ public class SPTimeline implements PathingRegistry {
|
||||
Optional<Interpolator> firstInterpolator =
|
||||
path.getSegments().stream().findFirst().map(PathSegment::getInterpolator);
|
||||
|
||||
// The interpolator of the previous segment
|
||||
Optional<Interpolator> interpolatorBefore =
|
||||
path.getSegments().stream().filter(s -> s.getEndKeyframe() == keyframe).findFirst().map(PathSegment::getInterpolator);
|
||||
|
||||
// The interpolator of the following segment
|
||||
Optional<Interpolator> interpolatorAfter =
|
||||
path.getSegments().stream().filter(s -> s.getStartKeyframe() == keyframe).findFirst().map(PathSegment::getInterpolator);
|
||||
// The interpolator that will be lost once we remove the old keyframe and has to be restored afterwards
|
||||
Optional<Interpolator> lostInterpolator = path.getSegments().stream().filter(s -> {
|
||||
// If this is the last keyframe,
|
||||
if (Iterables.getLast(path.getKeyframes()) == keyframe) {
|
||||
return s.getEndKeyframe() == keyframe; // the previous interpolator will be lost
|
||||
} else { // otherwise
|
||||
return s.getStartKeyframe() == keyframe; // the following interpolator will be lost
|
||||
}
|
||||
}).findFirst().map(PathSegment::getInterpolator);
|
||||
|
||||
// First remove the old keyframe
|
||||
Change removeChange = create(path, keyframe);
|
||||
@@ -369,18 +371,27 @@ public class SPTimeline implements PathingRegistry {
|
||||
Keyframe newKf = path.getKeyframe(newTime);
|
||||
if (Iterables.getLast(path.getKeyframes()) != newKf) { // Unless this is the last keyframe
|
||||
// the interpolator of the following segment has been lost and needs to be restored
|
||||
restoreInterpolatorChange = interpolatorAfter.<Change>flatMap(interpolator ->
|
||||
restoreInterpolatorChange = lostInterpolator.<Change>flatMap(interpolator ->
|
||||
path.getSegments().stream().filter(s -> s.getStartKeyframe() == newKf).findFirst().map(segment ->
|
||||
SetInterpolator.create(segment, interpolator)
|
||||
)
|
||||
).orElseGet(CombinedChange::create);
|
||||
} else { // If it is the last keyframe however,
|
||||
// the interpolator of the previous segment has been lost and needs to be restored
|
||||
restoreInterpolatorChange = interpolatorBefore.<Change>flatMap(interpolator ->
|
||||
path.getSegments().stream().filter(s -> s.getEndKeyframe() == newKf).findFirst().map(segment ->
|
||||
SetInterpolator.create(segment, interpolator)
|
||||
)
|
||||
).orElseGet(CombinedChange::create);
|
||||
restoreInterpolatorChange = path.getSegments().stream().filter(s -> s.getEndKeyframe() == newKf)
|
||||
.findFirst().flatMap(segment -> lostInterpolator.map(interpolator -> {
|
||||
// additionally, if the interpolation of this keyframe was set to explicit, that property
|
||||
// has to be transferred to the start keyframe of the new segment
|
||||
if (newKf.getValue(ExplicitInterpolationProperty.PROPERTY).isPresent()) {
|
||||
return CombinedChange.create(
|
||||
SetInterpolator.create(segment, interpolator),
|
||||
UpdateKeyframeProperties.create(path, segment.getStartKeyframe())
|
||||
.setValue(ExplicitInterpolationProperty.PROPERTY, ObjectUtils.NULL).done()
|
||||
);
|
||||
} else {
|
||||
return SetInterpolator.create(segment, interpolator);
|
||||
}
|
||||
})).orElseGet(CombinedChange::create);
|
||||
}
|
||||
restoreInterpolatorChange.apply(timeline);
|
||||
|
||||
|
||||
@@ -109,6 +109,9 @@ public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends Abst
|
||||
if (newTime != time) {
|
||||
change = CombinedChange.createFromApplied(change,
|
||||
gui.getMod().getCurrentTimeline().moveKeyframe(path, time, newTime));
|
||||
if (gui.getMod().getSelectedPath() == path && gui.getMod().getSelectedTime() == time) {
|
||||
gui.getMod().setSelected(path, newTime);
|
||||
}
|
||||
}
|
||||
gui.getMod().getCurrentTimeline().getTimeline().pushChange(change);
|
||||
close();
|
||||
@@ -252,6 +255,10 @@ public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends Abst
|
||||
xField.getDouble(), yField.getDouble(), zField.getDouble(),
|
||||
yawField.getFloat(), pitchField.getFloat(), rollField.getFloat()
|
||||
);
|
||||
if (interpolationPanel.getSettingsPanel() == null) {
|
||||
// The last keyframe doesn't have interpolator settings because there is no segment following it
|
||||
return positionChange;
|
||||
}
|
||||
Interpolator interpolator = interpolationPanel.getSettingsPanel().createInterpolator();
|
||||
if (interpolationPanel.getInterpolatorType() == InterpolatorType.DEFAULT) {
|
||||
return CombinedChange.createFromApplied(positionChange, timeline.setInterpolatorToDefault(time),
|
||||
@@ -303,6 +310,7 @@ public abstract class GuiEditKeyframe<T extends GuiEditKeyframe<T>> extends Abst
|
||||
dropdown.setSelected(type); // trigger the callback once to display settings panel
|
||||
} else {
|
||||
setSettingsPanel(InterpolatorType.DEFAULT);
|
||||
type = InterpolatorType.DEFAULT;
|
||||
}
|
||||
if (getInterpolatorTypeNoDefault(type).getInterpolatorClass().isInstance(interpolator)) {
|
||||
//noinspection unchecked
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.replaymod.simplepathing.gui;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.core.utils.Utils;
|
||||
import com.replaymod.pathing.gui.GuiKeyframeRepository;
|
||||
import com.replaymod.pathing.player.RealtimeTimelinePlayer;
|
||||
import com.replaymod.pathing.properties.CameraProperties;
|
||||
@@ -255,6 +257,7 @@ public class GuiPathing {
|
||||
}
|
||||
};
|
||||
|
||||
private final ReplayMod core;
|
||||
private final ReplayModSimplePathing mod;
|
||||
private final ReplayHandler replayHandler;
|
||||
private final RealtimeTimelinePlayer player;
|
||||
@@ -264,6 +267,7 @@ public class GuiPathing {
|
||||
private SettableFuture<Void> entityTrackerFuture;
|
||||
|
||||
public GuiPathing(final ReplayMod core, final ReplayModSimplePathing mod, final ReplayHandler replayHandler) {
|
||||
this.core = core;
|
||||
this.mod = mod;
|
||||
this.replayHandler = replayHandler;
|
||||
this.player = new RealtimeTimelinePlayer(replayHandler);
|
||||
@@ -300,9 +304,15 @@ public class GuiPathing {
|
||||
ListenableFuture<Void> future = player.start(mod.getCurrentTimeline().getTimeline(), startTime);
|
||||
overlay.setCloseable(false);
|
||||
overlay.setMouseVisible(true);
|
||||
core.printInfoToChat("replaymod.chat.pathstarted");
|
||||
Futures.addCallback(future, new FutureCallback<Void>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Void result) {
|
||||
if (future.isCancelled()) {
|
||||
core.printInfoToChat("replaymod.chat.pathinterrupted");
|
||||
} else {
|
||||
core.printInfoToChat("replaymod.chat.pathfinished");
|
||||
}
|
||||
overlay.setCloseable(true);
|
||||
timePath.setActive(true);
|
||||
}
|
||||
@@ -393,94 +403,90 @@ public class GuiPathing {
|
||||
}
|
||||
});
|
||||
|
||||
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.keyframerepository", Keyboard.KEY_X, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!overlay.isVisible()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
GuiKeyframeRepository gui = new GuiKeyframeRepository(
|
||||
mod.getCurrentTimeline(), replayHandler.getReplayFile(), mod.getCurrentTimeline().getTimeline());
|
||||
Futures.addCallback(gui.getFuture(), new FutureCallback<Timeline>() {
|
||||
@Override
|
||||
public void onSuccess(Timeline result) {
|
||||
if (result != null) {
|
||||
mod.setCurrentTimeline(new SPTimeline(result));
|
||||
}
|
||||
}
|
||||
startLoadingEntityTracker();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
t.printStackTrace();
|
||||
core.printWarningToChat("Error loading timeline: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
gui.display();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
core.printWarningToChat("Error loading timeline: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
core.getKeyBindingRegistry().registerKeyBinding("replaymod.input.clearkeyframes", Keyboard.KEY_C, () -> {
|
||||
GuiYesNoPopup popup = GuiYesNoPopup.open(overlay,
|
||||
new GuiLabel().setI18nText("replaymod.gui.clearcallback.title").setColor(Colors.BLACK)
|
||||
).setYesI18nLabel("gui.yes").setNoI18nLabel("gui.no");
|
||||
Futures.addCallback(popup.getFuture(), new FutureCallback<Boolean>() {
|
||||
public void keyframeRepoButtonPressed() {
|
||||
try {
|
||||
GuiKeyframeRepository gui = new GuiKeyframeRepository(
|
||||
mod.getCurrentTimeline(), replayHandler.getReplayFile(), mod.getCurrentTimeline().getTimeline());
|
||||
Futures.addCallback(gui.getFuture(), new FutureCallback<Timeline>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean delete) {
|
||||
if (delete) {
|
||||
mod.clearCurrentTimeline();
|
||||
if (entityTracker != null) {
|
||||
mod.getCurrentTimeline().setEntityTracker(entityTracker);
|
||||
}
|
||||
public void onSuccess(Timeline result) {
|
||||
if (result != null) {
|
||||
mod.setCurrentTimeline(new SPTimeline(result));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
t.printStackTrace();
|
||||
core.printWarningToChat("Error loading timeline: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
});
|
||||
gui.display();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
core.printWarningToChat("Error loading timeline: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
core.getKeyBindingRegistry().registerRepeatedKeyBinding("replaymod.input.synctimeline", Keyboard.KEY_V, () -> {
|
||||
// Current replay time
|
||||
int time = replayHandler.getReplaySender().currentTimeStamp();
|
||||
// Position of the cursor
|
||||
int cursor = timeline.getCursorPosition();
|
||||
// Get the last time keyframe before the cursor
|
||||
mod.getCurrentTimeline().getTimePath().getKeyframes().stream()
|
||||
.filter(it -> it.getTime() <= cursor).reduce((__, last) -> last).ifPresent(keyframe -> {
|
||||
// Cursor position at the keyframe
|
||||
int keyframeCursor = (int) keyframe.getTime();
|
||||
// Replay time at the keyframe
|
||||
// This is a keyframe from the time path, so it _should_ always have a time property
|
||||
int keyframeTime = keyframe.getValue(TimestampProperty.PROPERTY).get();
|
||||
// Replay time passed
|
||||
int timePassed = time - keyframeTime;
|
||||
// Speed (set to 1 when shift is held)
|
||||
double speed = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) ? 1 : overlay.getSpeedSliderValue();
|
||||
// Cursor time passed
|
||||
int cursorPassed = (int) (timePassed / speed);
|
||||
// Move cursor to new position
|
||||
timeline.setCursorPosition(keyframeCursor + cursorPassed);
|
||||
// Deselect keyframe to allow the user to add a new one right away
|
||||
mod.setSelected(null, 0);
|
||||
});
|
||||
});
|
||||
|
||||
core.getKeyBindingRegistry().registerRaw(Keyboard.KEY_DELETE, () -> {
|
||||
if (!overlay.isVisible()) {
|
||||
return;
|
||||
public void clearKeyframesButtonPressed() {
|
||||
GuiYesNoPopup popup = GuiYesNoPopup.open(replayHandler.getOverlay(),
|
||||
new GuiLabel().setI18nText("replaymod.gui.clearcallback.title").setColor(Colors.BLACK)
|
||||
).setYesI18nLabel("gui.yes").setNoI18nLabel("gui.no");
|
||||
Futures.addCallback(popup.getFuture(), new FutureCallback<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean delete) {
|
||||
if (delete) {
|
||||
mod.clearCurrentTimeline();
|
||||
if (entityTracker != null) {
|
||||
mod.getCurrentTimeline().setEntityTracker(entityTracker);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mod.getSelectedPath() != null) {
|
||||
updateKeyframe(mod.getSelectedPath());
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void syncTimeButtonPressed() {
|
||||
// Current replay time
|
||||
int time = replayHandler.getReplaySender().currentTimeStamp();
|
||||
// Position of the cursor
|
||||
int cursor = timeline.getCursorPosition();
|
||||
// Get the last time keyframe before the cursor
|
||||
mod.getCurrentTimeline().getTimePath().getKeyframes().stream()
|
||||
.filter(it -> it.getTime() <= cursor).reduce((__, last) -> last).ifPresent(keyframe -> {
|
||||
// Cursor position at the keyframe
|
||||
int keyframeCursor = (int) keyframe.getTime();
|
||||
// Replay time at the keyframe
|
||||
// This is a keyframe from the time path, so it _should_ always have a time property
|
||||
int keyframeTime = keyframe.getValue(TimestampProperty.PROPERTY).get();
|
||||
// Replay time passed
|
||||
int timePassed = time - keyframeTime;
|
||||
// Speed (set to 1 when shift is held)
|
||||
double speed = Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) ? 1 : replayHandler.getOverlay().getSpeedSliderValue();
|
||||
// Cursor time passed
|
||||
int cursorPassed = (int) (timePassed / speed);
|
||||
// Move cursor to new position
|
||||
timeline.setCursorPosition(keyframeCursor + cursorPassed);
|
||||
// Deselect keyframe to allow the user to add a new one right away
|
||||
mod.setSelected(null, 0);
|
||||
});
|
||||
}
|
||||
|
||||
public void deleteButtonPressed() {
|
||||
if (mod.getSelectedPath() != null) {
|
||||
updateKeyframe(mod.getSelectedPath());
|
||||
}
|
||||
}
|
||||
|
||||
private void startLoadingEntityTracker() {
|
||||
Preconditions.checkState(entityTrackerFuture == null);
|
||||
// Start loading entity tracker
|
||||
entityTrackerFuture = SettableFuture.create();
|
||||
new Thread(() -> {
|
||||
@@ -493,12 +499,13 @@ public class GuiPathing {
|
||||
}
|
||||
});
|
||||
logger.info("Loaded entity tracker in " + (System.currentTimeMillis() - start) + "ms");
|
||||
} catch (IOException e) {
|
||||
} catch (Throwable e) {
|
||||
logger.error("Loading entity tracker:", e);
|
||||
mod.getCore().runLater(() -> {
|
||||
mod.getCore().printWarningToChat("Error loading entity tracker: %s", e.getLocalizedMessage());
|
||||
entityTrackerFuture.setException(e);
|
||||
});
|
||||
return;
|
||||
}
|
||||
entityTracker = tracker;
|
||||
mod.getCore().runLater(() -> {
|
||||
@@ -557,7 +564,9 @@ public class GuiPathing {
|
||||
|
||||
@Override
|
||||
public void onFailure(@Nonnull Throwable t) {
|
||||
popup.close();
|
||||
String message = "Failed to load entity tracker, pathing will be unavailable.";
|
||||
GuiReplayOverlay overlay = replayHandler.getOverlay();
|
||||
Utils.error(LOGGER, overlay, CrashReport.makeCrashReport(t, message), popup::close);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user