Add common pathing code and SimplePathing module

This commit is contained in:
johni0702
2016-03-10 12:31:37 +01:00
parent cf9879b198
commit 626429a780
51 changed files with 3296 additions and 77 deletions

View File

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

View 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);
}
}

View File

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

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