Add common pathing code and SimplePathing module
This commit is contained in:
17
src/main/java/com/replaymod/pathing/PathingRegistry.java
Normal file
17
src/main/java/com/replaymod/pathing/PathingRegistry.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.replaymod.pathing;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.replaymod.pathing.interpolation.Interpolator;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Contains mappings required for serialization.
|
||||
*/
|
||||
public interface PathingRegistry {
|
||||
Timeline createTimeline();
|
||||
void serializeInterpolator(JsonWriter writer, Interpolator interpolator) throws IOException;
|
||||
Interpolator deserializeInterpolator(JsonReader reader) throws IOException;
|
||||
}
|
||||
62
src/main/java/com/replaymod/pathing/change/AddKeyframe.java
Normal file
62
src/main/java/com/replaymod/pathing/change/AddKeyframe.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package com.replaymod.pathing.change;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.path.Path;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* Adds a new property.
|
||||
*/
|
||||
public final class AddKeyframe implements Change {
|
||||
@NonNull
|
||||
public static AddKeyframe create(Path path, long time) {
|
||||
return new AddKeyframe(path.getTimeline().getPaths().indexOf(path), time);
|
||||
}
|
||||
|
||||
AddKeyframe(int path, long time) {
|
||||
this.path = path;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path index
|
||||
*/
|
||||
private final int path;
|
||||
|
||||
/**
|
||||
* Time at which the property should be injected.
|
||||
*/
|
||||
private final long time;
|
||||
|
||||
/**
|
||||
* Index of the newly created property.
|
||||
*/
|
||||
private int index;
|
||||
|
||||
private boolean applied;
|
||||
|
||||
@Override
|
||||
public void apply(Timeline timeline) {
|
||||
Preconditions.checkState(!applied, "Already applied!");
|
||||
|
||||
Path path = timeline.getPaths().get(this.path);
|
||||
Keyframe keyframe = path.insert(time);
|
||||
index = Iterables.indexOf(path.getKeyframes(), Predicates.equalTo(keyframe));
|
||||
|
||||
applied = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo(Timeline timeline) {
|
||||
Preconditions.checkState(applied, "Not yet applied!");
|
||||
|
||||
Path path = timeline.getPaths().get(this.path);
|
||||
path.remove(Iterables.get(path.getKeyframes(), index), true);
|
||||
|
||||
applied = false;
|
||||
}
|
||||
}
|
||||
35
src/main/java/com/replaymod/pathing/change/AddPath.java
Normal file
35
src/main/java/com/replaymod/pathing/change/AddPath.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package com.replaymod.pathing.change;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* Adds a new property.
|
||||
*/
|
||||
public final class AddPath implements Change {
|
||||
@NonNull
|
||||
public static AddPath create() {
|
||||
return new AddPath();
|
||||
}
|
||||
|
||||
private boolean applied;
|
||||
|
||||
@Override
|
||||
public void apply(Timeline timeline) {
|
||||
Preconditions.checkState(!applied, "Already applied!");
|
||||
|
||||
timeline.createPath();
|
||||
|
||||
applied = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo(Timeline timeline) {
|
||||
Preconditions.checkState(applied, "Not yet applied!");
|
||||
|
||||
timeline.getPaths().remove(timeline.getPaths().size() - 1);
|
||||
|
||||
applied = false;
|
||||
}
|
||||
}
|
||||
29
src/main/java/com/replaymod/pathing/change/Change.java
Normal file
29
src/main/java/com/replaymod/pathing/change/Change.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package com.replaymod.pathing.change;
|
||||
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
|
||||
/**
|
||||
* A change to any part of a timeline.
|
||||
* If {@link #undo(Timeline)} is not called in the reverse order of {@link #apply(Timeline)}, the behavior is unspecified.
|
||||
* Instances implementing this interfaces must not reference any outside objects required for applying
|
||||
* as they might be serialized and deserialized at any time.
|
||||
* After deserialization, only the {@link #apply(Timeline)} method is guaranteed to work.
|
||||
*/
|
||||
public interface Change {
|
||||
|
||||
/**
|
||||
* Apply this change.
|
||||
*
|
||||
* @param timeline The timeline
|
||||
* @throws IllegalStateException If already applied.
|
||||
*/
|
||||
void apply(Timeline timeline);
|
||||
|
||||
/**
|
||||
* Undo this change.
|
||||
*
|
||||
* @param timeline The timeline
|
||||
* @throws IllegalStateException If not yet applied.
|
||||
*/
|
||||
void undo(Timeline timeline);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.replaymod.pathing.change;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
/**
|
||||
* Represents multiple changes as one change.
|
||||
*/
|
||||
public class CombinedChange implements Change {
|
||||
|
||||
/**
|
||||
* Combines the specified changes into one change in the order given.
|
||||
* All changes must not yet have been applied.
|
||||
*
|
||||
* @param changes List of changes
|
||||
* @return A new CombinedChange instance
|
||||
*/
|
||||
@NonNull
|
||||
public static CombinedChange create(Change... changes) {
|
||||
return new CombinedChange(Arrays.asList(changes), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines the specified changes into one change in the order given.
|
||||
* All changes must have been applied.
|
||||
*
|
||||
* @param changes List of changes
|
||||
* @return A new CombinedChange instance
|
||||
*/
|
||||
@NonNull
|
||||
public static CombinedChange createFromApplied(Change... changes) {
|
||||
return new CombinedChange(Arrays.asList(changes), true);
|
||||
}
|
||||
|
||||
CombinedChange(List<Change> changeList, boolean applied) {
|
||||
this.changeList = changeList;
|
||||
this.applied = applied;
|
||||
}
|
||||
|
||||
private final List<Change> changeList;
|
||||
private boolean applied;
|
||||
|
||||
@Override
|
||||
public void apply(Timeline timeline) {
|
||||
Preconditions.checkState(!applied, "Already applied!");
|
||||
|
||||
for (Change change : changeList) {
|
||||
change.apply(timeline);
|
||||
}
|
||||
|
||||
applied = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo(Timeline timeline) {
|
||||
Preconditions.checkState(applied, "Not yet applied!");
|
||||
|
||||
ListIterator<Change> iterator = changeList.listIterator(changeList.size());
|
||||
while (iterator.hasPrevious()) {
|
||||
iterator.previous().undo(timeline);
|
||||
}
|
||||
|
||||
applied = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.replaymod.pathing.change;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.replaymod.pathing.interpolation.Interpolator;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.path.Path;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* Removes a property.
|
||||
*/
|
||||
public final class RemoveKeyframe implements Change {
|
||||
@NonNull
|
||||
public static RemoveKeyframe create(@NonNull Path path, @NonNull Keyframe keyframe) {
|
||||
return new RemoveKeyframe(path.getTimeline().getPaths().indexOf(path),
|
||||
Iterables.indexOf(path.getKeyframes(), Predicates.equalTo(keyframe)));
|
||||
}
|
||||
|
||||
RemoveKeyframe(int path, int index) {
|
||||
this.path = path;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path index
|
||||
*/
|
||||
private final int path;
|
||||
|
||||
/**
|
||||
* Index of the property to be removed.
|
||||
*/
|
||||
private final int index;
|
||||
|
||||
private volatile Keyframe removedKeyframe;
|
||||
private volatile Interpolator removedInterpolator;
|
||||
|
||||
private boolean applied;
|
||||
|
||||
@Override
|
||||
public void apply(Timeline timeline) {
|
||||
Preconditions.checkState(!applied, "Already applied!");
|
||||
|
||||
Path path = timeline.getPaths().get(this.path);
|
||||
if (index < path.getSegments().size()) {
|
||||
removedInterpolator = Iterables.get(path.getSegments(), index).getInterpolator();
|
||||
}
|
||||
path.remove(removedKeyframe = Iterables.get(path.getKeyframes(), index), true);
|
||||
|
||||
applied = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo(Timeline timeline) {
|
||||
Preconditions.checkState(applied, "Not yet applied!");
|
||||
|
||||
Path path = timeline.getPaths().get(this.path);
|
||||
path.insert(removedKeyframe);
|
||||
if (removedInterpolator != null) {
|
||||
Iterables.get(path.getSegments(), index).setInterpolator(removedInterpolator);
|
||||
}
|
||||
|
||||
applied = false;
|
||||
}
|
||||
}
|
||||
50
src/main/java/com/replaymod/pathing/change/RemovePath.java
Normal file
50
src/main/java/com/replaymod/pathing/change/RemovePath.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package com.replaymod.pathing.change;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.replaymod.pathing.path.Path;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* Adds a new property.
|
||||
*/
|
||||
public final class RemovePath implements Change {
|
||||
@NonNull
|
||||
public static RemovePath create(Path path) {
|
||||
return new RemovePath(path.getTimeline().getPaths().indexOf(path));
|
||||
}
|
||||
|
||||
RemovePath(int path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path index
|
||||
*/
|
||||
private final int path;
|
||||
|
||||
/**
|
||||
* The removed path
|
||||
*/
|
||||
private volatile Path oldPath;
|
||||
|
||||
private boolean applied;
|
||||
|
||||
@Override
|
||||
public void apply(Timeline timeline) {
|
||||
Preconditions.checkState(!applied, "Already applied!");
|
||||
|
||||
oldPath = timeline.getPaths().remove(path);
|
||||
|
||||
applied = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo(Timeline timeline) {
|
||||
Preconditions.checkState(applied, "Not yet applied!");
|
||||
|
||||
timeline.getPaths().add(path, oldPath);
|
||||
|
||||
applied = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.replaymod.pathing.change;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.path.Path;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import com.replaymod.pathing.property.PropertyGroup;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.google.common.base.Predicates.equalTo;
|
||||
import static com.google.common.collect.Iterables.get;
|
||||
import static com.google.common.collect.Iterables.indexOf;
|
||||
|
||||
/**
|
||||
* Updates some properties of a property.
|
||||
*/
|
||||
public final class UpdateKeyframeProperties implements Change {
|
||||
|
||||
private static String toId(Property property) {
|
||||
assert property != null;
|
||||
PropertyGroup group = property.getGroup();
|
||||
return (group != null ? group.getId() + ":" : "") + property.getId();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final int path;
|
||||
private final int keyframe;
|
||||
private final Map<String, Optional<Object>> updates = new HashMap<>();
|
||||
|
||||
private Builder(int path, int keyframe) {
|
||||
this.path = path;
|
||||
this.keyframe = keyframe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value for the property at this property.
|
||||
* If the property is not present, adds it.
|
||||
*
|
||||
* @param property The property
|
||||
* @param value Value of the property, may be {@code null}
|
||||
* @param <T> Type of the property
|
||||
* @return {@code this} for chaining
|
||||
*/
|
||||
public <T> Builder setValue(Property<T> property, T value) {
|
||||
updates.put(toId(property), Optional.of((Object) value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified property from this property.
|
||||
*
|
||||
* @param property The property to be removed
|
||||
* @return {@code this} for chaining
|
||||
*/
|
||||
public Builder removeProperty(Property property) {
|
||||
updates.put(toId(property), Optional.absent());
|
||||
return this;
|
||||
}
|
||||
|
||||
public UpdateKeyframeProperties done() {
|
||||
return new UpdateKeyframeProperties(path, keyframe, updates);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static UpdateKeyframeProperties.Builder create(@NonNull Path path, @NonNull Keyframe keyframe) {
|
||||
return new UpdateKeyframeProperties.Builder(path.getTimeline().getPaths().indexOf(path),
|
||||
indexOf(path.getKeyframes(), equalTo(keyframe)));
|
||||
}
|
||||
|
||||
UpdateKeyframeProperties(int path, int index, Map<String, Optional<Object>> newValues) {
|
||||
this.path = path;
|
||||
this.index = index;
|
||||
this.newValues = newValues;
|
||||
}
|
||||
|
||||
private final int path;
|
||||
private final int index;
|
||||
private final Map<String, Optional<Object>> newValues;
|
||||
private final Map<String, Optional<Object>> oldValues = new HashMap<>();
|
||||
private boolean applied;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void apply(Timeline timeline) {
|
||||
Preconditions.checkState(!applied, "Already applied!");
|
||||
|
||||
Path path = timeline.getPaths().get(this.path);
|
||||
Keyframe keyframe = get(path.getKeyframes(), index);
|
||||
for (Map.Entry<String, Optional<Object>> entry : newValues.entrySet()) {
|
||||
Property property = timeline.getProperty(entry.getKey());
|
||||
if (property == null) throw new IllegalStateException("Property " + entry.getKey() + " unknown.");
|
||||
Optional<Object> newValue = entry.getValue();
|
||||
oldValues.put(entry.getKey(), keyframe.getValue(property));
|
||||
if (newValue.isPresent()) {
|
||||
keyframe.setValue(property, newValue.get());
|
||||
} else {
|
||||
keyframe.removeProperty(property);
|
||||
}
|
||||
}
|
||||
|
||||
applied = true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void undo(Timeline timeline) {
|
||||
Preconditions.checkState(applied, "Not yet applied!");
|
||||
|
||||
Path path = timeline.getPaths().get(this.path);
|
||||
Keyframe keyframe = get(path.getKeyframes(), index);
|
||||
for (Map.Entry<String, Optional<Object>> entry : oldValues.entrySet()) {
|
||||
Property property = timeline.getProperty(entry.getKey());
|
||||
if (property == null) throw new IllegalStateException("Property " + entry.getKey() + " unknown.");
|
||||
Optional<Object> oldValue = entry.getValue();
|
||||
newValues.put(entry.getKey(), keyframe.getValue(property));
|
||||
if (oldValue.isPresent()) {
|
||||
keyframe.setValue(property, oldValue.get());
|
||||
} else {
|
||||
keyframe.removeProperty(property);
|
||||
}
|
||||
}
|
||||
|
||||
applied = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
package com.replaymod.pathing.gui;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.pathing.PathingRegistry;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.pathing.serialize.TimelineSerialization;
|
||||
import com.replaymod.replay.ReplayModReplay;
|
||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||
import de.johni0702.minecraft.gui.RenderInfo;
|
||||
import de.johni0702.minecraft.gui.container.*;
|
||||
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.IGuiButton;
|
||||
import de.johni0702.minecraft.gui.function.Closeable;
|
||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
||||
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
||||
import de.johni0702.minecraft.gui.popup.GuiYesNoPopup;
|
||||
import de.johni0702.minecraft.gui.utils.Colors;
|
||||
import de.johni0702.minecraft.gui.utils.Consumer;
|
||||
import de.johni0702.replaystudio.replay.ReplayFile;
|
||||
import org.lwjgl.util.Dimension;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Gui for loading and saving {@link com.replaymod.pathing.path.Timeline Timelines}.
|
||||
*/
|
||||
public class GuiKeyframeRepository extends GuiScreen implements Closeable {
|
||||
public final GuiPanel contentPanel = new GuiPanel(this).setBackgroundColor(Colors.DARK_TRANSPARENT);
|
||||
public final GuiLabel title = new GuiLabel(contentPanel).setI18nText("replaymod.gui.keyframerepository.title");
|
||||
public final GuiVerticalList list = new GuiVerticalList(contentPanel).setDrawShadow(true).setDrawSlider(true);
|
||||
public final GuiPanel buttonPanel = new GuiPanel(contentPanel).setLayout(new HorizontalLayout().setSpacing(5));
|
||||
public final GuiButton overwriteButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
timelines.put(selectedEntry.name, currentTimeline);
|
||||
overwriteButton.setDisabled();
|
||||
save();
|
||||
}
|
||||
}).setSize(75, 20).setI18nLabel("replaymod.gui.overwrite").setDisabled();
|
||||
public final GuiButton saveAsButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true);
|
||||
final GuiYesNoPopup popup = GuiYesNoPopup.open(GuiKeyframeRepository.this,
|
||||
new GuiLabel().setI18nText("replaymod.gui.saveas").setColor(Colors.BLACK),
|
||||
nameField
|
||||
).setYesI18nLabel("replaymod.gui.save").setNoI18nLabel("replaymod.gui.cancel");
|
||||
popup.getYesButton().setDisabled();
|
||||
((VerticalLayout) popup.getInfo().getLayout()).setSpacing(7);
|
||||
nameField.onEnter(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (popup.getYesButton().isEnabled()) {
|
||||
popup.getYesButton().onClick();
|
||||
}
|
||||
}
|
||||
}).onTextChanged(new Consumer<String>() {
|
||||
@Override
|
||||
public void consume(String obj) {
|
||||
popup.getYesButton().setEnabled(!nameField.getText().isEmpty()
|
||||
&& !timelines.containsKey(nameField.getText()));
|
||||
}
|
||||
});
|
||||
Futures.addCallback(popup.getFuture(), new FutureCallback<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean save) {
|
||||
if (save) {
|
||||
String name = nameField.getText();
|
||||
timelines.put(name, currentTimeline);
|
||||
list.getListPanel().addElements(null, new Entry(name));
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).setSize(75, 20).setI18nLabel("replaymod.gui.saveas");
|
||||
public final GuiButton loadButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getMinecraft().displayGuiScreen(null);
|
||||
future.set(timelines.get(selectedEntry.name));
|
||||
}
|
||||
}).setSize(75, 20).setI18nLabel("replaymod.gui.load").setDisabled();
|
||||
public final GuiButton renameButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final GuiTextField nameField = new GuiTextField().setSize(200, 20).setFocused(true).setText(selectedEntry.name);
|
||||
final GuiYesNoPopup popup = GuiYesNoPopup.open(GuiKeyframeRepository.this,
|
||||
new GuiLabel().setI18nText("replaymod.gui.rename").setColor(Colors.BLACK),
|
||||
nameField
|
||||
).setYesI18nLabel("replaymod.gui.done").setNoI18nLabel("replaymod.gui.cancel");
|
||||
popup.getYesButton().setDisabled();
|
||||
((VerticalLayout) popup.getInfo().getLayout()).setSpacing(7);
|
||||
nameField.onEnter(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (popup.getYesButton().isEnabled()) {
|
||||
popup.getYesButton().onClick();
|
||||
}
|
||||
}
|
||||
}).onTextChanged(new Consumer<String>() {
|
||||
@Override
|
||||
public void consume(String obj) {
|
||||
popup.getYesButton().setEnabled(!nameField.getText().isEmpty()
|
||||
&& !timelines.containsKey(nameField.getText()));
|
||||
}
|
||||
});
|
||||
Futures.addCallback(popup.getFuture(), new FutureCallback<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean save) {
|
||||
if (save) {
|
||||
String name = nameField.getText();
|
||||
timelines.put(name, timelines.remove(selectedEntry.name));
|
||||
selectedEntry.name = name;
|
||||
selectedEntry.label.setText(name);
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).setSize(75, 20).setI18nLabel("replaymod.gui.rename").setDisabled();
|
||||
public final GuiButton removeButton = new GuiButton(buttonPanel).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GuiYesNoPopup popup = GuiYesNoPopup.open(GuiKeyframeRepository.this,
|
||||
new GuiLabel().setI18nText("replaymod.gui.keyframerepo.delete").setColor(Colors.BLACK)
|
||||
).setYesI18nLabel("replaymod.gui.delete").setNoI18nLabel("replaymod.gui.cancel");
|
||||
Futures.addCallback(popup.getFuture(), new FutureCallback<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean delete) {
|
||||
if (delete) {
|
||||
timelines.remove(selectedEntry.name);
|
||||
list.getListPanel().removeElement(selectedEntry);
|
||||
|
||||
selectedEntry = null;
|
||||
overwriteButton.setDisabled();
|
||||
loadButton.setDisabled();
|
||||
renameButton.setDisabled();
|
||||
removeButton.setDisabled();
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).setSize(75, 20).setI18nLabel("replaymod.gui.remove").setDisabled();
|
||||
|
||||
private final Map<String, Timeline> timelines = new LinkedHashMap<>();
|
||||
private final Timeline currentTimeline;
|
||||
private final SettableFuture<Timeline> future = SettableFuture.create();
|
||||
private final TimelineSerialization serialization;
|
||||
|
||||
private Entry selectedEntry;
|
||||
|
||||
{
|
||||
setDrawBackground(false);
|
||||
setLayout(new CustomLayout<GuiScreen>() {
|
||||
@Override
|
||||
protected void layout(GuiScreen container, int width, int height) {
|
||||
pos(contentPanel, width / 2 - width(contentPanel) / 2, height / 2 - height(contentPanel) / 2);
|
||||
}
|
||||
});
|
||||
contentPanel.setLayout(new CustomLayout<GuiPanel>() {
|
||||
@Override
|
||||
protected void layout(GuiPanel container, int width, int height) {
|
||||
pos(title, width / 2 - width(title) / 2, 5);
|
||||
size(list, width, height - 10 - height(buttonPanel) - 10 - y(title) - height(title) - 5);
|
||||
pos(list, width / 2 - width(list) / 2, y(title) + height(title) + 5);
|
||||
pos(buttonPanel, width / 2 - width(buttonPanel) / 2, y(list) + height(list) + 10);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadableDimension calcMinSize(GuiContainer<?> container) {
|
||||
ReadableDimension screenSize = getMinSize();
|
||||
return new Dimension(screenSize.getWidth() - 10, screenSize.getHeight() - 10);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public GuiKeyframeRepository(PathingRegistry registry, ReplayFile replayFile, Timeline currentTimeline) throws IOException {
|
||||
this.currentTimeline = currentTimeline;
|
||||
this.serialization = new TimelineSerialization(registry, replayFile);
|
||||
|
||||
timelines.putAll(serialization.load());
|
||||
|
||||
for (Map.Entry<String, Timeline> entry : timelines.entrySet()) {
|
||||
list.getListPanel().addElements(null, new Entry(entry.getKey()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display() {
|
||||
super.display();
|
||||
ReplayModReplay.instance.getReplayHandler().getOverlay().setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
ReplayModReplay.instance.getReplayHandler().getOverlay().setVisible(true);
|
||||
}
|
||||
|
||||
public SettableFuture<Timeline> getFuture() {
|
||||
return future;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
try {
|
||||
serialization.save(timelines);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
ReplayMod.instance.printWarningToChat("Error saving timelines: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public class Entry extends AbstractGuiClickableContainer<Entry> {
|
||||
public final GuiLabel label = new GuiLabel(this);
|
||||
private String name;
|
||||
|
||||
public Entry(String name) {
|
||||
this.name = name;
|
||||
|
||||
setLayout(new CustomLayout<Entry>() {
|
||||
@Override
|
||||
protected void layout(Entry container, int width, int height) {
|
||||
pos(label, 5, height / 2 - height(label) / 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadableDimension calcMinSize(GuiContainer<?> container) {
|
||||
return new Dimension(buttonPanel.calcMinSize().getWidth(), 16);
|
||||
}
|
||||
});
|
||||
label.setText(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick() {
|
||||
selectedEntry = this;
|
||||
buttonPanel.forEach(IGuiButton.class).setEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
||||
if (selectedEntry == this) {
|
||||
renderer.drawRect(0, 0, size.getWidth(), size.getHeight(), Colors.BLACK);
|
||||
renderer.drawRect(0, 0, 2, size.getHeight(), Colors.WHITE);
|
||||
}
|
||||
super.draw(renderer, size, renderInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Entry getThis() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/main/java/com/replaymod/pathing/impl/KeyframeImpl.java
Normal file
43
src/main/java/com/replaymod/pathing/impl/KeyframeImpl.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.replaymod.pathing.impl;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class KeyframeImpl implements Keyframe {
|
||||
private final long time;
|
||||
private final Map<Property, Object> properties = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Optional<T> getValue(Property<T> property) {
|
||||
return properties.containsKey(property) ? Optional.of((T) properties.get(property)) : Optional.<T>absent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void setValue(Property<T> property, T value) {
|
||||
properties.put(property, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProperty(Property property) {
|
||||
properties.remove(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Property> getProperties() {
|
||||
return Collections.unmodifiableSet(properties.keySet());
|
||||
}
|
||||
}
|
||||
175
src/main/java/com/replaymod/pathing/impl/PathImpl.java
Normal file
175
src/main/java/com/replaymod/pathing/impl/PathImpl.java
Normal file
@@ -0,0 +1,175 @@
|
||||
package com.replaymod.pathing.impl;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.replaymod.pathing.interpolation.InterpolationParameters;
|
||||
import com.replaymod.pathing.interpolation.Interpolator;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.path.Path;
|
||||
import com.replaymod.pathing.path.PathSegment;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import com.replaymod.pathing.property.PropertyPart;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class PathImpl implements Path {
|
||||
private final Timeline timeline;
|
||||
private Map<Long, Keyframe> keyframes = new TreeMap<>();
|
||||
private List<PathSegment> segments = new LinkedList<>();
|
||||
|
||||
public PathImpl(Timeline timeline) {
|
||||
this.timeline = timeline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeline getTimeline() {
|
||||
return timeline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Keyframe> getKeyframes() {
|
||||
return Collections.unmodifiableCollection(keyframes.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PathSegment> getSegments() {
|
||||
return Collections.unmodifiableCollection(segments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
update(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAll() {
|
||||
update(false);
|
||||
}
|
||||
|
||||
private void update(boolean force) {
|
||||
Interpolator interpolator = null;
|
||||
Map<PropertyPart, InterpolationParameters> parameters = new HashMap<>();
|
||||
for (PathSegment segment : segments) {
|
||||
if (segment.getInterpolator() != interpolator) {
|
||||
interpolator = segment.getInterpolator();
|
||||
if (force || interpolator.isDirty()) {
|
||||
parameters = interpolator.bake(parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getValue(Property<T> property, long time) {
|
||||
PathSegment segment = getSegment(time);
|
||||
if (segment != null) {
|
||||
Interpolator interpolator = segment.getInterpolator();
|
||||
if (interpolator != null) {
|
||||
if (interpolator.getKeyframeProperties().contains(property)) {
|
||||
return interpolator.getValue(property, time);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Keyframe insert(long time) {
|
||||
Keyframe keyframe = new KeyframeImpl(time);
|
||||
insert(keyframe);
|
||||
return keyframe;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Keyframe getKeyframe(long time) {
|
||||
return keyframes.get(time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(Keyframe keyframe) {
|
||||
if (keyframes.containsKey(keyframe.getTime())) {
|
||||
throw new IllegalStateException("A keyframe at " + keyframe.getTime() + " already exists.");
|
||||
}
|
||||
keyframes.put(keyframe.getTime(), keyframe);
|
||||
|
||||
if (segments.isEmpty()) {
|
||||
if (keyframes.size() >= 2) {
|
||||
Iterator<Keyframe> iter = keyframes.values().iterator();
|
||||
segments.add(new PathSegmentImpl(iter.next(), iter.next()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ListIterator<PathSegment> iter = segments.listIterator();
|
||||
PathSegment next = iter.next();
|
||||
if (keyframe.getTime() < next.getStartKeyframe().getTime()) {
|
||||
iter.previous();
|
||||
iter.add(new PathSegmentImpl(keyframe, next.getStartKeyframe(), next.getInterpolator()));
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (next.getStartKeyframe().getTime() <= keyframe.getTime()
|
||||
&& next.getEndKeyframe().getTime() >= keyframe.getTime()) {
|
||||
iter.remove();
|
||||
next.setInterpolator(null);
|
||||
iter.add(new PathSegmentImpl(next.getStartKeyframe(), keyframe, next.getInterpolator()));
|
||||
iter.add(new PathSegmentImpl(keyframe, next.getEndKeyframe(), next.getInterpolator()));
|
||||
return;
|
||||
}
|
||||
if (iter.hasNext()) {
|
||||
next = iter.next();
|
||||
} else {
|
||||
iter.add(new PathSegmentImpl(next.getEndKeyframe(), keyframe, next.getInterpolator()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(Keyframe keyframe, boolean useFirstInterpolator) {
|
||||
if (keyframes.get(keyframe.getTime()) != keyframe) {
|
||||
throw new IllegalArgumentException("The keyframe " + keyframe + " is not part of this path.");
|
||||
}
|
||||
keyframes.remove(keyframe.getTime());
|
||||
|
||||
if (segments.size() < 2) {
|
||||
for (PathSegment segment : segments) {
|
||||
segment.setInterpolator(null);
|
||||
}
|
||||
segments.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
ListIterator<PathSegment> iter = segments.listIterator();
|
||||
while (iter.hasNext()) {
|
||||
PathSegment next = iter.next();
|
||||
if (next.getEndKeyframe() == keyframe) {
|
||||
iter.remove();
|
||||
if (iter.hasNext()) {
|
||||
PathSegment next2 = iter.next();
|
||||
iter.remove();
|
||||
iter.add(new PathSegmentImpl(next.getStartKeyframe(), next2.getEndKeyframe(),
|
||||
(useFirstInterpolator ? next : next2).getInterpolator()));
|
||||
} else {
|
||||
next.setInterpolator(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (next.getStartKeyframe() == keyframe) {
|
||||
next.setInterpolator(null);
|
||||
iter.remove();
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new AssertionError("No segment for keyframe found!");
|
||||
}
|
||||
|
||||
private PathSegment getSegment(long time) {
|
||||
for (PathSegment segment : segments) {
|
||||
if (segment.getStartKeyframe().getTime() <= time && segment.getEndKeyframe().getTime() >= time) {
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.replaymod.pathing.impl;
|
||||
|
||||
import com.replaymod.pathing.interpolation.Interpolator;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.path.PathSegment;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class PathSegmentImpl implements PathSegment {
|
||||
private final Keyframe startKeyframe;
|
||||
private final Keyframe endKeyframe;
|
||||
private Interpolator interpolator;
|
||||
|
||||
public PathSegmentImpl(Keyframe startKeyframe, Keyframe endKeyframe, Interpolator interpolator) {
|
||||
this.startKeyframe = startKeyframe;
|
||||
this.endKeyframe = endKeyframe;
|
||||
setInterpolator(interpolator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInterpolator(Interpolator interpolator) {
|
||||
if (this.interpolator != null) {
|
||||
this.interpolator.removeSegment(this);
|
||||
}
|
||||
this.interpolator = interpolator;
|
||||
if (this.interpolator != null) {
|
||||
this.interpolator.addSegment(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
101
src/main/java/com/replaymod/pathing/impl/TimelineImpl.java
Normal file
101
src/main/java/com/replaymod/pathing/impl/TimelineImpl.java
Normal file
@@ -0,0 +1,101 @@
|
||||
package com.replaymod.pathing.impl;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.replaymod.pathing.change.Change;
|
||||
import com.replaymod.pathing.path.Path;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import com.replaymod.replay.ReplayHandler;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class TimelineImpl implements Timeline {
|
||||
private final List<Path> paths = new ArrayList<>();
|
||||
private Map<String, Property> properties = new HashMap<>();
|
||||
private Deque<Change> undoStack = new ArrayDeque<>();
|
||||
private Deque<Change> redoStack = new ArrayDeque<>();
|
||||
|
||||
@Override
|
||||
public List<Path> getPaths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path createPath() {
|
||||
Path path = new PathImpl(this);
|
||||
paths.add(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getValue(Property<T> property, long time) {
|
||||
for (Path path : paths) {
|
||||
Optional<T> value = path.getValue(property, time);
|
||||
if (value.isPresent()) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyToGame(long time, ReplayHandler replayHandler) {
|
||||
for (Property<?> property : properties.values()) {
|
||||
applyToGame(time, replayHandler, property);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void applyToGame(long time, ReplayHandler replayHandler, Property<T> property) {
|
||||
Optional<T> value = getValue(property, time);
|
||||
if (value.isPresent()) {
|
||||
property.applyToGame(value.get(), replayHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerProperty(Property property) {
|
||||
String id = (property.getGroup() == null ? "" : property.getGroup().getId() + ":") + property.getId();
|
||||
properties.put(id, property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Property getProperty(String id) {
|
||||
return properties.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyChange(Change change) {
|
||||
change.apply(this);
|
||||
pushChange(change);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pushChange(Change change) {
|
||||
undoStack.push(change);
|
||||
redoStack.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undoLastChange() {
|
||||
Change change = undoStack.pop();
|
||||
change.undo(this);
|
||||
redoStack.push(change);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void redoLastChange() {
|
||||
Change change = redoStack.pop();
|
||||
change.apply(this);
|
||||
undoStack.push(change);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Change peekUndoStack() {
|
||||
return undoStack.peek();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Change peekRedoStack() {
|
||||
return undoStack.peek();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.replaymod.pathing.interpolation;
|
||||
|
||||
import com.replaymod.pathing.path.PathSegment;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import com.replaymod.pathing.property.PropertyPart;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public abstract class AbstractInterpolator implements Interpolator {
|
||||
private List<PathSegment> segments = new LinkedList<>();
|
||||
private boolean dirty;
|
||||
private final Set<Property> properties = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public Collection<Property> getKeyframeProperties() {
|
||||
return Collections.unmodifiableCollection(properties);
|
||||
}
|
||||
|
||||
public void registerProperty(Property property) {
|
||||
properties.add(property);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSegment(PathSegment segment) {
|
||||
segments.add(segment);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSegment(PathSegment segment) {
|
||||
segments.remove(segment);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PathSegment> getSegments() {
|
||||
return Collections.unmodifiableList(segments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<PropertyPart, InterpolationParameters> bake(Map<PropertyPart, InterpolationParameters> parameters) {
|
||||
if (segments.isEmpty()) throw new IllegalStateException("No segments have been added yet.");
|
||||
Collections.sort(segments, new Comparator<PathSegment>() {
|
||||
@Override
|
||||
public int compare(PathSegment s1, PathSegment s2) {
|
||||
return Long.compare(s1.getStartKeyframe().getTime(), s2.getStartKeyframe().getTime());
|
||||
}
|
||||
});
|
||||
|
||||
// Check for continuity
|
||||
Iterator<PathSegment> iter = segments.iterator();
|
||||
PathSegment last = iter.next();
|
||||
while (iter.hasNext()) {
|
||||
if (last.getEndKeyframe() != (last = iter.next()).getStartKeyframe()) {
|
||||
throw new IllegalStateException("Segments are not continuous.");
|
||||
}
|
||||
}
|
||||
|
||||
return bakeInterpolation(parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bake the interpolation of the current path segments with the specified parameters.
|
||||
* Order of {@link #getSegments()} is guaranteed.
|
||||
* @param parameters Map of parameters for some properties
|
||||
* @return Map of parameters for the next interpolator
|
||||
*/
|
||||
protected abstract Map<PropertyPart, InterpolationParameters> bakeInterpolation(Map<PropertyPart, InterpolationParameters> parameters);
|
||||
|
||||
@Override
|
||||
public boolean isDirty() {
|
||||
return dirty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.replaymod.pathing.interpolation;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Parameters which can be used to make an interpolation more fitting to the previous one.
|
||||
*/
|
||||
@Data
|
||||
public class InterpolationParameters {
|
||||
/**
|
||||
* The final value.
|
||||
* Should be the same as for the final property.
|
||||
* If it differs though, this value should be preferred.
|
||||
*/
|
||||
private final double value;
|
||||
|
||||
/**
|
||||
* Velocity at the end of the interpolation.
|
||||
* Normally this is the first derivative of the interpolating function at the end of the interpolation.
|
||||
*/
|
||||
private final double velocity;
|
||||
|
||||
/**
|
||||
* Acceleration at the end of the interpolation.
|
||||
* Normally this is the second derivative of the interpolating function at the end of the interpolation.
|
||||
*/
|
||||
private final double acceleration;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.replaymod.pathing.interpolation;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.replaymod.pathing.path.Path;
|
||||
import com.replaymod.pathing.path.PathSegment;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import com.replaymod.pathing.property.PropertyPart;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Interpolates multiple keyframes. Also provides all Properties it can handle.
|
||||
* Each Interpolator may provide certain outputs for the next Interpolator.
|
||||
* Interpolators are evaluated first to last.
|
||||
*/
|
||||
public interface Interpolator {
|
||||
/**
|
||||
* Returns a collection of all properties applicable for this Interpolator.
|
||||
*
|
||||
* @return Collection of properties or empty collection if none
|
||||
*/
|
||||
@NonNull
|
||||
Collection<Property> getKeyframeProperties();
|
||||
|
||||
/**
|
||||
* Add the specified path segment to this interpolator.
|
||||
* <p/>
|
||||
* This method should be called by the PathSegment itself.
|
||||
*
|
||||
* @param segment The path segments
|
||||
*/
|
||||
void addSegment(PathSegment segment);
|
||||
|
||||
/**
|
||||
* Remove the specified path segment from this interpolator.
|
||||
* <p/>
|
||||
* This method should be called by the PathSegment itself.
|
||||
*
|
||||
* @param segment The path segments
|
||||
*/
|
||||
void removeSegment(PathSegment segment);
|
||||
|
||||
/**
|
||||
* Returns an immutable list of all path segments handled by this interpolator.
|
||||
* Ordering is only guaranteed after {@link #bake(Map)} has been called and only until
|
||||
* the list is modified.
|
||||
*
|
||||
* @return List of path segments or empty list if none
|
||||
*/
|
||||
@NonNull
|
||||
List<PathSegment> getSegments();
|
||||
|
||||
/**
|
||||
* Bake the interpolation of the current path segments with the specified parameters.
|
||||
* Note that when this method is called directly, the path might need to be updated via {@link Path#update()}
|
||||
*
|
||||
* @param parameters Map of parameters for some properties
|
||||
* @return Map of parameters for the next interpolator
|
||||
* @throws IllegalStateException If no segments have been set yet
|
||||
* or the segments are not continuous
|
||||
*/
|
||||
@NonNull
|
||||
Map<PropertyPart, InterpolationParameters> bake(Map<PropertyPart, InterpolationParameters> parameters);
|
||||
|
||||
/**
|
||||
* Returns whether the segments handled by this interpolator have changed since the last
|
||||
* call of {@link #bake(Map)}.
|
||||
* This only includes the segments themselves, not the properties of their keyframes, those
|
||||
* have to be tracked manually.
|
||||
* @return {@code true} if segments have changed, {@code false} otherwise
|
||||
*/
|
||||
boolean isDirty();
|
||||
|
||||
/**
|
||||
* Return the value of the property at the specified point in time.
|
||||
*
|
||||
* @param property The property
|
||||
* @param time Time in milliseconds since the start
|
||||
* @param <T> Type of the property
|
||||
* @return Optional value of the property
|
||||
* @throws IllegalStateException If {@link #bake(Map)} has not yet been called
|
||||
* or {@link #addSegment(PathSegment)}/{@link #removeSegment(PathSegment)}
|
||||
* has been changed since the last bake
|
||||
*/
|
||||
<T> Optional<T> getValue(Property<T> property, long time);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.replaymod.pathing.interpolation;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.path.PathSegment;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import com.replaymod.pathing.property.PropertyPart;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class LinearInterpolator extends AbstractInterpolator {
|
||||
private Map<Property, Set<Keyframe>> framesToProperty = new HashMap<>();
|
||||
|
||||
private void addToMap(Property property, Keyframe keyframe) {
|
||||
Set<Keyframe> set = framesToProperty.get(property);
|
||||
if (set == null) {
|
||||
framesToProperty.put(property, set = new LinkedHashSet<>());
|
||||
}
|
||||
set.add(keyframe);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected Map<PropertyPart, InterpolationParameters> bakeInterpolation(Map<PropertyPart, InterpolationParameters> parameters) {
|
||||
framesToProperty.clear();
|
||||
for (PathSegment segment : getSegments()) {
|
||||
for (Property property : getKeyframeProperties()) {
|
||||
if (segment.getStartKeyframe().getValue(property).isPresent()) {
|
||||
addToMap(property, segment.getStartKeyframe());
|
||||
}
|
||||
if (segment.getEndKeyframe().getValue(property).isPresent()) {
|
||||
addToMap(property, segment.getEndKeyframe());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Keyframe lastKeyframe = getSegments().get(getSegments().size() - 1).getEndKeyframe();
|
||||
Map<PropertyPart, InterpolationParameters> lastParameters = new HashMap<>();
|
||||
for (Property<?> property : getKeyframeProperties()) {
|
||||
Optional optionalValue = lastKeyframe.getValue(property);
|
||||
if (optionalValue.isPresent()) {
|
||||
Object value = optionalValue.get();
|
||||
for (PropertyPart part : property.getParts()) {
|
||||
lastParameters.put(part, new InterpolationParameters(part.toDouble(value), 1, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
return lastParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> getValue(Property<T> property, long time) {
|
||||
Set<Keyframe> kfSet = framesToProperty.get(property);
|
||||
if (kfSet == null) {
|
||||
return Optional.absent();
|
||||
}
|
||||
Keyframe kfBefore = null, kfAfter = null;
|
||||
for (Keyframe keyframe : kfSet) {
|
||||
if (keyframe.getTime() == time) {
|
||||
return keyframe.getValue(property);
|
||||
} else if (keyframe.getTime() < time) {
|
||||
kfBefore = keyframe;
|
||||
} else if (keyframe.getTime() > time) {
|
||||
kfAfter = keyframe;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (kfBefore == null || kfAfter == null) {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
T valueBefore = kfBefore.getValue(property).get();
|
||||
T valueAfter = kfAfter.getValue(property).get();
|
||||
double fraction = (time - kfBefore.getTime()) / (double) (kfAfter.getTime() - kfBefore.getTime());
|
||||
|
||||
T interpolated = valueBefore;
|
||||
for (PropertyPart<T> part : property.getParts()) {
|
||||
if (part.isInterpolatable()) {
|
||||
double before = part.toDouble(valueBefore);
|
||||
double after = part.toDouble(valueAfter);
|
||||
interpolated = part.fromDouble(interpolated, (after - before) * fraction + before);
|
||||
}
|
||||
}
|
||||
return Optional.of(interpolated);
|
||||
}
|
||||
}
|
||||
55
src/main/java/com/replaymod/pathing/path/Keyframe.java
Normal file
55
src/main/java/com/replaymod/pathing/path/Keyframe.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package com.replaymod.pathing.path;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents a key frame in time that is used to compute frames in between multiple key frames.
|
||||
* Keyframes have different properties depending on the Interpolator tying them together.
|
||||
* One property tied by a different Interpolator to the previous one than to the next one inherits both properties.
|
||||
*/
|
||||
public interface Keyframe {
|
||||
|
||||
/**
|
||||
* Return the time at which this property is set.
|
||||
*
|
||||
* @return Time in milliseconds since the start
|
||||
*/
|
||||
long getTime();
|
||||
|
||||
/**
|
||||
* Return the value of the property set at this property.
|
||||
*
|
||||
* @param property The property
|
||||
* @param <T> Type of the property
|
||||
* @return Optional value of the property
|
||||
*/
|
||||
@NonNull
|
||||
<T> Optional<T> getValue(Property<T> property);
|
||||
|
||||
/**
|
||||
* Set the value for the property at this property.
|
||||
* If the property is not present, adds it.
|
||||
*
|
||||
* @param property The property
|
||||
* @param value Value of the property, may be {@code null}
|
||||
* @param <T> Type of the property
|
||||
*/
|
||||
<T> void setValue(Property<T> property, T value);
|
||||
|
||||
/**
|
||||
* Remove the specified property from this property.
|
||||
*
|
||||
* @param property The property to be removed
|
||||
*/
|
||||
void removeProperty(Property property);
|
||||
|
||||
/**
|
||||
* Returns all properties of this property
|
||||
* @return Set of properties
|
||||
*/
|
||||
Set<Property> getProperties();
|
||||
}
|
||||
102
src/main/java/com/replaymod/pathing/path/Path.java
Normal file
102
src/main/java/com/replaymod/pathing/path/Path.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package com.replaymod.pathing.path;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.replaymod.pathing.interpolation.Interpolator;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a path for some object consisting of keyframes, path segments and interpolators.
|
||||
*/
|
||||
public interface Path {
|
||||
/**
|
||||
* Returns the timeline this path belongs to.
|
||||
* @return The timeline
|
||||
*/
|
||||
Timeline getTimeline();
|
||||
|
||||
/**
|
||||
* Return an immutable collection of all keyframes in this path.
|
||||
*
|
||||
* @return Collection of keyframes or empty list if none
|
||||
*/
|
||||
@NonNull
|
||||
Collection<Keyframe> getKeyframes();
|
||||
|
||||
/**
|
||||
* Return an immutable collection of all segments of this path.
|
||||
*
|
||||
* @return Collection of segments or empty list if none
|
||||
*/
|
||||
@NonNull
|
||||
Collection<PathSegment> getSegments();
|
||||
|
||||
/**
|
||||
* Update all interpolators that need updating.
|
||||
* This does <b>not</b> detect changes to keyframes, only interpolators.
|
||||
* If a property has changed, call {@link Interpolator#bake(Map)} before calling this method
|
||||
* or call {@link #updateAll()}.
|
||||
*
|
||||
* @throws IllegalStateException If any path segments do not have an interpolator set.
|
||||
*/
|
||||
void update();
|
||||
|
||||
/**
|
||||
* Update all interpolations.
|
||||
* This method is significantly slower than {@link #update()}.
|
||||
*
|
||||
* @throws IllegalStateException If any path segments do not have an interpolator set.
|
||||
*/
|
||||
void updateAll();
|
||||
|
||||
/**
|
||||
* Return the value of the property at the specified point in time.
|
||||
*
|
||||
* @param property The property
|
||||
* @param time Time in milliseconds since the start
|
||||
* @param <T> Type of the property
|
||||
* @return Optional value of the property
|
||||
* @throws IllegalStateException If {@link #update()} has not yet been called
|
||||
* or interpolators have changed since the last call
|
||||
*/
|
||||
<T> Optional<T> getValue(Property<T> property, long time);
|
||||
|
||||
/**
|
||||
* Insert a new property at the specified time.
|
||||
* The two new path segments inherit the interpolator of the previous one.
|
||||
* Does <b>not</b> update the interpolators, call {@link #update()} to do so.
|
||||
*
|
||||
* @param time Time in milliseconds
|
||||
* @return The new property
|
||||
*/
|
||||
Keyframe insert(long time);
|
||||
|
||||
/**
|
||||
* Returns the property at the specified time.
|
||||
* @param time Time in milliseconds
|
||||
* @return The property or {@code null} if none exists at that timestamp
|
||||
*/
|
||||
Keyframe getKeyframe(long time);
|
||||
|
||||
/**
|
||||
* Insert the specified property.
|
||||
* The two new path segments inherit the interpolator of the previous one.
|
||||
* Does <b>not</b> update the interpolators, call {@link #update()} to do so.
|
||||
*
|
||||
* @param keyframe The property
|
||||
*/
|
||||
void insert(Keyframe keyframe);
|
||||
|
||||
/**
|
||||
* Removes the specified property.
|
||||
* Does <b>not</b> update the interpolators, call {@link #update()} to do so.
|
||||
*
|
||||
* @param keyframe The property to remove
|
||||
* @param useFirstInterpolator {@code true} if the new path segment inherits the interpolator of the first segment,
|
||||
* {@code false} if it inherits the one of the second segment
|
||||
*/
|
||||
void remove(Keyframe keyframe, boolean useFirstInterpolator);
|
||||
}
|
||||
42
src/main/java/com/replaymod/pathing/path/PathSegment.java
Normal file
42
src/main/java/com/replaymod/pathing/path/PathSegment.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package com.replaymod.pathing.path;
|
||||
|
||||
import com.replaymod.pathing.interpolation.Interpolator;
|
||||
import lombok.NonNull;
|
||||
|
||||
/**
|
||||
* Represents a segment of a Path consisting of one start property and one end property.
|
||||
* Each segment is interpolated by one Interpolator. Multiple segments may have the same Interpolator and can only
|
||||
* be interpolated together (they may or may not influence each other indirectly).
|
||||
*/
|
||||
public interface PathSegment {
|
||||
/**
|
||||
* Return the start property of this segment.
|
||||
*
|
||||
* @return The first property
|
||||
*/
|
||||
@NonNull
|
||||
Keyframe getStartKeyframe();
|
||||
|
||||
/**
|
||||
* Return the end property of this segment.
|
||||
*
|
||||
* @return The second property
|
||||
*/
|
||||
@NonNull
|
||||
Keyframe getEndKeyframe();
|
||||
|
||||
/**
|
||||
* Return the interpolator responsible for this path segment.
|
||||
*
|
||||
* @return The interpolator or {@code null} if not yet set
|
||||
* @throws IllegalStateException If no interpolator has been set yet
|
||||
*/
|
||||
@NonNull
|
||||
Interpolator getInterpolator();
|
||||
|
||||
/**
|
||||
* Set the interpolator responsible for this path segment.
|
||||
* @param interpolator The interpolator
|
||||
*/
|
||||
void setInterpolator(Interpolator interpolator);
|
||||
}
|
||||
104
src/main/java/com/replaymod/pathing/path/Timeline.java
Normal file
104
src/main/java/com/replaymod/pathing/path/Timeline.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package com.replaymod.pathing.path;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.replaymod.pathing.change.Change;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import com.replaymod.replay.ReplayHandler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A timeline is a collection of paths that are played together.
|
||||
*/
|
||||
public interface Timeline {
|
||||
/**
|
||||
* Returns the list of paths that compose this timeline.
|
||||
* The returned list can be modified and changes are reflected on the timeline.
|
||||
* The order of the paths is their priority in case of conflicting properties.
|
||||
* The first path to support a property is the one whose value is used.
|
||||
* @return List of paths
|
||||
*/
|
||||
List<Path> getPaths();
|
||||
|
||||
/**
|
||||
* Creates a new path and adds it to this timeline.
|
||||
* @return A new Path instance
|
||||
*/
|
||||
Path createPath();
|
||||
|
||||
/**
|
||||
* Return the value of the property at the specified point in time.
|
||||
*
|
||||
* @param property The property
|
||||
* @param time Time in milliseconds since the start
|
||||
* @param <T> Type of the property
|
||||
* @return Optional value of the property
|
||||
* @throws IllegalStateException If {@link Path#update()} has not yet been called
|
||||
* or interpolators have changed since the last call
|
||||
*/
|
||||
<T> Optional<T> getValue(Property<T> property, long time);
|
||||
|
||||
/**
|
||||
* Apply the values of all properties at the specified time to the game.
|
||||
*
|
||||
* @param time The time on this path
|
||||
* @param replayHandler The ReplayHandler instance
|
||||
*/
|
||||
void applyToGame(long time, ReplayHandler replayHandler);
|
||||
|
||||
/**
|
||||
* Registers the specified property for use in keyframes in this path.
|
||||
* @param property The property
|
||||
*/
|
||||
void registerProperty(Property property);
|
||||
|
||||
/**
|
||||
* Returns the property corresponding to the specified id.
|
||||
* The id is either "groupId:propertyId" or "propertyId" if the property doesn't belong to any group.
|
||||
* @param id Id of the property
|
||||
* @return The property or {@code null} if not existent
|
||||
*/
|
||||
Property getProperty(String id);
|
||||
|
||||
/**
|
||||
* Apply the change and push it on the undo stack.
|
||||
* Clears the redo stack.
|
||||
* @param change The change
|
||||
* @throws IllegalStateException if the change has already been applied
|
||||
*/
|
||||
void applyChange(Change change);
|
||||
|
||||
/**
|
||||
* Push the change on the undo stack.
|
||||
* Clears the redo stack.
|
||||
* @param change The change
|
||||
* @throws IllegalStateException if the change has not yet been applied
|
||||
*/
|
||||
void pushChange(Change change);
|
||||
|
||||
/**
|
||||
* Undo the last change and push it on the redo stack.
|
||||
* @throws java.util.NoSuchElementException if the stack is empty
|
||||
*/
|
||||
void undoLastChange();
|
||||
|
||||
/**
|
||||
* Redo the last undone change and push it back on the undo stack.
|
||||
* @throws java.util.NoSuchElementException if the stack is empty
|
||||
*/
|
||||
void redoLastChange();
|
||||
|
||||
/**
|
||||
* Peek at the top element of the undo stack.
|
||||
* The returned element must never be undone manually.
|
||||
* @return Top element on the undo stack, or {@code null} if the stack is empty
|
||||
*/
|
||||
Change peekUndoStack();
|
||||
|
||||
/**
|
||||
* Peek at the top element of the redo stack.
|
||||
* The returned element must never be redone manually.
|
||||
* @return Top element on the redo stack, or {@code null} if the stack is empty
|
||||
*/
|
||||
Change peekRedoStack();
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.replaymod.pathing.player;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.primitives.Longs;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.path.Path;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.replay.ReplayHandler;
|
||||
import net.minecraftforge.fml.common.FMLCommonHandler;
|
||||
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Plays a timeline.
|
||||
*/
|
||||
public abstract class AbstractTimelinePlayer {
|
||||
private final ReplayHandler replayHandler;
|
||||
private Timeline timeline;
|
||||
private long lastTimestamp;
|
||||
private ListenableFuture<Void> future;
|
||||
private SettableFuture<Void> settableFuture;
|
||||
|
||||
public AbstractTimelinePlayer(ReplayHandler replayHandler) {
|
||||
this.replayHandler = replayHandler;
|
||||
}
|
||||
|
||||
public ListenableFuture<Void> start(Timeline timeline) {
|
||||
this.timeline = timeline;
|
||||
|
||||
Iterator<Keyframe> iter = FluentIterable.from(timeline.getPaths())
|
||||
.transformAndConcat(new Function<Path, Iterable<Keyframe>>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Iterable<Keyframe> apply(@Nullable Path input) {
|
||||
assert input != null;
|
||||
return input.getKeyframes();
|
||||
}
|
||||
}).iterator();
|
||||
if (!iter.hasNext()) {
|
||||
lastTimestamp = 0;
|
||||
} else {
|
||||
lastTimestamp = new Ordering<Keyframe>() {
|
||||
@Override
|
||||
public int compare(@Nullable Keyframe left, @Nullable Keyframe right) {
|
||||
assert left != null;
|
||||
assert right != null;
|
||||
return Longs.compare(left.getTime(), right.getTime());
|
||||
}
|
||||
}.max(iter).getTime();
|
||||
}
|
||||
|
||||
replayHandler.getReplaySender().setSyncModeAndWait();
|
||||
FMLCommonHandler.instance().bus().register(this);
|
||||
return future = settableFuture = SettableFuture.create();
|
||||
}
|
||||
|
||||
public ListenableFuture<Void> getFuture() {
|
||||
return future;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return future != null && !future.isDone();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onTick(TickEvent.RenderTickEvent event) {
|
||||
if (future.isDone()) {
|
||||
replayHandler.getReplaySender().setAsyncMode(true);
|
||||
FMLCommonHandler.instance().bus().unregister(this);
|
||||
return;
|
||||
}
|
||||
long time = getTimePassed();
|
||||
if (time > lastTimestamp) {
|
||||
time = lastTimestamp;
|
||||
}
|
||||
timeline.applyToGame(time, replayHandler);
|
||||
if (time == lastTimestamp) {
|
||||
settableFuture.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract long getTimePassed();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.replaymod.pathing.player;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.replay.ReplayHandler;
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent;
|
||||
|
||||
/**
|
||||
* Timeline player using the system time.
|
||||
*/
|
||||
public class RealtimeTimelinePlayer extends AbstractTimelinePlayer {
|
||||
/**
|
||||
* Wether the next frame is the first frame.
|
||||
* We only start measuring time from the second frame
|
||||
* as the first might have to jump in time which might take time.
|
||||
*/
|
||||
private boolean firstFrame;
|
||||
private boolean secondFrame;
|
||||
|
||||
/**
|
||||
* System time in milliseconds at the start.
|
||||
*/
|
||||
private long startTime;
|
||||
|
||||
public RealtimeTimelinePlayer(ReplayHandler replayHandler) {
|
||||
super(replayHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Void> start(Timeline timeline) {
|
||||
firstFrame = true;
|
||||
return super.start(timeline);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTick(TickEvent.RenderTickEvent event) {
|
||||
if (secondFrame) {
|
||||
secondFrame = false;
|
||||
startTime = System.currentTimeMillis();
|
||||
}
|
||||
super.onTick(event);
|
||||
if (firstFrame) {
|
||||
firstFrame = false;
|
||||
secondFrame = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimePassed() {
|
||||
return firstFrame ? 0 : System.currentTimeMillis() - startTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.replaymod.pathing.properties;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.replaymod.pathing.change.Change;
|
||||
import com.replaymod.pathing.property.AbstractProperty;
|
||||
import com.replaymod.pathing.property.AbstractPropertyGroup;
|
||||
import com.replaymod.pathing.property.PropertyPart;
|
||||
import com.replaymod.replay.ReplayHandler;
|
||||
import lombok.NonNull;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Properties for camera positioning.
|
||||
*/
|
||||
public class CameraProperties extends AbstractPropertyGroup {
|
||||
public static final CameraProperties GROUP = new CameraProperties();
|
||||
public static final Position POSITION = new Position();
|
||||
public static final Rotation ROTATION = new Rotation();
|
||||
private CameraProperties() {
|
||||
super("camera", "replaymod.gui.camera");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Callable<ListenableFuture<Change>>> getSetter() {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
public static class Position extends AbstractProperty<Triple<Double, Double, Double>> {
|
||||
public final PropertyPart<Triple<Double, Double, Double>>
|
||||
X = new PropertyParts.ForDoubleTriple(this, true, PropertyParts.TripleElement.LEFT),
|
||||
Y = new PropertyParts.ForDoubleTriple(this, true, PropertyParts.TripleElement.MIDDLE),
|
||||
Z = new PropertyParts.ForDoubleTriple(this, true, PropertyParts.TripleElement.RIGHT);
|
||||
|
||||
private Position() {
|
||||
super("position", "replaymod.gui.position", GROUP, Triple.of(0d, 0d, 0d));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PropertyPart<Triple<Double, Double, Double>>> getParts() {
|
||||
return Arrays.asList(X, Y, Z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyToGame(Triple<Double, Double, Double> value, @NonNull ReplayHandler replayHandler) {
|
||||
replayHandler.getCameraEntity().setCameraPosition(value.getLeft(), value.getMiddle(), value.getRight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, Triple<Double, Double, Double> value) throws IOException {
|
||||
writer.beginArray().value(value.getLeft()).value(value.getMiddle()).value(value.getRight()).endArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Triple<Double, Double, Double> fromJson(JsonReader reader) throws IOException {
|
||||
reader.beginArray();
|
||||
try {
|
||||
return Triple.of(reader.nextDouble(), reader.nextDouble(), reader.nextDouble());
|
||||
} finally {
|
||||
reader.endArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Rotation extends AbstractProperty<Triple<Float, Float, Float>> {
|
||||
public final PropertyPart<Triple<Float, Float, Float>>
|
||||
YAW = new PropertyParts.ForFloatTriple(this, true, PropertyParts.TripleElement.LEFT),
|
||||
PITCH = new PropertyParts.ForFloatTriple(this, true, PropertyParts.TripleElement.MIDDLE),
|
||||
ROLL = new PropertyParts.ForFloatTriple(this, true, PropertyParts.TripleElement.RIGHT);
|
||||
|
||||
private Rotation() {
|
||||
super("rotation", "replaymod.gui.rotation", GROUP, Triple.of(0f, 0f, 0f));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PropertyPart<Triple<Float, Float, Float>>> getParts() {
|
||||
return Arrays.asList(YAW, PITCH, ROLL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyToGame(Triple<Float, Float, Float> value, @NonNull ReplayHandler replayHandler) {
|
||||
replayHandler.getCameraEntity().setCameraRotation(value.getLeft(), value.getMiddle(), value.getRight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, Triple<Float, Float, Float> value) throws IOException {
|
||||
writer.beginArray().value(value.getLeft()).value(value.getMiddle()).value(value.getRight()).endArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Triple<Float, Float, Float> fromJson(JsonReader reader) throws IOException {
|
||||
reader.beginArray();
|
||||
try {
|
||||
return Triple.of((float) reader.nextDouble(), (float) reader.nextDouble(), (float) reader.nextDouble());
|
||||
} finally {
|
||||
reader.endArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.replaymod.pathing.properties;
|
||||
|
||||
import com.replaymod.pathing.property.AbstractPropertyPart;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
|
||||
public class PropertyParts {
|
||||
private PropertyParts(){}
|
||||
|
||||
public static class ForInteger extends AbstractPropertyPart<Integer> {
|
||||
public ForInteger(Property<Integer> property, boolean interpolatable) {
|
||||
super(property, interpolatable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double toDouble(Integer value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer fromDouble(Integer value, double d) {
|
||||
return (int) Math.round(d);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ForDoubleTriple extends AbstractPropertyPart<Triple<Double, Double, Double>> {
|
||||
private final TripleElement element;
|
||||
public ForDoubleTriple(Property<Triple<Double, Double, Double>> property, boolean interpolatable, TripleElement element) {
|
||||
super(property, interpolatable);
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double toDouble(Triple<Double, Double, Double> value) {
|
||||
switch (element) {
|
||||
case LEFT: return value.getLeft();
|
||||
case MIDDLE: return value.getMiddle();
|
||||
case RIGHT: return value.getRight();
|
||||
}
|
||||
throw new AssertionError(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Triple<Double, Double, Double> fromDouble(Triple<Double, Double, Double> value, double d) {
|
||||
switch (element) {
|
||||
case LEFT: return Triple.of(d, value.getMiddle(), value.getRight());
|
||||
case MIDDLE: return Triple.of(value.getLeft(), d, value.getRight());
|
||||
case RIGHT: return Triple.of(value.getLeft(), value.getMiddle(), d);
|
||||
}
|
||||
throw new AssertionError(element);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ForFloatTriple extends AbstractPropertyPart<Triple<Float, Float, Float>> {
|
||||
private final TripleElement element;
|
||||
public ForFloatTriple(Property<Triple<Float, Float, Float>> property, boolean interpolatable, TripleElement element) {
|
||||
super(property, interpolatable);
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double toDouble(Triple<Float, Float, Float> value) {
|
||||
switch (element) {
|
||||
case LEFT: return value.getLeft();
|
||||
case MIDDLE: return value.getMiddle();
|
||||
case RIGHT: return value.getRight();
|
||||
}
|
||||
throw new AssertionError(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Triple<Float, Float, Float> fromDouble(Triple<Float, Float, Float> value, double d) {
|
||||
switch (element) {
|
||||
case LEFT: return Triple.of((float) d, value.getMiddle(), value.getRight());
|
||||
case MIDDLE: return Triple.of(value.getLeft(), (float) d, value.getRight());
|
||||
case RIGHT: return Triple.of(value.getLeft(), value.getMiddle(), (float) d);
|
||||
}
|
||||
throw new AssertionError(element);
|
||||
}
|
||||
}
|
||||
|
||||
public enum TripleElement {
|
||||
LEFT, MIDDLE, RIGHT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.replaymod.pathing.properties;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.replaymod.pathing.property.AbstractProperty;
|
||||
import com.replaymod.pathing.property.PropertyPart;
|
||||
import com.replaymod.replay.ReplayHandler;
|
||||
import com.replaymod.replay.ReplaySender;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Property for the time in the replay.
|
||||
*/
|
||||
public class TimestampProperty extends AbstractProperty<Integer> {
|
||||
public static final TimestampProperty PROPERTY = new TimestampProperty();
|
||||
public final PropertyPart<Integer> TIME = new PropertyParts.ForInteger(this, true);
|
||||
private TimestampProperty() {
|
||||
super("timestamp", "replaymod.gui.editkeyframe.timestamp", null, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<PropertyPart<Integer>> getParts() {
|
||||
return Collections.singletonList(TIME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyToGame(Integer value, @NonNull ReplayHandler replayHandler) {
|
||||
ReplaySender replaySender = replayHandler.getReplaySender();
|
||||
if (replaySender.isAsyncMode()) {
|
||||
replaySender.jumpToTime(value);
|
||||
} else {
|
||||
replaySender.sendPacketsTill(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, Integer value) throws IOException {
|
||||
writer.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer fromJson(JsonReader reader) throws IOException {
|
||||
return reader.nextInt();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.replaymod.pathing.property;
|
||||
|
||||
import net.minecraft.client.resources.I18n;
|
||||
|
||||
/**
|
||||
* Abstract base class for most properties.
|
||||
*/
|
||||
public abstract class AbstractProperty<T> implements Property<T> {
|
||||
private final String id, localizationKey;
|
||||
private final PropertyGroup propertyGroup;
|
||||
private final T initialValue;
|
||||
|
||||
public AbstractProperty(String id, String localizationKey, PropertyGroup propertyGroup, T initialValue) {
|
||||
this.id = id;
|
||||
this.localizationKey = localizationKey;
|
||||
this.propertyGroup = propertyGroup;
|
||||
this.initialValue = initialValue;
|
||||
|
||||
if (propertyGroup != null) {
|
||||
propertyGroup.getProperties().add(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocalizedName() {
|
||||
return I18n.format(localizationKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PropertyGroup getGroup() {
|
||||
return propertyGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getNewValue() {
|
||||
return initialValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.replaymod.pathing.property;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract base class for most property groups.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public abstract class AbstractPropertyGroup implements PropertyGroup {
|
||||
private final String id, localizationKey;
|
||||
private final List<Property> properties = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public String getLocalizedName() {
|
||||
return I18n.format(localizationKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Property> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.replaymod.pathing.property;
|
||||
|
||||
public abstract class AbstractPropertyPart<T> implements PropertyPart<T> {
|
||||
private final Property<T> property;
|
||||
private final boolean interpolatable;
|
||||
|
||||
public AbstractPropertyPart(Property<T> property, boolean interpolatable) {
|
||||
this.property = property;
|
||||
this.interpolatable = interpolatable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Property<T> getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInterpolatable() {
|
||||
return interpolatable;
|
||||
}
|
||||
}
|
||||
78
src/main/java/com/replaymod/pathing/property/Property.java
Normal file
78
src/main/java/com/replaymod/pathing/property/Property.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package com.replaymod.pathing.property;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.replaymod.replay.ReplayHandler;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Represents a property of a property.
|
||||
* Such properties may or may not be interpolated between keyframes.
|
||||
* <br>
|
||||
* If a property cannot be interpolated between keyframes, it is only active between two keyframes having the same
|
||||
* value for that property.
|
||||
*
|
||||
* @param <T> The type of the property, must be immutable
|
||||
*/
|
||||
public interface Property<T> {
|
||||
/**
|
||||
* Returns the localized name of this property.
|
||||
*
|
||||
* @return Localized name.
|
||||
*/
|
||||
@NonNull
|
||||
String getLocalizedName();
|
||||
|
||||
/**
|
||||
* Returns the group this property belongs to.
|
||||
*
|
||||
* @return The group
|
||||
*/
|
||||
PropertyGroup getGroup();
|
||||
|
||||
/**
|
||||
* Returns an ID unique for this property.
|
||||
* There may be multiple instances for the same property ID
|
||||
* if they all represent the same concept (all x-Coordinate).
|
||||
*
|
||||
* @return Unique ID
|
||||
*/
|
||||
@NonNull
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Returns a new value for this property.
|
||||
* @return New value, may be {@code null}
|
||||
*/
|
||||
T getNewValue();
|
||||
|
||||
/**
|
||||
* Returns (optionally) interpolatable parts of this property.
|
||||
* @return Collection of parts
|
||||
*/
|
||||
Collection<PropertyPart<T>> getParts();
|
||||
|
||||
/**
|
||||
* Appy the specified value of this property to the game.
|
||||
* @param value The value of this property
|
||||
* @param replayHandler The ReplayHandler instance
|
||||
*/
|
||||
void applyToGame(T value, ReplayHandler replayHandler);
|
||||
|
||||
/**
|
||||
* Writes the specified value of this property to JSON.
|
||||
* @param writer The json writer
|
||||
* @param value The value
|
||||
*/
|
||||
void toJson(JsonWriter writer, T value) throws IOException;
|
||||
|
||||
/**
|
||||
* Reads the value of this property from JSON.
|
||||
* @param reader The json reader
|
||||
* @return The value
|
||||
*/
|
||||
T fromJson(JsonReader reader) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.replaymod.pathing.property;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.replaymod.pathing.change.Change;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Represents a group of property properties.
|
||||
* These groups are displayed and stored together.
|
||||
* The same property id may exist in multiple groups.<br>
|
||||
* Groups may also define a way of setting their properties in one simple way (e.g. "set to current position").
|
||||
*/
|
||||
public interface PropertyGroup {
|
||||
/**
|
||||
* Returns the localized name of this group.
|
||||
*
|
||||
* @return Localized name.
|
||||
*/
|
||||
@NonNull
|
||||
String getLocalizedName();
|
||||
|
||||
/**
|
||||
* Returns an ID unique for this group.
|
||||
*
|
||||
* @return Unique ID
|
||||
*/
|
||||
@NonNull
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Return a list of all properties in this group.
|
||||
*
|
||||
* @return List of properties or empty list if none.
|
||||
*/
|
||||
@NonNull
|
||||
List<Property> getProperties();
|
||||
|
||||
/**
|
||||
* Return a Callable which can be used to set all properties in this group at once.
|
||||
* The Callable returns a future which is completed once all properties have been updated.
|
||||
* It is up to the caller to apply those changes to the path. If those changes are not applied
|
||||
* immediately, their content may become invalid and applying them results in undefined behavior.
|
||||
*
|
||||
* @return Optional callable
|
||||
*/
|
||||
@NonNull
|
||||
Optional<Callable<ListenableFuture<Change>>> getSetter();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.replaymod.pathing.property;
|
||||
|
||||
/**
|
||||
* Represents an (optionally) interpolatable part of a property (such as the X coordinate of the position property).
|
||||
* @param <T> Type of the property
|
||||
*/
|
||||
public interface PropertyPart<T> {
|
||||
Property<T> getProperty();
|
||||
|
||||
/**
|
||||
* Returns whether this part should be interpolated between keyframes.
|
||||
* Examples would be x/y/z coordinates or sun location.
|
||||
* Counterexamples are spectated entity id or
|
||||
* @return {@code true} if this part should be interpolated, {@code false} otherwise
|
||||
*/
|
||||
boolean isInterpolatable();
|
||||
|
||||
/**
|
||||
* Convert this part of the value to a double for interpolation.
|
||||
* @param value The value, may be {@code null}
|
||||
* @return A double representing this value
|
||||
* @throws UnsupportedOperationException if this part is not interpolatable
|
||||
*/
|
||||
double toDouble(T value);
|
||||
|
||||
/**
|
||||
* Convert the specified double to this part of the value and return it combined with the value for other parts.
|
||||
* @param value Value of other parts
|
||||
* @param d Value for this part
|
||||
* @return Combined value
|
||||
* @throws UnsupportedOperationException if this part is not interpolatable
|
||||
*/
|
||||
T fromDouble(T value, double d);
|
||||
}
|
||||
@@ -1,101 +1,121 @@
|
||||
package eu.crushedpixel.replaymod.utils;
|
||||
package com.replaymod.pathing.serialize;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import eu.crushedpixel.replaymod.assets.CustomImageObject;
|
||||
import eu.crushedpixel.replaymod.holders.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.replaymod.pathing.serialize.LegacyTimelineConverter.*;
|
||||
|
||||
public class LegacyKeyframeSetAdapter extends TypeAdapter<KeyframeSet[]> {
|
||||
|
||||
public LegacyKeyframeSetAdapter() {
|
||||
super();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public KeyframeSet[] read(JsonReader in) throws IOException {
|
||||
List<KeyframeSet> sets = new ArrayList<KeyframeSet>();
|
||||
List<KeyframeSet> sets = new ArrayList<>();
|
||||
|
||||
in.beginArray();
|
||||
while(in.hasNext()) { //iterate over all array entries
|
||||
|
||||
KeyframeSet set = new KeyframeSet();
|
||||
List<Keyframe> keyframes = new ArrayList<Keyframe>();
|
||||
List<Keyframe> positionKeyframes = new ArrayList<>();
|
||||
List<Keyframe> timeKeyframes = new ArrayList<>();
|
||||
|
||||
in.beginObject();
|
||||
while(in.hasNext()) { //iterate over all object entries
|
||||
String jsonTag = in.nextName();
|
||||
|
||||
if("name".equals(jsonTag)) {
|
||||
set.setName(in.nextString());
|
||||
set.name = in.nextString();
|
||||
|
||||
//TODO: Adapt to new Spectator Keyframe system
|
||||
} else if("positionKeyframes".equals(jsonTag)) {
|
||||
in.beginArray();
|
||||
while(in.hasNext()) {
|
||||
Keyframe<AdvancedPosition> newKeyframe = new Keyframe<AdvancedPosition>();
|
||||
Keyframe<AdvancedPosition> newKeyframe = new Keyframe<>();
|
||||
Integer spectatedEntityID = null;
|
||||
in.beginObject();
|
||||
while(in.hasNext()) {
|
||||
String jsonKeyframeTag = in.nextName();
|
||||
if("value".equals(jsonKeyframeTag) || "position".equals(jsonKeyframeTag)) {
|
||||
SpectatorData spectatorData = new Gson().fromJson(in, SpectatorData.class);
|
||||
newKeyframe.setValue(spectatorData.normalize());
|
||||
if (spectatorData.spectatedEntityID != null) {
|
||||
newKeyframe.value = spectatorData;
|
||||
} else {
|
||||
newKeyframe.value = new AdvancedPosition();
|
||||
newKeyframe.value.x = spectatorData.x;
|
||||
newKeyframe.value.y = spectatorData.y;
|
||||
newKeyframe.value.z = spectatorData.z;
|
||||
newKeyframe.value.yaw = spectatorData.yaw;
|
||||
newKeyframe.value.pitch = spectatorData.pitch;
|
||||
newKeyframe.value.roll = spectatorData.roll;
|
||||
}
|
||||
} else if("realTimestamp".equals(jsonKeyframeTag)) {
|
||||
newKeyframe.setRealTimestamp(in.nextInt());
|
||||
newKeyframe.realTimestamp = in.nextInt();
|
||||
} else if("spectatedEntityID".equals(jsonKeyframeTag)) {
|
||||
spectatedEntityID = in.nextInt();
|
||||
}
|
||||
}
|
||||
|
||||
if(spectatedEntityID != null) {
|
||||
newKeyframe.setValue(newKeyframe.getValue().asSpectatorData(spectatedEntityID));
|
||||
AdvancedPosition pos = newKeyframe.value;
|
||||
SpectatorData spectatorData = new SpectatorData();
|
||||
spectatorData.spectatedEntityID = spectatedEntityID;
|
||||
newKeyframe.value = spectatorData;
|
||||
newKeyframe.value.x = pos.x;
|
||||
newKeyframe.value.y = pos.y;
|
||||
newKeyframe.value.z = pos.z;
|
||||
newKeyframe.value.yaw = pos.yaw;
|
||||
newKeyframe.value.pitch = pos.pitch;
|
||||
newKeyframe.value.roll = pos.roll;
|
||||
}
|
||||
|
||||
in.endObject();
|
||||
|
||||
keyframes.add(newKeyframe);
|
||||
positionKeyframes.add(newKeyframe);
|
||||
}
|
||||
in.endArray();
|
||||
|
||||
} else if("timeKeyframes".equals(jsonTag)) {
|
||||
in.beginArray();
|
||||
while(in.hasNext()) {
|
||||
Keyframe<TimestampValue> newKeyframe = new Keyframe<TimestampValue>();
|
||||
Keyframe<TimestampValue> newKeyframe = new Keyframe<>();
|
||||
|
||||
in.beginObject();
|
||||
while(in.hasNext()) {
|
||||
String jsonKeyframeTag = in.nextName();
|
||||
if("timestamp".equals(jsonKeyframeTag)) {
|
||||
TimestampValue timestampValue = new TimestampValue(in.nextInt());
|
||||
newKeyframe.setValue(timestampValue);
|
||||
TimestampValue timestampValue = new TimestampValue();
|
||||
timestampValue.value = in.nextInt();
|
||||
newKeyframe.value = timestampValue;
|
||||
} else if("value".equals(jsonKeyframeTag)) {
|
||||
TimestampValue timestampValue = new Gson().fromJson(in, TimestampValue.class);
|
||||
newKeyframe.setValue(timestampValue);
|
||||
newKeyframe.value = new Gson().fromJson(in, TimestampValue.class);
|
||||
} else if("realTimestamp".equals(jsonKeyframeTag)) {
|
||||
newKeyframe.setRealTimestamp(in.nextInt());
|
||||
newKeyframe.realTimestamp = in.nextInt();
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
keyframes.add(newKeyframe);
|
||||
timeKeyframes.add(newKeyframe);
|
||||
}
|
||||
in.endArray();
|
||||
|
||||
} else if("customObjects".equals(jsonTag)) {
|
||||
CustomImageObject[] customObjects = new Gson().fromJson(in, CustomImageObject[].class);
|
||||
|
||||
set.setCustomObjects(customObjects);
|
||||
set.customObjects = new Gson().fromJson(in, CustomImageObject[].class);
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
set.setKeyframes(keyframes.toArray(new Keyframe[keyframes.size()]));
|
||||
set.positionKeyframes = positionKeyframes.toArray(new Keyframe[positionKeyframes.size()]);
|
||||
set.timeKeyframes = timeKeyframes.toArray(new Keyframe[timeKeyframes.size()]);
|
||||
sets.add(set);
|
||||
}
|
||||
in.endArray();
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.replaymod.pathing.serialize;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.replaymod.pathing.PathingRegistry;
|
||||
import com.replaymod.pathing.path.Path;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.pathing.properties.CameraProperties;
|
||||
import com.replaymod.pathing.properties.TimestampProperty;
|
||||
import de.johni0702.replaystudio.replay.ReplayFile;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.*;
|
||||
|
||||
public class LegacyTimelineConverter {
|
||||
public static Map<String, Timeline> convert(PathingRegistry registry, ReplayFile replayFile) throws IOException {
|
||||
KeyframeSet[] keyframeSets = readAndParse(replayFile);
|
||||
if (keyframeSets == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, Timeline> timelines = new LinkedHashMap<>();
|
||||
for (KeyframeSet keyframeSet : keyframeSets) {
|
||||
timelines.put(keyframeSet.name, convert(registry, keyframeSet));
|
||||
}
|
||||
return timelines;
|
||||
}
|
||||
|
||||
private static Optional<InputStream> read(ReplayFile replayFile) throws IOException {
|
||||
Optional<InputStream> in = replayFile.get("paths.json");
|
||||
if (!in.isPresent()) {
|
||||
in = replayFile.get("paths");
|
||||
}
|
||||
return in;
|
||||
}
|
||||
|
||||
private static KeyframeSet[] parse(InputStream in) {
|
||||
return new GsonBuilder()
|
||||
.registerTypeAdapter(KeyframeSet[].class, new LegacyKeyframeSetAdapter())
|
||||
.create().fromJson(new InputStreamReader(in), KeyframeSet[].class);
|
||||
}
|
||||
|
||||
private static KeyframeSet[] readAndParse(ReplayFile replayFile) throws IOException {
|
||||
Optional<InputStream> optIn = read(replayFile);
|
||||
if (!optIn.isPresent()) {
|
||||
return null;
|
||||
}
|
||||
KeyframeSet[] keyframeSets;
|
||||
try (InputStream in = optIn.get()) {
|
||||
keyframeSets = parse(in);
|
||||
}
|
||||
return keyframeSets;
|
||||
}
|
||||
|
||||
private static Timeline convert(PathingRegistry registry, KeyframeSet keyframeSet) {
|
||||
Path path = registry.createTimeline().createPath();
|
||||
for (Keyframe<AdvancedPosition> positionKeyframe : keyframeSet.positionKeyframes) {
|
||||
AdvancedPosition value = positionKeyframe.value;
|
||||
com.replaymod.pathing.path.Keyframe keyframe = getKeyframe(path, positionKeyframe.realTimestamp);
|
||||
keyframe.setValue(CameraProperties.POSITION, Triple.of(value.x, value.y, value.z));
|
||||
keyframe.setValue(CameraProperties.ROTATION, Triple.of(value.yaw, value.pitch, value.roll));
|
||||
if (value instanceof SpectatorData) {
|
||||
// TODO Spectator keyframes
|
||||
}
|
||||
}
|
||||
for (Keyframe<TimestampValue> timeKeyframe : keyframeSet.timeKeyframes) {
|
||||
TimestampValue value = timeKeyframe.value;
|
||||
com.replaymod.pathing.path.Keyframe keyframe = getKeyframe(path, timeKeyframe.realTimestamp);
|
||||
keyframe.setValue(TimestampProperty.PROPERTY, (int) value.value);
|
||||
}
|
||||
return path.getTimeline();
|
||||
}
|
||||
|
||||
private static com.replaymod.pathing.path.Keyframe getKeyframe(Path path, long time) {
|
||||
com.replaymod.pathing.path.Keyframe keyframe = path.getKeyframe(time);
|
||||
if (keyframe == null) {
|
||||
keyframe = path.insert(time);
|
||||
}
|
||||
return keyframe;
|
||||
}
|
||||
|
||||
static class KeyframeSet {
|
||||
String name;
|
||||
Keyframe<AdvancedPosition>[] positionKeyframes;
|
||||
Keyframe<TimestampValue>[] timeKeyframes;
|
||||
CustomImageObject[] customObjects;
|
||||
}
|
||||
static class Keyframe<T> {
|
||||
int realTimestamp;
|
||||
T value;
|
||||
}
|
||||
static class Position {
|
||||
double x, y, z;
|
||||
}
|
||||
static class AdvancedPosition extends Position {
|
||||
float pitch, yaw, roll;
|
||||
}
|
||||
static class SpectatorData extends AdvancedPosition {
|
||||
Integer spectatedEntityID;
|
||||
SpectatingMethod spectatingMethod;
|
||||
SpectatorDataThirdPersonInfo thirdPersonInfo;
|
||||
enum SpectatingMethod {
|
||||
FIRST_PERSON, SHOULDER_CAM;
|
||||
}
|
||||
}
|
||||
static class SpectatorDataThirdPersonInfo {
|
||||
double shoulderCamDistance;
|
||||
double shoulderCamPitchOffset;
|
||||
double shoulderCamYawOffset;
|
||||
double shoulderCamSmoothness;
|
||||
}
|
||||
static class TimestampValue {
|
||||
double value;
|
||||
}
|
||||
static class CustomImageObject {
|
||||
String name;
|
||||
UUID linkedAsset;
|
||||
float width, height;
|
||||
float textureWidth, textureHeight;
|
||||
Transformations transformations = new Transformations();
|
||||
}
|
||||
static class Transformations {
|
||||
Position defaultAnchor, defaultPosition, defaultOrientation, defaultScale;
|
||||
NumberValue defaultOpacity;
|
||||
|
||||
List<Position> anchorKeyframes;
|
||||
List<Position> positionKeyframes;
|
||||
List<Position> orientationKeyframes;
|
||||
List<Position> scaleKeyframes;
|
||||
List<NumberValue> opacityKeyframes;
|
||||
}
|
||||
static class NumberValue {
|
||||
double value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package com.replaymod.pathing.serialize;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.replaymod.pathing.PathingRegistry;
|
||||
import com.replaymod.pathing.interpolation.Interpolator;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.path.Path;
|
||||
import com.replaymod.pathing.path.PathSegment;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.pathing.property.Property;
|
||||
import de.johni0702.replaystudio.replay.ReplayFile;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public class TimelineSerialization {
|
||||
private static final String FILE_ENTRY = "timelines.json";
|
||||
|
||||
private final PathingRegistry registry;
|
||||
private final ReplayFile replayFile;
|
||||
|
||||
public TimelineSerialization(PathingRegistry registry, ReplayFile replayFile) {
|
||||
this.registry = registry;
|
||||
this.replayFile = replayFile;
|
||||
}
|
||||
|
||||
public void save(Map<String, Timeline> timelines) throws IOException {
|
||||
String serialized = serialize(timelines);
|
||||
try (OutputStream out = replayFile.write(FILE_ENTRY)) {
|
||||
out.write(serialized.getBytes(Charsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Timeline> load() throws IOException {
|
||||
Map<String, Timeline> timelines = LegacyTimelineConverter.convert(registry, replayFile);
|
||||
|
||||
Optional<InputStream> optionalIn = replayFile.get(FILE_ENTRY);
|
||||
if (optionalIn.isPresent()) {
|
||||
String serialized;
|
||||
try (InputStream in = optionalIn.get()) {
|
||||
serialized = IOUtils.toString(in, Charsets.UTF_8);
|
||||
}
|
||||
Map<String, Timeline> deserialized = deserialize(serialized);
|
||||
timelines.putAll(deserialized);
|
||||
}
|
||||
return timelines;
|
||||
}
|
||||
|
||||
public String serialize(Map<String, Timeline> timelines) throws IOException {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
JsonWriter writer = new JsonWriter(stringWriter);
|
||||
writer.beginObject();
|
||||
for (Map.Entry<String, Timeline> entry : timelines.entrySet()) {
|
||||
Timeline timeline = entry.getValue();
|
||||
writer.name(entry.getKey()).beginArray();
|
||||
for (Path path : timeline.getPaths()) {
|
||||
writer.beginObject();
|
||||
writer.name("keyframes").beginArray();
|
||||
for (Keyframe keyframe : path.getKeyframes()) {
|
||||
writer.beginObject();
|
||||
writer.name("time").value(keyframe.getTime());
|
||||
writer.name("properties").beginObject();
|
||||
for (Property<?> property : keyframe.getProperties()) {
|
||||
writer.name((property.getGroup() == null ? "" : property.getGroup().getId()) + property.getId());
|
||||
writeProperty(writer, keyframe, property);
|
||||
}
|
||||
writer.endObject();
|
||||
writer.endObject();
|
||||
}
|
||||
writer.endArray();
|
||||
Map<Interpolator, Integer> interpolators = new LinkedHashMap<>();
|
||||
writer.name("segments").beginArray();
|
||||
for (PathSegment segment : path.getSegments()) {
|
||||
Interpolator interpolator = segment.getInterpolator();
|
||||
if (interpolator == null) {
|
||||
writer.nullValue();
|
||||
} else {
|
||||
Integer index = interpolators.get(interpolator);
|
||||
if (index == null) {
|
||||
interpolators.put(interpolator, index = interpolators.size());
|
||||
}
|
||||
writer.value(index);
|
||||
}
|
||||
}
|
||||
writer.endArray();
|
||||
writer.name("interpolators").beginArray();
|
||||
for (Interpolator interpolator : interpolators.keySet()) {
|
||||
registry.serializeInterpolator(writer, interpolator);
|
||||
}
|
||||
writer.endArray();
|
||||
writer.endObject();
|
||||
}
|
||||
writer.endArray();
|
||||
}
|
||||
writer.endObject();
|
||||
writer.flush();
|
||||
return stringWriter.toString();
|
||||
}
|
||||
|
||||
private static <T> void writeProperty(JsonWriter writer, Keyframe keyframe, Property<T> property) throws IOException {
|
||||
property.toJson(writer, keyframe.getValue(property).get());
|
||||
}
|
||||
|
||||
public Map<String, Timeline> deserialize(String serialized) throws IOException {
|
||||
JsonReader reader = new JsonReader(new StringReader(serialized));
|
||||
Map<String, Timeline> timelines = new LinkedHashMap<>();
|
||||
reader.beginObject();
|
||||
while (reader.hasNext()) {
|
||||
Timeline timeline = registry.createTimeline();
|
||||
timelines.put(reader.nextName(), timeline);
|
||||
reader.beginArray();
|
||||
while (reader.hasNext()) {
|
||||
Path path = timeline.createPath();
|
||||
reader.beginObject();
|
||||
List<Integer> segments = new ArrayList<>();
|
||||
List<Interpolator> interpolators = new ArrayList<>();
|
||||
while (reader.hasNext()) {
|
||||
switch (reader.nextName()) {
|
||||
case "keyframes":
|
||||
reader.beginArray();
|
||||
while (reader.hasNext()) {
|
||||
long time = 0;
|
||||
Map<Property, Object> properties = new HashMap<>();
|
||||
reader.beginObject();
|
||||
while (reader.hasNext()) {
|
||||
switch (reader.nextName()) {
|
||||
case "time":
|
||||
time = reader.nextLong();
|
||||
break;
|
||||
case "properties":
|
||||
reader.beginObject();
|
||||
while (reader.hasNext()) {
|
||||
String id = reader.nextName();
|
||||
Property property = timeline.getProperty(id);
|
||||
if (property == null) {
|
||||
throw new IOException("Unknown property: " + id);
|
||||
}
|
||||
Object value = property.fromJson(reader);
|
||||
properties.put(property, value);
|
||||
}
|
||||
reader.endObject();
|
||||
break;
|
||||
}
|
||||
}
|
||||
reader.endObject();
|
||||
Keyframe keyframe = path.insert(time);
|
||||
for (Map.Entry<Property, Object> entry : properties.entrySet()) {
|
||||
keyframe.setValue(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
reader.endArray();
|
||||
break;
|
||||
case "segments":
|
||||
reader.beginArray();
|
||||
while (reader.hasNext()) {
|
||||
if (reader.peek() == JsonToken.NULL) {
|
||||
reader.nextNull();
|
||||
segments.add(null);
|
||||
} else {
|
||||
segments.add(reader.nextInt());
|
||||
}
|
||||
}
|
||||
reader.endArray();
|
||||
break;
|
||||
case "interpolators":
|
||||
reader.beginArray();
|
||||
while (reader.hasNext()) {
|
||||
interpolators.add(registry.deserializeInterpolator(reader));
|
||||
}
|
||||
reader.endArray();
|
||||
break;
|
||||
}
|
||||
}
|
||||
Iterator<Integer> iter = segments.iterator();
|
||||
for (PathSegment segment : path.getSegments()) {
|
||||
Integer next = iter.next();
|
||||
if (next != null) {
|
||||
segment.setInterpolator(interpolators.get(next));
|
||||
}
|
||||
}
|
||||
reader.endObject();
|
||||
}
|
||||
reader.endArray();
|
||||
}
|
||||
reader.endObject();
|
||||
return timelines;
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@ package com.replaymod.replay;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.common.util.concurrent.ListenableFutureTask;
|
||||
import com.replaymod.core.utils.Restrictions;
|
||||
import com.replaymod.replay.camera.CameraEntity;
|
||||
import de.johni0702.replaystudio.replay.ReplayFile;
|
||||
import eu.crushedpixel.replaymod.holders.PacketData;
|
||||
import com.replaymod.core.utils.Restrictions;
|
||||
import eu.crushedpixel.replaymod.settings.ReplaySettings;
|
||||
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
|
||||
import io.netty.channel.ChannelHandler.Sharable;
|
||||
@@ -191,6 +191,10 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAsyncMode() {
|
||||
return asyncMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this replay sender to operate in sync mode.
|
||||
* When in sync mode, it will send packets when {@link #sendPacketsTill(int)} is called.
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.replaymod.simplepathing;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.pathing.PathingRegistry;
|
||||
import com.replaymod.pathing.impl.TimelineImpl;
|
||||
import com.replaymod.pathing.interpolation.AbstractInterpolator;
|
||||
import com.replaymod.pathing.interpolation.Interpolator;
|
||||
import com.replaymod.pathing.interpolation.LinearInterpolator;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.pathing.properties.CameraProperties;
|
||||
import com.replaymod.pathing.properties.TimestampProperty;
|
||||
import com.replaymod.replay.events.ReplayOpenEvent;
|
||||
import com.replaymod.simplepathing.gui.GuiPathing;
|
||||
import net.minecraftforge.fml.common.FMLCommonHandler;
|
||||
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 java.io.IOException;
|
||||
|
||||
@Mod(modid = ReplayModSimplePathing.MOD_ID, useMetadata = true)
|
||||
public class ReplayModSimplePathing implements PathingRegistry {
|
||||
public static final String MOD_ID = "replaymod-simplepathing";
|
||||
|
||||
@Mod.Instance(ReplayMod.MOD_ID)
|
||||
private static ReplayMod core;
|
||||
|
||||
private Logger logger;
|
||||
|
||||
@Mod.EventHandler
|
||||
public void preInit(FMLPreInitializationEvent event) {
|
||||
logger = event.getModLog();
|
||||
|
||||
core.getSettingsRegistry().register(Setting.class);
|
||||
|
||||
FMLCommonHandler.instance().bus().register(this);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void postReplayOpen(ReplayOpenEvent.Post event) {
|
||||
new GuiPathing(core, this, event.getReplayHandler());
|
||||
}
|
||||
|
||||
private Timeline currentTimeline = createTimeline(); { currentTimeline.createPath(); }
|
||||
private Keyframe selectedKeyframe;
|
||||
|
||||
public Keyframe getSelectedKeyframe() {
|
||||
return selectedKeyframe;
|
||||
}
|
||||
|
||||
public void setSelectedKeyframe(Keyframe selectedKeyframe) {
|
||||
this.selectedKeyframe = selectedKeyframe;
|
||||
}
|
||||
|
||||
public void setCurrentTimeline(Timeline currentTimeline) {
|
||||
this.currentTimeline = currentTimeline;
|
||||
}
|
||||
|
||||
public Timeline getCurrentTimeline() {
|
||||
return currentTimeline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Timeline createTimeline() {
|
||||
Timeline timeline = new TimelineImpl();
|
||||
|
||||
timeline.registerProperty(TimestampProperty.PROPERTY);
|
||||
timeline.registerProperty(CameraProperties.POSITION);
|
||||
timeline.registerProperty(CameraProperties.ROTATION);
|
||||
|
||||
return timeline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serializeInterpolator(JsonWriter writer, Interpolator interpolator) throws IOException {
|
||||
if (interpolator instanceof LinearInterpolator) {
|
||||
writer.value("linear");
|
||||
} else {
|
||||
throw new IOException("Unknown interpolator type: " + interpolator);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Interpolator deserializeInterpolator(JsonReader reader) throws IOException {
|
||||
AbstractInterpolator interpolator;
|
||||
String type = reader.nextString();
|
||||
switch (type) {
|
||||
case "linear":
|
||||
interpolator = new LinearInterpolator();
|
||||
interpolator.registerProperty(TimestampProperty.PROPERTY);
|
||||
interpolator.registerProperty(CameraProperties.POSITION);
|
||||
interpolator.registerProperty(CameraProperties.ROTATION);
|
||||
return interpolator;
|
||||
default:
|
||||
throw new IOException("Unknown interpolation type: " + type);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/main/java/com/replaymod/simplepathing/Setting.java
Normal file
17
src/main/java/com/replaymod/simplepathing/Setting.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.replaymod.simplepathing;
|
||||
|
||||
import com.replaymod.core.SettingsRegistry;
|
||||
|
||||
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);
|
||||
|
||||
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("simplepathing", key, "replaymod.gui.settings." + displayString, defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.replaymod.simplepathing.gui;
|
||||
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.properties.CameraProperties;
|
||||
import com.replaymod.pathing.properties.TimestampProperty;
|
||||
import com.replaymod.simplepathing.ReplayModSimplePathing;
|
||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||
import de.johni0702.minecraft.gui.element.advanced.AbstractGuiTimeline;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
|
||||
public class GuiKeyframeTimeline extends AbstractGuiTimeline<GuiKeyframeTimeline> {
|
||||
protected static final int KEYFRAME_SIZE = 5;
|
||||
protected static final int KEYFRAME_TEXTURE_X = 74;
|
||||
protected static final int KEYFRAME_TEXTURE_Y = 20;
|
||||
|
||||
private final GuiPathing gui;
|
||||
|
||||
public GuiKeyframeTimeline(GuiPathing gui) {
|
||||
this.gui = gui;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawTimelineCursor(GuiRenderer renderer, ReadableDimension size) {
|
||||
ReplayModSimplePathing mod = gui.getMod();
|
||||
|
||||
int width = size.getWidth();
|
||||
int visibleWidth = width - BORDER_LEFT - BORDER_RIGHT;
|
||||
int startTime = getOffset();
|
||||
int visibleTime = (int) (getZoom() * getLength());
|
||||
int endTime = getOffset() + visibleTime;
|
||||
|
||||
renderer.bindTexture(ReplayMod.TEXTURE);
|
||||
|
||||
for (Keyframe keyframe : mod.getCurrentTimeline().getPaths().get(0).getKeyframes()) {
|
||||
if (keyframe.getTime() >= startTime && keyframe.getTime() <= endTime) {
|
||||
double relativeTime = keyframe.getTime() - startTime;
|
||||
int positonX = BORDER_LEFT + (int) (relativeTime / visibleTime * visibleWidth) - KEYFRAME_SIZE / 2;
|
||||
int u = KEYFRAME_TEXTURE_X + (mod.getSelectedKeyframe() == keyframe ? KEYFRAME_SIZE : 0);
|
||||
int v = KEYFRAME_TEXTURE_Y;
|
||||
if (keyframe.getValue(CameraProperties.POSITION).isPresent()) {
|
||||
renderer.drawTexturedRect(positonX, BORDER_TOP, u, v, KEYFRAME_SIZE, KEYFRAME_SIZE);
|
||||
}
|
||||
if (keyframe.getValue(TimestampProperty.PROPERTY).isPresent()) {
|
||||
v += KEYFRAME_SIZE;
|
||||
renderer.drawTexturedRect(positonX, BORDER_TOP + KEYFRAME_SIZE, u, v, KEYFRAME_SIZE, KEYFRAME_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.drawTimelineCursor(renderer, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GuiKeyframeTimeline getThis() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
346
src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java
Normal file
346
src/main/java/com/replaymod/simplepathing/gui/GuiPathing.java
Normal file
@@ -0,0 +1,346 @@
|
||||
package com.replaymod.simplepathing.gui;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.replaymod.core.ReplayMod;
|
||||
import com.replaymod.pathing.change.AddKeyframe;
|
||||
import com.replaymod.pathing.change.CombinedChange;
|
||||
import com.replaymod.pathing.change.UpdateKeyframeProperties;
|
||||
import com.replaymod.pathing.gui.GuiKeyframeRepository;
|
||||
import com.replaymod.pathing.interpolation.LinearInterpolator;
|
||||
import com.replaymod.pathing.path.Keyframe;
|
||||
import com.replaymod.pathing.path.Path;
|
||||
import com.replaymod.pathing.path.PathSegment;
|
||||
import com.replaymod.pathing.path.Timeline;
|
||||
import com.replaymod.pathing.player.RealtimeTimelinePlayer;
|
||||
import com.replaymod.pathing.properties.CameraProperties;
|
||||
import com.replaymod.pathing.properties.TimestampProperty;
|
||||
import com.replaymod.replay.ReplayHandler;
|
||||
import com.replaymod.replay.camera.CameraEntity;
|
||||
import com.replaymod.replay.gui.overlay.GuiReplayOverlay;
|
||||
import com.replaymod.simplepathing.ReplayModSimplePathing;
|
||||
import de.johni0702.minecraft.gui.GuiRenderer;
|
||||
import de.johni0702.minecraft.gui.RenderInfo;
|
||||
import de.johni0702.minecraft.gui.container.GuiPanel;
|
||||
import de.johni0702.minecraft.gui.element.*;
|
||||
import de.johni0702.minecraft.gui.element.advanced.GuiTimelineTime;
|
||||
import de.johni0702.minecraft.gui.element.advanced.IGuiTimeline;
|
||||
import de.johni0702.minecraft.gui.layout.CustomLayout;
|
||||
import de.johni0702.minecraft.gui.layout.HorizontalLayout;
|
||||
import de.johni0702.minecraft.gui.layout.VerticalLayout;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
import org.lwjgl.util.Dimension;
|
||||
import org.lwjgl.util.ReadableDimension;
|
||||
import org.lwjgl.util.ReadablePoint;
|
||||
import org.lwjgl.util.WritablePoint;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Gui plug-in to the GuiReplayOverlay for simple pathing.
|
||||
*/
|
||||
public class GuiPathing {
|
||||
public final GuiTexturedButton playPauseButton = new GuiTexturedButton().setSize(20, 20)
|
||||
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE);
|
||||
|
||||
public final GuiTexturedButton positionKeyframeButton = new GuiTexturedButton().setSize(20, 20)
|
||||
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE);
|
||||
|
||||
public final GuiTexturedButton timeKeyframeButton = new GuiTexturedButton().setSize(20, 20)
|
||||
.setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE);
|
||||
|
||||
public final GuiKeyframeTimeline timeline = new GuiKeyframeTimeline(this){
|
||||
@Override
|
||||
public void draw(GuiRenderer renderer, ReadableDimension size, RenderInfo renderInfo) {
|
||||
if (player.isActive()) {
|
||||
setCursorPosition((int) player.getTimePassed());
|
||||
}
|
||||
super.draw(renderer, size, renderInfo);
|
||||
}
|
||||
}.onClick(new IGuiTimeline.OnClick() {
|
||||
@Override
|
||||
public void run(int time) {
|
||||
timeline.setCursorPosition(time);
|
||||
// TODO: Keyframe selection
|
||||
mod.setSelectedKeyframe(null);
|
||||
}
|
||||
}).setSize(Integer.MAX_VALUE, 20).setLength(30 * 60 * 1000).setMarkers();
|
||||
|
||||
public final GuiHorizontalScrollbar scrollbar = new GuiHorizontalScrollbar().setSize(Integer.MAX_VALUE, 9);
|
||||
{scrollbar.onValueChanged(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
timeline.setOffset((int) (scrollbar.getPosition() * timeline.getLength()));
|
||||
timeline.setZoom(scrollbar.getZoom());
|
||||
}
|
||||
}).setZoom(0.1);}
|
||||
|
||||
public final GuiTimelineTime<GuiKeyframeTimeline> timelineTime = new GuiTimelineTime<GuiKeyframeTimeline>()
|
||||
.setTimeline(timeline);
|
||||
|
||||
public final GuiTexturedButton zoomInButton = new GuiTexturedButton().setSize(9, 9).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
zoomTimeline(2d / 3d);
|
||||
}
|
||||
}).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 20);
|
||||
|
||||
public final GuiTexturedButton zoomOutButton = new GuiTexturedButton().setSize(9, 9).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
zoomTimeline(3d / 2d);
|
||||
}
|
||||
}).setTexture(ReplayMod.TEXTURE, ReplayMod.TEXTURE_SIZE).setTexturePosH(40, 30);
|
||||
|
||||
public final GuiPanel zoomButtonPanel = new GuiPanel()
|
||||
.setLayout(new VerticalLayout(VerticalLayout.Alignment.CENTER).setSpacing(2))
|
||||
.addElements(null, zoomInButton, zoomOutButton);
|
||||
|
||||
public final GuiPanel timelinePanel = new GuiPanel().setSize(Integer.MAX_VALUE, 40)
|
||||
.setLayout(new CustomLayout<GuiPanel>() {
|
||||
@Override
|
||||
protected void layout(GuiPanel container, int width, int height) {
|
||||
pos(zoomButtonPanel, width - width(zoomButtonPanel), 10);
|
||||
pos(timelineTime, 0, 2);
|
||||
size(timelineTime, x(zoomButtonPanel), 8);
|
||||
pos(timeline, 0, y(timelineTime) + height(timelineTime));
|
||||
size(timeline, x(zoomButtonPanel) - 2, 20);
|
||||
pos(scrollbar, 0, y(timeline) + height(timeline) + 1);
|
||||
size(scrollbar, x(zoomButtonPanel) - 2, 9);
|
||||
}
|
||||
}).addElements(null, timelineTime, timeline, scrollbar, zoomButtonPanel);
|
||||
|
||||
public final GuiPanel panel = new GuiPanel()
|
||||
.setLayout(new HorizontalLayout(HorizontalLayout.Alignment.CENTER).setSpacing(5))
|
||||
.addElements(new HorizontalLayout.Data(0.5), playPauseButton, positionKeyframeButton, timeKeyframeButton,
|
||||
timelinePanel);
|
||||
|
||||
/**
|
||||
* IGuiClickable dummy component that is inserted at a high level.
|
||||
* During path playback, this catches all click events and forwards them to the
|
||||
* abort path playback button.
|
||||
* Dragging does not have to be intercepted as every GUI element should only
|
||||
* respond to dragging events after it has received and handled a click event.
|
||||
*/
|
||||
private final IGuiClickable clickCatcher = new AbstractGuiClickable() {
|
||||
@Override
|
||||
protected AbstractGuiElement getThis() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReadableDimension calcMinSize() {
|
||||
return new Dimension(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClick(ReadablePoint position, int button) {
|
||||
if (player.isActive()) {
|
||||
playPauseButton.mouseClick(position, button);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLayer() {
|
||||
return player.isActive() ? 10 : 0;
|
||||
}
|
||||
};
|
||||
|
||||
private final ReplayModSimplePathing mod;
|
||||
private final ReplayHandler replayHandler;
|
||||
private final RealtimeTimelinePlayer player;
|
||||
|
||||
public GuiPathing(final ReplayMod core, final ReplayModSimplePathing mod, final ReplayHandler replayHandler) {
|
||||
this.mod = mod;
|
||||
this.replayHandler = replayHandler;
|
||||
this.player = new RealtimeTimelinePlayer(replayHandler);
|
||||
final GuiReplayOverlay overlay = replayHandler.getOverlay();
|
||||
|
||||
playPauseButton.setTexturePosH(new ReadablePoint() {
|
||||
@Override
|
||||
public int getX() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getY() {
|
||||
return player.isActive() ? 20 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getLocation(WritablePoint dest) {
|
||||
dest.setLocation(getX(), getY());
|
||||
}
|
||||
}).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (player.isActive()) {
|
||||
player.getFuture().cancel(false);
|
||||
} else {
|
||||
Timeline timeline = mod.getCurrentTimeline();
|
||||
Path path = timeline.getPaths().get(0);
|
||||
|
||||
// TODO Change interpolator via Change class
|
||||
LinearInterpolator interpolator = new LinearInterpolator();
|
||||
interpolator.registerProperty(TimestampProperty.PROPERTY);
|
||||
interpolator.registerProperty(CameraProperties.POSITION);
|
||||
interpolator.registerProperty(CameraProperties.ROTATION);
|
||||
for (PathSegment segment : path.getSegments()) {
|
||||
segment.setInterpolator(interpolator);
|
||||
}
|
||||
path.updateAll();
|
||||
|
||||
player.start(timeline);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
positionKeyframeButton.setTexturePosH(new ReadablePoint() {
|
||||
@Override
|
||||
public int getX() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getY() {
|
||||
Keyframe keyframe = mod.getSelectedKeyframe();
|
||||
boolean present = keyframe != null
|
||||
&& keyframe.getValue(CameraProperties.POSITION).isPresent();
|
||||
return present ? 60 : 40;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getLocation(WritablePoint dest) {
|
||||
dest.setLocation(getX(), getY());
|
||||
}
|
||||
}).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateKeyframe(false);
|
||||
}
|
||||
});
|
||||
|
||||
timeKeyframeButton.setTexturePosH(new ReadablePoint() {
|
||||
@Override
|
||||
public int getX() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getY() {
|
||||
Keyframe keyframe = mod.getSelectedKeyframe();
|
||||
boolean present = keyframe != null
|
||||
&& keyframe.getValue(TimestampProperty.PROPERTY).isPresent();
|
||||
return present ? 100 : 80;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getLocation(WritablePoint dest) {
|
||||
dest.setLocation(getX(), getY());
|
||||
}
|
||||
}).onClick(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateKeyframe(true);
|
||||
}
|
||||
});
|
||||
|
||||
overlay.addElements(null, panel, clickCatcher);
|
||||
overlay.setLayout(new CustomLayout<GuiReplayOverlay>(overlay.getLayout()) {
|
||||
@Override
|
||||
protected void layout(GuiReplayOverlay container, int width, int height) {
|
||||
pos(panel, 10, y(overlay.topPanel) + height(overlay.topPanel) + 3);
|
||||
size(panel, width - 20, 40);
|
||||
size(clickCatcher, 0, 0);
|
||||
}
|
||||
});
|
||||
|
||||
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, replayHandler.getReplayFile(), mod.getCurrentTimeline());
|
||||
Futures.addCallback(gui.getFuture(), new FutureCallback<Timeline>() {
|
||||
@Override
|
||||
public void onSuccess(Timeline result) {
|
||||
if (result != null) {
|
||||
mod.setCurrentTimeline(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());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void zoomTimeline(double factor) {
|
||||
scrollbar.setZoom(scrollbar.getZoom() * factor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when either one of the property buttons is pressed.
|
||||
* @param isTime {@code true} for the time property button, {@code false} for the place property button
|
||||
*/
|
||||
private void updateKeyframe(final boolean isTime) {
|
||||
int time = timeline.getCursorPosition();
|
||||
Timeline timeline = mod.getCurrentTimeline();
|
||||
Path path = timeline.getPaths().get(0);
|
||||
|
||||
AddKeyframe addChange = null;
|
||||
Keyframe keyframe = path.getKeyframe(time);
|
||||
if (keyframe == null) {
|
||||
addChange = AddKeyframe.create(path, time);
|
||||
addChange.apply(timeline);
|
||||
keyframe = path.getKeyframe(time);
|
||||
}
|
||||
|
||||
UpdateKeyframeProperties.Builder builder = UpdateKeyframeProperties.create(path, keyframe);
|
||||
if (isTime) {
|
||||
if (keyframe.getValue(TimestampProperty.PROPERTY).isPresent()) {
|
||||
builder.removeProperty(TimestampProperty.PROPERTY);
|
||||
} else {
|
||||
builder.setValue(TimestampProperty.PROPERTY, replayHandler.getReplaySender().currentTimeStamp());
|
||||
}
|
||||
} else {
|
||||
if (keyframe.getValue(CameraProperties.POSITION).isPresent()) {
|
||||
builder.removeProperty(CameraProperties.POSITION);
|
||||
builder.removeProperty(CameraProperties.ROTATION);
|
||||
} else {
|
||||
CameraEntity camera = replayHandler.getCameraEntity();
|
||||
builder.setValue(CameraProperties.POSITION, Triple.of(camera.posX, camera.posY, camera.posZ));
|
||||
builder.setValue(CameraProperties.ROTATION, Triple.of(camera.rotationYaw, camera.rotationPitch, camera.roll));
|
||||
}
|
||||
}
|
||||
UpdateKeyframeProperties updateChange = builder.done();
|
||||
updateChange.apply(timeline);
|
||||
if (addChange == null) {
|
||||
timeline.pushChange(updateChange);
|
||||
} else {
|
||||
timeline.pushChange(CombinedChange.createFromApplied(addChange, updateChange));
|
||||
}
|
||||
|
||||
mod.setSelectedKeyframe(keyframe);
|
||||
}
|
||||
|
||||
public ReplayModSimplePathing getMod() {
|
||||
return mod;
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,7 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
public void onGuiClosed() {
|
||||
// TODO
|
||||
// if(!save) {
|
||||
// ReplayHandler.removeKeyframe(keyframe);
|
||||
// ReplayHandler.removeKeyframe(property);
|
||||
// ReplayHandler.addKeyframe(keyframeBackup);
|
||||
// ReplayHandler.selectKeyframe(keyframeBackup);
|
||||
// }
|
||||
@@ -218,8 +218,8 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
// private static class GuiEditKeyframeMarker extends GuiEditKeyframe<Marker> {
|
||||
// private GuiAdvancedTextField markerNameInput;
|
||||
//
|
||||
// public GuiEditKeyframeMarker(Keyframe<Marker> keyframe) {
|
||||
// super(keyframe, ReplayHandler.getMarkerKeyframes());
|
||||
// public GuiEditKeyframeMarker(Keyframe<Marker> property) {
|
||||
// super(property, ReplayHandler.getMarkerKeyframes());
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
@@ -227,7 +227,7 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
// super.initGui();
|
||||
//
|
||||
// if (!initialized) {
|
||||
// String name = keyframe.getValue().getName();
|
||||
// String name = property.getValue().getName();
|
||||
// if (name == null) name = "";
|
||||
// markerNameInput = new GuiAdvancedTextField(fontRendererObj, 0, 0, 200, 20);
|
||||
// markerNameInput.hint = I18n.format("replaymod.gui.editkeyframe.markername");
|
||||
@@ -246,7 +246,7 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
//
|
||||
// @Override
|
||||
// public void onGuiClosed() {
|
||||
// keyframe.getValue().setName(markerNameInput.getText().trim());
|
||||
// property.getValue().setName(markerNameInput.getText().trim());
|
||||
// super.onGuiClosed();
|
||||
// }
|
||||
// }
|
||||
@@ -254,8 +254,8 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
// private static class GuiEditKeyframeTime extends GuiEditKeyframe<TimestampValue> {
|
||||
// private GuiNumberInput kfMin, kfSec, kfMs;
|
||||
//
|
||||
// public GuiEditKeyframeTime(Keyframe<TimestampValue> keyframe) {
|
||||
// super(keyframe, ReplayHandler.getTimeKeyframes());
|
||||
// public GuiEditKeyframeTime(Keyframe<TimestampValue> property) {
|
||||
// super(property, ReplayHandler.getTimeKeyframes());
|
||||
// screenTitle = I18n.format("replaymod.gui.editkeyframe.title.time");
|
||||
// }
|
||||
//
|
||||
@@ -264,7 +264,7 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
// super.initGui();
|
||||
//
|
||||
// if (!initialized) {
|
||||
// int time = keyframe.getValue().asInt();
|
||||
// int time = property.getValue().asInt();
|
||||
//
|
||||
// kfMin = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 30, 0d, null, (double)TimestampUtils.timestampToWholeMinutes(time), false, "", 1);
|
||||
// kfSec = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 25, 0d, 59d, (double)TimestampUtils.getSecondsFromTimestamp(time), false, "", 1);
|
||||
@@ -277,7 +277,7 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
// KeyframeValueChangeListener listener = new KeyframeValueChangeListener(this) {
|
||||
// @Override
|
||||
// public void onValueChange(double value) {
|
||||
// keyframe.setValue(new TimestampValue(TimestampUtils.calculateTimestamp(
|
||||
// property.setValue(new TimestampValue(TimestampUtils.calculateTimestamp(
|
||||
// kfMin.getIntValue(), kfSec.getIntValue(), kfMs.getIntValue())));
|
||||
//
|
||||
// super.onValueChange(value);
|
||||
@@ -312,8 +312,8 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
// private GuiNumberInput xCoord, yCoord, zCoord, pitch, yaw, roll;
|
||||
// private ComposedElement posInputs;
|
||||
//
|
||||
// public GuiEditKeyframePosition(Keyframe<AdvancedPosition> keyframe) {
|
||||
// super(keyframe, ReplayHandler.getPositionKeyframes());
|
||||
// public GuiEditKeyframePosition(Keyframe<AdvancedPosition> property) {
|
||||
// super(property, ReplayHandler.getPositionKeyframes());
|
||||
// screenTitle = I18n.format("replaymod.gui.editkeyframe.title.pos");
|
||||
// }
|
||||
//
|
||||
@@ -322,7 +322,7 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
// super.initGui();
|
||||
//
|
||||
// if (!initialized) {
|
||||
// AdvancedPosition pos = keyframe.getValue();
|
||||
// AdvancedPosition pos = property.getValue();
|
||||
// xCoord = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 100, null, null, RoundUtils.round2Decimals(pos.getX()), true);
|
||||
// yCoord = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 100, null, null, RoundUtils.round2Decimals(pos.getY()), true);
|
||||
// zCoord = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 100, null, null, RoundUtils.round2Decimals(pos.getZ()), true);
|
||||
@@ -336,7 +336,7 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
// KeyframeValueChangeListener listener = new KeyframeValueChangeListener(this) {
|
||||
// @Override
|
||||
// public void onValueChange(double value) {
|
||||
// keyframe.setValue(new AdvancedPosition(xCoord.getPreciseValue(), yCoord.getPreciseValue(),
|
||||
// property.setValue(new AdvancedPosition(xCoord.getPreciseValue(), yCoord.getPreciseValue(),
|
||||
// zCoord.getPreciseValue(), new Float(pitch.getPreciseValue()), (float) yaw.getPreciseValue(),
|
||||
// (float) roll.getPreciseValue()));
|
||||
//
|
||||
@@ -378,7 +378,7 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
//
|
||||
// @Override
|
||||
// protected void drawScreen0() {
|
||||
// if (keyframe.getValue() instanceof SpectatorData) return;
|
||||
// if (property.getValue() instanceof SpectatorData) return;
|
||||
// drawString(fontRendererObj, I18n.format("replaymod.gui.editkeyframe.xpos"), left, virtualY + 27, Color.WHITE.getRGB());
|
||||
// drawString(fontRendererObj, I18n.format("replaymod.gui.editkeyframe.ypos"), left, virtualY + 57, Color.WHITE.getRGB());
|
||||
// drawString(fontRendererObj, I18n.format("replaymod.gui.editkeyframe.zpos"), left, virtualY + 87, Color.WHITE.getRGB());
|
||||
@@ -413,8 +413,8 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// public GuiEditKeyframeSpectator(Keyframe<AdvancedPosition> keyframe) {
|
||||
// super(keyframe, ReplayHandler.getPositionKeyframes());
|
||||
// public GuiEditKeyframeSpectator(Keyframe<AdvancedPosition> property) {
|
||||
// super(property, ReplayHandler.getPositionKeyframes());
|
||||
// screenTitle = I18n.format("replaymod.gui.editkeyframe.title.spec");
|
||||
// }
|
||||
//
|
||||
@@ -423,7 +423,7 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
// super.initGui();
|
||||
//
|
||||
// if (!initialized) {
|
||||
// SpectatorData data = (SpectatorData)keyframe.getValue();
|
||||
// SpectatorData data = (SpectatorData)property.getValue();
|
||||
//
|
||||
// perspectiveButton = new GuiToggleButton(0, 0, 0, 200, 20, I18n.format("replaymod.gui.editkeyframe.spec.method")+": ", new String[]{
|
||||
// I18n.format("replaymod.gui.editkeyframe.spec.method.firstperson"),
|
||||
@@ -481,7 +481,7 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
//
|
||||
// @Override
|
||||
// public void onGuiClosed() {
|
||||
// SpectatorData data = (SpectatorData)keyframe.getValue();
|
||||
// SpectatorData data = (SpectatorData)property.getValue();
|
||||
// data.setSpectatingMethod(SpectatorData.SpectatingMethod.values()[perspectiveButton.getValue()]);
|
||||
// data.getThirdPersonInfo().shoulderCamDistance = shoulderDistanceInput.getPreciseValue();
|
||||
// data.getThirdPersonInfo().shoulderCamPitchOffset = shoulderPitchOffsetInput.getIntValue();
|
||||
@@ -506,7 +506,7 @@ public abstract class GuiEditKeyframe<T extends KeyframeValue> extends GuiScreen
|
||||
// @Override
|
||||
// public void onValueChange(double value) {
|
||||
// int realTimestamp = TimestampUtils.calculateTimestamp(parent.min.getIntValue(), parent.sec.getIntValue(), parent.ms.getIntValue());
|
||||
// parent.keyframe.setRealTimestamp(realTimestamp);
|
||||
// parent.property.setRealTimestamp(realTimestamp);
|
||||
//
|
||||
// ReplayHandler.fireKeyframesModifyEvent();
|
||||
// }
|
||||
|
||||
@@ -459,7 +459,7 @@ public class GuiObjectManager extends GuiScreen {
|
||||
// public void onValueChange(double value) {
|
||||
// NumberValue numberValue = new NumberValue(value);
|
||||
//
|
||||
// //if keyframe selected, overwrite its value
|
||||
// //if property selected, overwrite its value
|
||||
// if(toModify.contains(objectKeyframeTimeline.getSelectedKeyframe())) {
|
||||
// objectKeyframeTimeline.getSelectedKeyframe().setValue(numberValue);
|
||||
// } else {
|
||||
@@ -488,7 +488,7 @@ public class GuiObjectManager extends GuiScreen {
|
||||
// public void onValueChange(double value) {
|
||||
// Position position = new Position(xInput.getPreciseValue(), yInput.getPreciseValue(), zInput.getPreciseValue());
|
||||
//
|
||||
// //if keyframe selected, overwrite its value
|
||||
// //if property selected, overwrite its value
|
||||
// if(toModify.contains(objectKeyframeTimeline.getSelectedKeyframe())) {
|
||||
// objectKeyframeTimeline.getSelectedKeyframe().setValue(position);
|
||||
// } else {
|
||||
|
||||
@@ -47,7 +47,7 @@ public class GuiKeyframeTimeline extends GuiTimeline {
|
||||
//
|
||||
// //left mouse button
|
||||
// if(button == 0) {
|
||||
// ReplayHandler.selectKeyframe(closest); //can be null, deselects keyframe
|
||||
// ReplayHandler.selectKeyframe(closest); //can be null, deselects property
|
||||
//
|
||||
// // If we clicked on a key frame, then continue monitoring the mouse for movements
|
||||
// long currentTime = System.currentTimeMillis();
|
||||
@@ -241,7 +241,7 @@ public class GuiKeyframeTimeline extends GuiTimeline {
|
||||
// textureY = KEYFRAME_TIME_Y;
|
||||
// y += 5;
|
||||
// } else {
|
||||
// throw new UnsupportedOperationException("Unknown keyframe type: " + kf.getClass());
|
||||
// throw new UnsupportedOperationException("Unknown property type: " + kf.getClass());
|
||||
// }
|
||||
//
|
||||
// if (ReplayHandler.isSelected(kf)) {
|
||||
|
||||
@@ -58,11 +58,11 @@ public class AdvancedPositionKeyframeList extends KeyframeList<AdvancedPosition>
|
||||
// TimestampValue timestampValue = ReplayHandler.getTimeKeyframes().getInterpolatedValueForTimestamp(keyframeTimestamp, true);
|
||||
// int replayTimestamp = timestampValue == null ? 0 : timestampValue.asInt();
|
||||
// if(firstTimestamp == -1) firstTimestamp = replayTimestamp;
|
||||
// spectatorInterpolation.addPoint(keyframe, replayTimestamp);
|
||||
// spectatorInterpolation.addPoint(property, replayTimestamp);
|
||||
// if(iterator.hasNext()) {
|
||||
// keyframe = iterator.next();
|
||||
// found = keyframe.getValue() instanceof SpectatorData;
|
||||
// if(!found) completedKeyframes.add(keyframe);
|
||||
// property = iterator.next();
|
||||
// found = property.getValue() instanceof SpectatorData;
|
||||
// if(!found) completedKeyframes.add(property);
|
||||
// } else {
|
||||
// found = false;
|
||||
// }
|
||||
|
||||
@@ -54,7 +54,7 @@ public class SpectatorDataInterpolation {
|
||||
}
|
||||
thirdPersonInfoInterpolation.prepare();
|
||||
|
||||
//updating the spectator keyframe's position in the world to smoothly continue the path
|
||||
//updating the spectator property's position in the world to smoothly continue the path
|
||||
//with non-spectator position keyframes
|
||||
for(Pair<Integer, Keyframe<AdvancedPosition>> pair : points) {
|
||||
// TODO
|
||||
@@ -74,7 +74,7 @@ public class SpectatorDataInterpolation {
|
||||
// pair.getValue().getValue().apply(entityPosition);
|
||||
}
|
||||
|
||||
//feed the underlying keyframe list with AdvancedPosition Keyframes that are derived from the Spectator Keyframes
|
||||
//feed the underlying property list with AdvancedPosition Keyframes that are derived from the Spectator Keyframes
|
||||
underlyingKeyframes = new KeyframeList<AdvancedPosition>();
|
||||
int i = 0;
|
||||
int firstTimestamp = -1;
|
||||
|
||||
@@ -40,7 +40,6 @@ public class KeybindRegistry {
|
||||
replayModKeyBindings.add(new KeyBinding(KEY_RESET_TILT, Keyboard.KEY_K, "replaymod.title"));
|
||||
|
||||
replayModKeyBindings.add(new KeyBinding(KEY_CLEAR_KEYFRAMES, Keyboard.KEY_C, "replaymod.title"));
|
||||
replayModKeyBindings.add(new KeyBinding(KEY_KEYFRAME_PRESETS, Keyboard.KEY_X, "replaymod.title"));
|
||||
replayModKeyBindings.add(new KeyBinding(KEY_PATH_PREVIEW, Keyboard.KEY_H, "replaymod.title"));
|
||||
|
||||
replayModKeyBindings.add(new KeyBinding(KEY_SYNC_TIMELINE, Keyboard.KEY_V, "replaymod.title"));
|
||||
|
||||
@@ -55,8 +55,8 @@ public class PathPreviewRenderer {
|
||||
// AdvancedPosition previousPosition = null;
|
||||
//
|
||||
// int i = 0;
|
||||
// for(Keyframe<AdvancedPosition> keyframe : keyframes) {
|
||||
// int timestamp = keyframe.getRealTimestamp();
|
||||
// for(Keyframe<AdvancedPosition> property : keyframes) {
|
||||
// int timestamp = property.getRealTimestamp();
|
||||
// int nextTimestamp = timestamp;
|
||||
//
|
||||
// if(i+1 < keyframes.size()) {
|
||||
|
||||
@@ -168,38 +168,38 @@ public class ReplayHandler {
|
||||
// // TODO
|
||||
// }
|
||||
//
|
||||
// public static void addTimeKeyframe(Keyframe<TimestampValue> keyframe) {
|
||||
// timeKeyframes.add(keyframe);
|
||||
// selectKeyframe(keyframe);
|
||||
// public static void addTimeKeyframe(Keyframe<TimestampValue> property) {
|
||||
// timeKeyframes.add(property);
|
||||
// selectKeyframe(property);
|
||||
//
|
||||
// fireKeyframesModifyEvent();
|
||||
// }
|
||||
//
|
||||
// public static void addPositionKeyframe(Keyframe<AdvancedPosition> keyframe) {
|
||||
// positionKeyframes.add(keyframe);
|
||||
// selectKeyframe(keyframe);
|
||||
// public static void addPositionKeyframe(Keyframe<AdvancedPosition> property) {
|
||||
// positionKeyframes.add(property);
|
||||
// selectKeyframe(property);
|
||||
//
|
||||
// fireKeyframesModifyEvent();
|
||||
// }
|
||||
//
|
||||
// @SuppressWarnings("unchecked")
|
||||
// public static void addKeyframe(Keyframe keyframe) {
|
||||
// if(keyframe.getValue() instanceof AdvancedPosition) {
|
||||
// addPositionKeyframe(keyframe);
|
||||
// } else if(keyframe.getValue() instanceof TimestampValue) {
|
||||
// addTimeKeyframe(keyframe);
|
||||
// public static void addKeyframe(Keyframe property) {
|
||||
// if(property.getValue() instanceof AdvancedPosition) {
|
||||
// addPositionKeyframe(property);
|
||||
// } else if(property.getValue() instanceof TimestampValue) {
|
||||
// addTimeKeyframe(property);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public static void removeKeyframe(Keyframe keyframe) {
|
||||
// if(keyframe.getValue() instanceof AdvancedPosition) {
|
||||
// positionKeyframes.remove(keyframe);
|
||||
// } else if(keyframe.getValue() instanceof TimestampValue) {
|
||||
// timeKeyframes.remove(keyframe);
|
||||
// public static void removeKeyframe(Keyframe property) {
|
||||
// if(property.getValue() instanceof AdvancedPosition) {
|
||||
// positionKeyframes.remove(property);
|
||||
// } else if(property.getValue() instanceof TimestampValue) {
|
||||
// timeKeyframes.remove(property);
|
||||
// }
|
||||
// // TODO Marker
|
||||
//
|
||||
// if(keyframe == selectedKeyframe) {
|
||||
// if(property == selectedKeyframe) {
|
||||
// selectKeyframe(null);
|
||||
// }
|
||||
//
|
||||
@@ -485,14 +485,14 @@ public class ReplayHandler {
|
||||
//
|
||||
// int prevTime, prevRealTime;
|
||||
//
|
||||
// Keyframe<TimestampValue> keyframe = timeKeyframes.last();
|
||||
// Keyframe<TimestampValue> property = timeKeyframes.last();
|
||||
//
|
||||
// if(keyframe == null) {
|
||||
// if(property == null) {
|
||||
// prevTime = 0;
|
||||
// prevRealTime = 0;
|
||||
// } else {
|
||||
// prevTime = (int)keyframe.getValue().value;
|
||||
// prevRealTime = keyframe.getRealTimestamp();
|
||||
// prevTime = (int)property.getValue().value;
|
||||
// prevRealTime = property.getRealTimestamp();
|
||||
// }
|
||||
//
|
||||
// double speed = ignoreReplaySpeed ? 1 : ReplayMod.overlay.getSpeedSliderValue();
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.google.common.base.Optional;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.replaymod.pathing.serialize.LegacyKeyframeSetAdapter;
|
||||
import de.johni0702.replaystudio.PacketData;
|
||||
import de.johni0702.replaystudio.filter.ChangeTimestampFilter;
|
||||
import de.johni0702.replaystudio.filter.NeutralizerFilter;
|
||||
@@ -19,7 +20,6 @@ import eu.crushedpixel.replaymod.gui.elements.listeners.ProgressUpdateListener;
|
||||
import eu.crushedpixel.replaymod.holders.Keyframe;
|
||||
import eu.crushedpixel.replaymod.holders.KeyframeSet;
|
||||
import eu.crushedpixel.replaymod.holders.TimestampValue;
|
||||
import eu.crushedpixel.replaymod.utils.LegacyKeyframeSetAdapter;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
@@ -86,6 +86,9 @@ replaymod.gui.loading=Loading
|
||||
replaymod.gui.pleasewait=Please wait
|
||||
replaymod.gui.render=Render
|
||||
replaymod.gui.iphidden=Server IP Hidden
|
||||
replaymod.gui.overwrite=Overwrite
|
||||
replaymod.gui.saveas=Save as ...
|
||||
replaymod.gui.done=Done
|
||||
|
||||
replaymod.gui.original=Original
|
||||
replaymod.gui.modified=Modified
|
||||
@@ -101,6 +104,9 @@ replaymod.gui.pitch=Pitch
|
||||
replaymod.gui.yaw=Yaw
|
||||
replaymod.gui.roll=Roll
|
||||
|
||||
replaymod.gui.camera=Camera
|
||||
replaymod.gui.position=Position
|
||||
|
||||
replaymod.gui.unknownerror=An unknown error occured
|
||||
|
||||
replaymod.gui.exit=Exit Replay
|
||||
@@ -113,6 +119,8 @@ replaymod.gui.loggedout=LOGGED OUT
|
||||
replaymod.gui.mainmenu=Main Menu
|
||||
replaymod.gui.settings=Settings
|
||||
|
||||
replaymod.gui.keyframerepo.delete=Are you sure you want to delete these keyframes?
|
||||
|
||||
#Only change these if it's neccessary
|
||||
replaymod.gui.replayviewer=Replay Viewer
|
||||
replaymod.gui.replaycenter=Replay Center
|
||||
|
||||
@@ -66,6 +66,23 @@
|
||||
"screenshots": [],
|
||||
"dependencies": ["required-after:replaymod-replay"]
|
||||
},
|
||||
{
|
||||
"modid": "replaymod-simplepathing",
|
||||
"name": "Replay Mod - Simple Pathing",
|
||||
"description": "Simple Pathing Module of the ReplayMod - aka the original pathing",
|
||||
"version": "${version}",
|
||||
"mcversion": "${mcversion}",
|
||||
"url": "https://replaymod.com",
|
||||
"updateUrl": "https://replaymod.com/download",
|
||||
"authorList": [
|
||||
"CrushedPixel",
|
||||
"johni0702"
|
||||
],
|
||||
"logoFile": "replaymod_logo.png",
|
||||
"parent": "replaymod",
|
||||
"screenshots": [],
|
||||
"dependencies": ["required-after:replaymod-replay"]
|
||||
},
|
||||
{
|
||||
"modid": "replaymod-extras",
|
||||
"name": "Replay Mod - Extras",
|
||||
|
||||
Reference in New Issue
Block a user