From 01f80294aaa4384d750d5c1a3ea513b09fd9de31 Mon Sep 17 00:00:00 2001 From: CrushedPixel Date: Wed, 3 Jun 2015 14:20:32 +0200 Subject: [PATCH] Spectator Keyframes are now colored blue, and the timespan during which an Entity is spectated is tinted --- .../gui/elements/GuiKeyframeTimeline.java | 120 ++++++++++++++---- .../replaymod/gui/elements/GuiTimeline.java | 4 + .../replaymod/holders/KeyframeComparator.java | 4 - .../replaymod/replay/ReplayHandler.java | 1 - .../resources/assets/replaymod/replay_gui.png | Bin 6392 -> 6425 bytes 5 files changed, 98 insertions(+), 31 deletions(-) diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiKeyframeTimeline.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiKeyframeTimeline.java index d0441413..22873381 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiKeyframeTimeline.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiKeyframeTimeline.java @@ -7,11 +7,15 @@ import eu.crushedpixel.replaymod.holders.TimeKeyframe; import eu.crushedpixel.replaymod.replay.ReplayHandler; import net.minecraft.client.Minecraft; +import java.util.ListIterator; + public class GuiKeyframeTimeline extends GuiTimeline { private static final int KEYFRAME_PLACE_X = 74; private static final int KEYFRAME_PLACE_Y = 20; private static final int KEYFRAME_TIME_X = 74; 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 Keyframe clickedKeyFrame; private long clickTime; @@ -95,34 +99,98 @@ public class GuiKeyframeTimeline extends GuiTimeline { double segmentLength = timelineLength * zoom; - //Draw Keyframe logos - for(Keyframe kf : ReplayHandler.getKeyframes()) { - if (kf.getRealTimestamp() <= rightTime && kf.getRealTimestamp() >= leftTime) { - int textureX; - int textureY; - int y = positionY; - if (kf instanceof PositionKeyframe) { - textureX = KEYFRAME_PLACE_X; - textureY = KEYFRAME_PLACE_Y; - y += 0; - } else if (kf instanceof TimeKeyframe) { - textureX = KEYFRAME_TIME_X; - textureY = KEYFRAME_TIME_Y; - y += 5; - } else { - throw new UnsupportedOperationException("Unknown keyframe type: " + kf.getClass()); + //iterate over keyframes to find spectator segments + ListIterator iterator = ReplayHandler.getKeyframes().listIterator(); + while(iterator.hasNext()) { + Keyframe kf = iterator.next(); + + if(!(kf instanceof PositionKeyframe) || ((PositionKeyframe)kf).getSpectatedEntityID() == null) continue; + + int i = iterator.nextIndex(); + int nextSpectatorKeyframeRealTime = -1; + + while(iterator.hasNext()) { + Keyframe kf2 = iterator.next(); + + if(kf2 instanceof PositionKeyframe) { + if(((PositionKeyframe) kf).getSpectatedEntityID() + .equals(((PositionKeyframe) kf2).getSpectatedEntityID())) { + + nextSpectatorKeyframeRealTime = kf2.getRealTimestamp(); + } + break; } - - if (ReplayHandler.isSelected(kf)) { - textureX += 5; - } - - long positionInSegment = kf.getRealTimestamp() - leftTime; - double fractionOfSegment = positionInSegment / segmentLength; - int x = (int) (positionX + BORDER_LEFT + fractionOfSegment * bodyWidth); - - rect(x - 2, y + BORDER_TOP, textureX, textureY, 5, 5); } + + int i2 = iterator.previousIndex(); + + while(i2 >= i) { + iterator.previous(); + i2--; + } + + if(nextSpectatorKeyframeRealTime != -1) { + int keyframeX = getKeyframeX(kf.getRealTimestamp(), leftTime, rightTime, bodyWidth, segmentLength); + int nextX = getKeyframeX(nextSpectatorKeyframeRealTime, leftTime, rightTime, bodyWidth, segmentLength); + + drawGradientRect(keyframeX + 2, positionY + BORDER_TOP + 1, nextX - 2, positionY + BORDER_TOP + 4, 0xFF0080FF, 0xFF0080FF); + } + } + + drawTimelineCursor(leftTime, rightTime, bodyWidth); + + + //Draw Keyframe logos + iterator = ReplayHandler.getKeyframes().listIterator(); + while(iterator.hasNext()) { + Keyframe kf = iterator.next(); + + if(!kf.equals(ReplayHandler.getSelectedKeyframe())) + drawKeyframe(kf, bodyWidth, leftTime, rightTime, segmentLength); + } + + if(ReplayHandler.getSelectedKeyframe() != null) { + drawKeyframe(ReplayHandler.getSelectedKeyframe(), bodyWidth, leftTime, rightTime, segmentLength); + } + } + + private int getKeyframeX(int timestamp, long leftTime, long rightTime, int bodyWidth, double segmentLength) { + long positionInSegment = timestamp - leftTime; + double fractionOfSegment = positionInSegment / segmentLength; + return (int) (positionX + BORDER_LEFT + fractionOfSegment * bodyWidth); + } + + private void drawKeyframe(Keyframe kf, int bodyWidth, long leftTime, long rightTime, double segmentLength) { + if (kf.getRealTimestamp() <= rightTime && kf.getRealTimestamp() >= leftTime) { + int textureX; + int textureY; + int y = positionY; + + int keyframeX = getKeyframeX(kf.getRealTimestamp(), leftTime, rightTime, bodyWidth, segmentLength); + + if (kf instanceof PositionKeyframe) { + textureX = KEYFRAME_PLACE_X; + textureY = KEYFRAME_PLACE_Y; + y += 0; + + //If Spectator Keyframe, use different texture + if(((PositionKeyframe) kf).getSpectatedEntityID() != null) { + textureX = KEYFRAME_SPEC_X; + textureY = KEYFRAME_SPEC_Y; + } + } else if (kf instanceof TimeKeyframe) { + textureX = KEYFRAME_TIME_X; + textureY = KEYFRAME_TIME_Y; + y += 5; + } else { + throw new UnsupportedOperationException("Unknown keyframe type: " + kf.getClass()); + } + + if (ReplayHandler.isSelected(kf)) { + textureX += 5; + } + + rect(keyframeX - 2, y + BORDER_TOP, textureX, textureY, 5, 5); } } } diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiTimeline.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiTimeline.java index c7089d4a..05d6b63a 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiTimeline.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiTimeline.java @@ -138,6 +138,10 @@ public class GuiTimeline extends Gui implements GuiElement { } } + drawTimelineCursor(leftTime, rightTime, bodyWidth); + } + + protected void drawTimelineCursor(long leftTime, long rightTime, int bodyWidth) { // Draw the cursor if it's on the current timeline segment if (cursorPosition >= leftTime && cursorPosition <= rightTime) { long positionInSegment = cursorPosition - leftTime; diff --git a/src/main/java/eu/crushedpixel/replaymod/holders/KeyframeComparator.java b/src/main/java/eu/crushedpixel/replaymod/holders/KeyframeComparator.java index 7b65a901..75c737cc 100755 --- a/src/main/java/eu/crushedpixel/replaymod/holders/KeyframeComparator.java +++ b/src/main/java/eu/crushedpixel/replaymod/holders/KeyframeComparator.java @@ -1,15 +1,11 @@ package eu.crushedpixel.replaymod.holders; -import eu.crushedpixel.replaymod.replay.ReplayHandler; - import java.util.Comparator; public class KeyframeComparator implements Comparator { @Override public int compare(Keyframe o1, Keyframe o2) { - if(ReplayHandler.isSelected(o1)) return 1; - if(ReplayHandler.isSelected(o2)) return -1; return ((Integer) o1.getRealTimestamp()).compareTo(o2.getRealTimestamp()); } diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java index bc7b6c61..1f588956 100755 --- a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java @@ -27,7 +27,6 @@ public class ReplayHandler { public static long lastExit = 0; private static NetworkManager networkManager; private static Minecraft mc = Minecraft.getMinecraft(); - //private static ReplaySender replaySender; private static OpenEmbeddedChannel channel; private static int realTimelinePosition = 0; private static Keyframe selectedKeyframe; diff --git a/src/main/resources/assets/replaymod/replay_gui.png b/src/main/resources/assets/replaymod/replay_gui.png index a3f4b29b848020544fc7f9de869aaa17f6d94ee7..535beefe1d7630b0af743027b771bf5036c64811 100755 GIT binary patch literal 6425 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 B0y_Wz delta 6367 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