Markers are now subclasses of Keyframe

Markers can now be moved and jumped to on the Replay Timeline
This commit is contained in:
CrushedPixel
2015-06-09 22:55:20 +02:00
parent d30ef19c89
commit b2ad52d5b6
13 changed files with 133 additions and 99 deletions

View File

@@ -152,6 +152,12 @@ public class CameraEntity extends EntityPlayer {
direction = direction.normalize().add(new Vec3(oldDir.xCoord * (motion / 4f), oldDir.yCoord * (motion / 4f), oldDir.zCoord * (motion / 4f)).normalize());
}
public void moveAbsolute(Position pos) {
this.moveAbsolute(pos.getX(), pos.getY(), pos.getZ());
rotationPitch = pos.getPitch();
rotationYaw = pos.getYaw();
}
public void moveAbsolute(double x, double y, double z) {
if(ReplayHandler.isInPath()) return;
this.lastTickPosX = this.prevPosX = this.posX = x;

View File

@@ -1,7 +1,9 @@
package eu.crushedpixel.replaymod.gui.elements;
import eu.crushedpixel.replaymod.ReplayMod;
import eu.crushedpixel.replaymod.gui.GuiEditKeyframe;
import eu.crushedpixel.replaymod.holders.Keyframe;
import eu.crushedpixel.replaymod.holders.MarkerKeyframe;
import eu.crushedpixel.replaymod.holders.PositionKeyframe;
import eu.crushedpixel.replaymod.holders.TimeKeyframe;
import eu.crushedpixel.replaymod.replay.ReplayHandler;
@@ -16,15 +18,23 @@ public class GuiKeyframeTimeline extends GuiTimeline {
private static final int KEYFRAME_TIME_Y = 25;
private static final int KEYFRAME_SPEC_X = 74;
private static final int KEYFRAME_SPEC_Y = 30;
private static final int KEYFRAME_MARKER_X = 40;
private static final int KEYFRAME_MARKER_Y = 39;
private Keyframe clickedKeyFrame;
private long clickTime;
private boolean dragging;
private boolean markerKeyframes;
private boolean timeKeyframes;
private boolean placeKeyframes;
public GuiKeyframeTimeline(int positionX, int positionY, int width) {
public GuiKeyframeTimeline(int positionX, int positionY, int width, boolean showMarkers,
boolean showMarkerKeyframes, boolean showPlaceKeyframes, boolean showTimeKeyframes) {
super(positionX, positionY, width);
showMarkers = true;
showMarkerIndicators = false;
this.showMarkers = showMarkers;
this.markerKeyframes = showMarkerKeyframes;
this.timeKeyframes = showTimeKeyframes;
this.placeKeyframes = showPlaceKeyframes;
}
@Override
@@ -34,13 +44,15 @@ public class GuiKeyframeTimeline extends GuiTimeline {
return;
}
int tolerance = (int) (2 * Math.round(zoom * timelineLength / width));
int tolerance = (int)(2 * Math.round(zoom * timelineLength / width));
Keyframe closest;
if (mouseY >= positionY + BORDER_TOP + 5) {
if (mouseY >= positionY + BORDER_TOP + 5 && timeKeyframes) {
closest = ReplayHandler.getClosestTimeKeyframeForRealTime((int) time, tolerance);
} else if (mouseY >= positionY + BORDER_TOP) {
} else if (mouseY >= positionY + BORDER_TOP && placeKeyframes) {
closest = ReplayHandler.getClosestPlaceKeyframeForRealTime((int) time, tolerance);
} else if (mouseY >= positionY + BORDER_TOP + 10 && markerKeyframes) {
closest = ReplayHandler.getClosestMarkerForRealTime((int) time, tolerance);
} else {
closest = null;
}
@@ -85,6 +97,10 @@ public class GuiKeyframeTimeline extends GuiTimeline {
@Override
public void mouseRelease(Minecraft mc, int mouseX, int mouseY, int button) {
mouseDrag(mc, mouseX, mouseY, button);
if(clickedKeyFrame instanceof MarkerKeyframe && dragging == false) {
ReplayMod.replaySender.jumpToTime(clickedKeyFrame.getRealTimestamp());
ReplayHandler.setLastPosition(((MarkerKeyframe)clickedKeyFrame).getPosition());
}
clickedKeyFrame = null;
dragging = false;
}
@@ -169,7 +185,8 @@ public class GuiKeyframeTimeline extends GuiTimeline {
int keyframeX = getKeyframeX(kf.getRealTimestamp(), leftTime, bodyWidth, segmentLength);
if (kf instanceof PositionKeyframe) {
if(kf instanceof PositionKeyframe) {
if(!placeKeyframes) return;
textureX = KEYFRAME_PLACE_X;
textureY = KEYFRAME_PLACE_Y;
y += 0;
@@ -179,10 +196,16 @@ public class GuiKeyframeTimeline extends GuiTimeline {
textureX = KEYFRAME_SPEC_X;
textureY = KEYFRAME_SPEC_Y;
}
} else if (kf instanceof TimeKeyframe) {
} else if(kf instanceof TimeKeyframe) {
if(!timeKeyframes) return;
textureX = KEYFRAME_TIME_X;
textureY = KEYFRAME_TIME_Y;
y += 5;
} else if(kf instanceof MarkerKeyframe) {
if(!markerKeyframes) return;
textureX = KEYFRAME_MARKER_X;
textureY = KEYFRAME_MARKER_Y;
y += 10;
} else {
throw new UnsupportedOperationException("Unknown keyframe type: " + kf.getClass());
}

View File

@@ -1,8 +1,6 @@
package eu.crushedpixel.replaymod.gui.elements;
import eu.crushedpixel.replaymod.ReplayMod;
import eu.crushedpixel.replaymod.holders.Marker;
import eu.crushedpixel.replaymod.replay.ReplayHandler;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
@@ -27,11 +25,6 @@ public class GuiTimeline extends Gui implements GuiElement {
protected static final int BORDER_RIGHT = 4;
protected static final int BODY_WIDTH = TEXTURE_WIDTH - BORDER_LEFT - BORDER_RIGHT;
protected static final int MARKER_TEXTURE_X = 40;
protected static final int MARKER_TEXTURE_Y = 39;
protected static final int MARKER_TEXTURE_WIDTH = 5;
protected static final int MARKER_TEXTURE_HEIGHT = 5;
/**
* Current position of the cursor. Should normally be between 0 and {@link #timelineLength}.
*/
@@ -59,11 +52,6 @@ public class GuiTimeline extends Gui implements GuiElement {
*/
public boolean showMarkers;
/**
* Whether to draw indicators for Replay Markers.
*/
public boolean showMarkerIndicators = true;
protected final int positionX;
protected final int positionY;
protected final int width;
@@ -150,26 +138,6 @@ public class GuiTimeline extends Gui implements GuiElement {
}
}
if(showMarkerIndicators) {
double segmentLength = timelineLength * zoom;
for(Marker marker : ReplayHandler.getMarkers()) {
if(marker.getTimestamp() <= rightTime && marker.getTimestamp() >= leftTime) {
int textureX = MARKER_TEXTURE_X;
int textureY = MARKER_TEXTURE_Y;
int y = positionY;
int markerX = getMarkerX(marker.getTimestamp(), leftTime, bodyWidth, segmentLength);
if(ReplayHandler.isSelected(marker)) {
textureX += MARKER_TEXTURE_WIDTH;
}
rect(markerX - 2, y + HEIGHT - 3 - MARKER_TEXTURE_HEIGHT, textureX, textureY, MARKER_TEXTURE_WIDTH, MARKER_TEXTURE_HEIGHT);
}
}
}
drawTimelineCursor(leftTime, rightTime, bodyWidth);
}

View File

@@ -7,6 +7,7 @@ import eu.crushedpixel.replaymod.gui.GuiMouseInput;
import eu.crushedpixel.replaymod.gui.GuiRenderSettings;
import eu.crushedpixel.replaymod.gui.GuiReplaySpeedSlider;
import eu.crushedpixel.replaymod.gui.elements.*;
import eu.crushedpixel.replaymod.holders.MarkerKeyframe;
import eu.crushedpixel.replaymod.holders.Position;
import eu.crushedpixel.replaymod.holders.PositionKeyframe;
import eu.crushedpixel.replaymod.holders.TimeKeyframe;
@@ -181,6 +182,28 @@ public class GuiReplayOverlay extends Gui {
}
};
private final GuiElement buttonMarker = new DelegatingElement() {
private final GuiElement buttonNotSelected = texturedButton(BUTTON_TIME_X, BOTTOM_ROW, 0, 80, 20, new Runnable() {
@Override
public void run() {
ReplayHandler.addKeyframe(new TimeKeyframe(ReplayHandler.getRealTimelineCursor(), ReplayMod.replaySender.currentTimeStamp()));
}
}, "replaymod.gui.ingame.menu.addtimekeyframe");
private final GuiElement buttonSelected = texturedButton(BUTTON_TIME_X, BOTTOM_ROW, 0, 100, 20, new Runnable() {
@Override
public void run() {
ReplayHandler.removeKeyframe(ReplayHandler.getSelectedKeyframe());
}
}, "replaymod.gui.ingame.menu.removetimekeyframe");
@Override
public GuiElement delegate() {
return ReplayHandler.getSelectedKeyframe() instanceof TimeKeyframe ? buttonSelected : buttonNotSelected;
}
};
private final GuiElement buttonZoomIn = texturedButton(WIDTH - 14 - 9, BOTTOM_ROW, 40, 20, 9, new Runnable() {
@Override
public void run() {
@@ -197,14 +220,16 @@ public class GuiReplayOverlay extends Gui {
}
}, "replaymod.gui.ingame.menu.zoomout");
private final GuiTimeline timeline = new GuiTimeline(TIMELINE_X, TOP_ROW - 1, WIDTH - 14 - TIMELINE_X) {
private final GuiKeyframeTimeline timeline = new GuiKeyframeTimeline(TIMELINE_X, TOP_ROW - 1, WIDTH - 14 - TIMELINE_X, false, true, false, false) {
@Override
public void mouseClick(Minecraft mc, int mouseX, int mouseY, int button) {
performJump(timeline.getTimeAt(mouseX, mouseY));
super.mouseClick(mc, mouseX, mouseY, button);
if(!(ReplayHandler.getSelectedKeyframe() instanceof MarkerKeyframe))
performJump(timeline.getTimeAt(mouseX, mouseY));
}
};
private final GuiKeyframeTimeline timelineReal = new GuiKeyframeTimeline(TIMELINE_REAL_X, BOTTOM_ROW - 1, TIMELINE_REAL_WIDTH);
private final GuiKeyframeTimeline timelineReal = new GuiKeyframeTimeline(TIMELINE_REAL_X, BOTTOM_ROW - 1, TIMELINE_REAL_WIDTH, true, false, true, true);
{
timelineReal.timelineLength = 10 * 60 * 1000;
}

View File

@@ -2,25 +2,26 @@ package eu.crushedpixel.replaymod.holders;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public class Marker {
public Marker(Position position, int timestamp, String name) {
this.position = position;
this.timestamp = timestamp;
this.name = name;
}
public class MarkerKeyframe extends Keyframe {
private Position position;
private int timestamp;
private String name;
@Override
public Object clone() {
return new MarkerKeyframe(this.getPosition(), this.getRealTimestamp(), this.getName());
}
public MarkerKeyframe(Position position, int timestamp, String name) {
super(timestamp);
this.position = position;
this.name = name;
}
public Position getPosition() {
return position;
}
public int getTimestamp() {
return timestamp;
}
public String getName() {
return name;
@@ -29,8 +30,8 @@ public class Marker {
@Override
public boolean equals(Object o2) {
if(o2 == null) return false;
if(!(o2 instanceof Marker)) return false;
Marker m2 = (Marker)o2;
if(!(o2 instanceof MarkerKeyframe)) return false;
MarkerKeyframe m2 = (MarkerKeyframe)o2;
return hashCode() == m2.hashCode();
}
@@ -38,7 +39,7 @@ public class Marker {
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(position)
.append(timestamp)
.append(getRealTimestamp())
.append(name)
.toHashCode();
}

View File

@@ -62,7 +62,7 @@ public class ConnectionEventHandler {
public static void addMarker() {
if(!isRecording || packetListener == null) {
String reason = isRecording ? " (recording)" : " (null)";
logger.error("Invalid attempt to insert Marker!" + reason);
logger.error("Invalid attempt to insert MarkerKeyframe!" + reason);
return;
}
try {

View File

@@ -4,7 +4,7 @@ import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import com.google.gson.Gson;
import eu.crushedpixel.replaymod.ReplayMod;
import eu.crushedpixel.replaymod.holders.Marker;
import eu.crushedpixel.replaymod.holders.MarkerKeyframe;
import eu.crushedpixel.replaymod.holders.PacketData;
import eu.crushedpixel.replaymod.utils.ReplayFile;
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
@@ -35,7 +35,7 @@ public abstract class DataListener extends ChannelInboundHandlerAdapter {
protected boolean alive = true;
protected DataWriter dataWriter;
protected Set<String> players = new HashSet<String>();
protected Set<Marker> markers = new HashSet<Marker>();
protected Set<MarkerKeyframe> markers = new HashSet<MarkerKeyframe>();
private boolean singleplayer;
private Gson gson = new Gson();
@@ -154,7 +154,7 @@ public abstract class DataListener extends ChannelInboundHandlerAdapter {
queue.add(data);
}
public void requestFinish(Set<String> players, Set<Marker> markers) {
public void requestFinish(Set<String> players, Set<MarkerKeyframe> markers) {
active = false;
try {

View File

@@ -7,7 +7,7 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import eu.crushedpixel.replaymod.ReplayMod;
import eu.crushedpixel.replaymod.chat.ChatMessageHandler;
import eu.crushedpixel.replaymod.holders.Marker;
import eu.crushedpixel.replaymod.holders.MarkerKeyframe;
import eu.crushedpixel.replaymod.holders.PacketData;
import eu.crushedpixel.replaymod.holders.Position;
import eu.crushedpixel.replaymod.utils.ReplayFileIO;
@@ -223,7 +223,7 @@ public class PacketListener extends DataListener {
Position pos = new Position(Minecraft.getMinecraft().getRenderViewEntity());
int timestamp = (int) (System.currentTimeMillis() - startTime);
Marker marker = new Marker(pos, timestamp, null);
MarkerKeyframe marker = new MarkerKeyframe(pos, timestamp, null);
markers.add(marker);

View File

@@ -40,7 +40,6 @@ public class ReplayHandler {
private static float cameraTilt = 0;
private static KeyframeSet[] keyframeRepository = new KeyframeSet[]{};
private static Marker[] markers = new Marker[]{};
/**
* The file currently being played.
@@ -66,10 +65,22 @@ public class ReplayHandler {
}
}
public static Marker[] getMarkers() { return markers; }
public static MarkerKeyframe[] getMarkers() {
List<MarkerKeyframe> markers = new ArrayList<MarkerKeyframe>();
for(Keyframe kf : keyframes) {
if(kf instanceof MarkerKeyframe) markers.add((MarkerKeyframe)kf);
}
return markers.toArray(new MarkerKeyframe[markers.size()]);
}
public static void setMarkers(MarkerKeyframe[] m, boolean write) {
for(Keyframe kf : new ArrayList<Keyframe>(keyframes)) {
if(kf instanceof MarkerKeyframe) keyframes.remove(kf);
}
for(MarkerKeyframe marker : m) {
keyframes.add(marker);
}
public static void setMarkers(Marker[] m, boolean write) {
markers = m;
if(write) {
try {
File tempFile = File.createTempFile(ReplayFile.ENTRY_MARKERS, "json");
@@ -165,19 +176,7 @@ public class ReplayHandler {
public static void addMarker() {
Position pos = new Position(mc.getRenderViewEntity());
int timestamp = ReplayMod.replaySender.currentTimeStamp();
addMarker(new Marker(pos, timestamp, null));
}
public static void addMarker(Marker marker) {
List<Marker> markerList = new ArrayList<Marker>(Arrays.asList(markers));
markerList.add(marker);
markers = markerList.toArray(new Marker[markerList.size()]);
}
public static void removeMarker(Marker marker) {
List<Marker> markerList = new ArrayList<Marker>(Arrays.asList(markers));
markerList.remove(marker);
markers = markerList.toArray(new Marker[markerList.size()]);
addKeyframe(new MarkerKeyframe(pos, timestamp, null));
}
public static void addKeyframe(Keyframe keyframe) {
@@ -287,6 +286,25 @@ public class ReplayHandler {
return closest;
}
public static MarkerKeyframe getClosestMarkerForRealTime(int realTime, int tolerance) {
List<MarkerKeyframe> found = new ArrayList<MarkerKeyframe>();
for(Keyframe kf : keyframes) {
if(!(kf instanceof MarkerKeyframe)) continue;
if(Math.abs(kf.getRealTimestamp() - realTime) <= tolerance) {
found.add((MarkerKeyframe) kf);
}
}
MarkerKeyframe closest = null;
for(MarkerKeyframe kf : found) {
if(closest == null || Math.abs(closest.getRealTimestamp() - realTime) > Math.abs(kf.getRealTimestamp() - realTime)) {
closest = kf;
}
}
return closest;
}
public static PositionKeyframe getPreviousPositionKeyframe(int realTime) {
if(keyframes.isEmpty()) return null;
PositionKeyframe backup = null;
@@ -365,11 +383,6 @@ public class ReplayHandler {
return kf == selectedKeyframe;
}
public static boolean isSelected(Marker marker) {
//TODO: Make marker selectable
return false;
}
public static void selectKeyframe(Keyframe kf) {
selectedKeyframe = kf;
sortKeyframes();
@@ -421,8 +434,8 @@ public class ReplayHandler {
KeyframeSet[] paths = currentReplayFile.paths().get();
ReplayHandler.setKeyframeRepository(paths == null ? new KeyframeSet[0] : paths, false);
Marker[] markers = currentReplayFile.markers().get();
ReplayHandler.setMarkers(markers == null ? new Marker[0] : markers, false);
MarkerKeyframe[] markers = currentReplayFile.markers().get();
ReplayHandler.setMarkers(markers == null ? new MarkerKeyframe[0] : markers, false);
PlayerVisibility visibility = currentReplayFile.visibility().get();
PlayerHandler.loadPlayerVisibilityConfiguration(visibility);

View File

@@ -536,9 +536,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter {
if (cam != null && pos != null) {
// Move camera back in case we have been respawned
if (Math.abs(pos.getX() - cam.posX) < ReplayMod.TP_DISTANCE_LIMIT && Math.abs(pos.getZ() - cam.posZ) < ReplayMod.TP_DISTANCE_LIMIT) {
cam.moveAbsolute(pos.getX(), pos.getY(), pos.getZ());
cam.rotationPitch = pos.getPitch();
cam.rotationYaw = pos.getYaw();
cam.moveAbsolute(pos);
}
}

View File

@@ -5,7 +5,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import eu.crushedpixel.replaymod.holders.KeyframeSet;
import eu.crushedpixel.replaymod.holders.Marker;
import eu.crushedpixel.replaymod.holders.MarkerKeyframe;
import eu.crushedpixel.replaymod.holders.PlayerVisibility;
import eu.crushedpixel.replaymod.recording.ReplayMetaData;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
@@ -129,17 +129,17 @@ public class ReplayFile extends ZipFile {
return getEntry(ENTRY_MARKERS);
}
public Supplier<Marker[]> markers() {
return new Supplier<Marker[]>() {
public Supplier<MarkerKeyframe[]> markers() {
return new Supplier<MarkerKeyframe[]>() {
@Override
public Marker[] get() {
public MarkerKeyframe[] get() {
try {
ZipArchiveEntry entry = markersEntry();
if (entry == null) {
return null;
}
BufferedReader reader = new BufferedReader(new InputStreamReader(getInputStream(entry)));
return new Gson().fromJson(reader, Marker[].class);
return new Gson().fromJson(reader, MarkerKeyframe[].class);
} catch (IOException e) {
throw new RuntimeException(e);
}

View File

@@ -3,7 +3,7 @@ package eu.crushedpixel.replaymod.utils;
import com.google.gson.Gson;
import eu.crushedpixel.replaymod.ReplayMod;
import eu.crushedpixel.replaymod.holders.KeyframeSet;
import eu.crushedpixel.replaymod.holders.Marker;
import eu.crushedpixel.replaymod.holders.MarkerKeyframe;
import eu.crushedpixel.replaymod.holders.PacketData;
import eu.crushedpixel.replaymod.holders.PlayerVisibility;
import eu.crushedpixel.replaymod.recording.PacketSerializer;
@@ -58,7 +58,7 @@ public class ReplayFileIO {
return files;
}
public static void writeReplayFile(File replayFile, File tempFile, ReplayMetaData metaData, Set<Marker> markers,
public static void writeReplayFile(File replayFile, File tempFile, ReplayMetaData metaData, Set<MarkerKeyframe> markers,
Map<String, File> resourcePacks, Map<Integer, String> resourcePackRequests) throws IOException {
byte[] buffer = new byte[1024];
@@ -91,7 +91,7 @@ public class ReplayFileIO {
zos.putNextEntry(new ZipEntry(ReplayFile.ENTRY_MARKERS));
pw = new PrintWriter(zos);
pw.write(new Gson().toJson(markers.toArray(new Marker[markers.size()])));
pw.write(new Gson().toJson(markers.toArray(new MarkerKeyframe[markers.size()])));
pw.flush();
zos.closeEntry();
}
@@ -332,7 +332,7 @@ public class ReplayFileIO {
FileUtils.write(file, json);
}
public static void writeMarkersToFile(Marker[] markers, File file) throws IOException {
public static void writeMarkersToFile(MarkerKeyframe[] markers, File file) throws IOException {
file.mkdirs();
file.createNewFile();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB