diff --git a/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java b/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java index 58f40b8c..4363e2fd 100755 --- a/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java @@ -4,6 +4,7 @@ import eu.crushedpixel.replaymod.ReplayMod; import eu.crushedpixel.replaymod.entities.CameraEntity.MoveDirection; import eu.crushedpixel.replaymod.gui.GuiKeyframeRepository; import eu.crushedpixel.replaymod.gui.GuiMouseInput; +import eu.crushedpixel.replaymod.recording.ConnectionEventHandler; import eu.crushedpixel.replaymod.registry.KeybindRegistry; import eu.crushedpixel.replaymod.registry.PlayerHandler; import eu.crushedpixel.replaymod.replay.ReplayHandler; @@ -21,6 +22,8 @@ public class KeyInputHandler { private long prevKeysDown = Sys.getTime(); public void onKeyInput() throws Exception { + if(!ReplayHandler.isInReplay()) return; + if(!Keyboard.isCreated()) Keyboard.create(); Keyboard.poll(); @@ -96,8 +99,6 @@ public class KeyInputHandler { @SubscribeEvent public void keyInput(InputEvent.KeyInputEvent event) { - if (!ReplayHandler.isInReplay()) return; - try { onKeyInput(); } catch(Exception e) { @@ -124,6 +125,16 @@ public class KeyInputHandler { public void handleCustomKeybindings(KeyBinding kb, boolean found, int keyCode) { //Custom registered handlers + if(kb.getKeyDescription().equals(KeybindRegistry.KEY_ADD_MARKER) && (kb.isPressed() || kb.getKeyCode() == keyCode)) { + if(ReplayHandler.isInReplay()) { + ReplayHandler.addMarker(); + } else if(ConnectionEventHandler.isRecording()) { + ConnectionEventHandler.addMarker(); + } + } + + if(!ReplayHandler.isInReplay()) return; + if(kb.getKeyDescription().equals(KeybindRegistry.KEY_PLAY_PAUSE) && (kb.isPressed() || kb.getKeyCode() == keyCode) && !ReplayHandler.isInPath()) { ReplayMod.overlay.togglePlayPause(); } 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 22873381..1afe9236 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiKeyframeTimeline.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiKeyframeTimeline.java @@ -24,6 +24,7 @@ public class GuiKeyframeTimeline extends GuiTimeline { public GuiKeyframeTimeline(int positionX, int positionY, int width) { super(positionX, positionY, width); showMarkers = true; + showMarkerIndicators = false; } @Override @@ -130,8 +131,8 @@ public class GuiKeyframeTimeline extends GuiTimeline { } if(nextSpectatorKeyframeRealTime != -1) { - int keyframeX = getKeyframeX(kf.getRealTimestamp(), leftTime, rightTime, bodyWidth, segmentLength); - int nextX = getKeyframeX(nextSpectatorKeyframeRealTime, leftTime, rightTime, bodyWidth, segmentLength); + int keyframeX = getKeyframeX(kf.getRealTimestamp(), leftTime, bodyWidth, segmentLength); + int nextX = getKeyframeX(nextSpectatorKeyframeRealTime, leftTime, bodyWidth, segmentLength); drawGradientRect(keyframeX + 2, positionY + BORDER_TOP + 1, nextX - 2, positionY + BORDER_TOP + 4, 0xFF0080FF, 0xFF0080FF); } @@ -154,7 +155,7 @@ public class GuiKeyframeTimeline extends GuiTimeline { } } - private int getKeyframeX(int timestamp, long leftTime, long rightTime, int bodyWidth, double segmentLength) { + private int getKeyframeX(int timestamp, long leftTime, int bodyWidth, double segmentLength) { long positionInSegment = timestamp - leftTime; double fractionOfSegment = positionInSegment / segmentLength; return (int) (positionX + BORDER_LEFT + fractionOfSegment * bodyWidth); @@ -166,7 +167,7 @@ public class GuiKeyframeTimeline extends GuiTimeline { int textureY; int y = positionY; - int keyframeX = getKeyframeX(kf.getRealTimestamp(), leftTime, rightTime, bodyWidth, segmentLength); + int keyframeX = getKeyframeX(kf.getRealTimestamp(), leftTime, bodyWidth, segmentLength); if (kf instanceof PositionKeyframe) { textureX = KEYFRAME_PLACE_X; 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 05d6b63a..6e24dd4a 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiTimeline.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiTimeline.java @@ -1,6 +1,8 @@ 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; @@ -25,6 +27,11 @@ 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}. */ @@ -52,6 +59,11 @@ 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; @@ -138,9 +150,35 @@ 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); } + private int getMarkerX(int timestamp, long leftTime, int bodyWidth, double segmentLength) { + long positionInSegment = timestamp - leftTime; + double fractionOfSegment = positionInSegment / segmentLength; + return (int) (positionX + BORDER_LEFT + fractionOfSegment * 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) { diff --git a/src/main/java/eu/crushedpixel/replaymod/holders/Marker.java b/src/main/java/eu/crushedpixel/replaymod/holders/Marker.java new file mode 100644 index 00000000..7c86bfed --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/holders/Marker.java @@ -0,0 +1,45 @@ +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; + } + + private Position position; + private int timestamp; + private String name; + + public Position getPosition() { + return position; + } + + public int getTimestamp() { + return timestamp; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o2) { + if(o2 == null) return false; + if(!(o2 instanceof Marker)) return false; + Marker m2 = (Marker)o2; + return hashCode() == m2.hashCode(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(position) + .append(timestamp) + .append(name) + .toHashCode(); + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/recording/ConnectionEventHandler.java b/src/main/java/eu/crushedpixel/replaymod/recording/ConnectionEventHandler.java index 48c9d702..5ca2f6cf 100755 --- a/src/main/java/eu/crushedpixel/replaymod/recording/ConnectionEventHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/recording/ConnectionEventHandler.java @@ -59,6 +59,19 @@ public class ConnectionEventHandler { } } + public static void addMarker() { + if(!isRecording || packetListener == null) { + String reason = isRecording ? " (recording)" : " (null)"; + logger.error("Invalid attempt to insert Marker!" + reason); + return; + } + try { + packetListener.addMarker(); + } catch(Exception e) { + e.printStackTrace(); + } + } + @SubscribeEvent public void onConnectedToServerEvent(ClientConnectedToServerEvent event) { ReplayMod.chatMessageHandler.initialize(); diff --git a/src/main/java/eu/crushedpixel/replaymod/recording/DataListener.java b/src/main/java/eu/crushedpixel/replaymod/recording/DataListener.java index c73a40ab..8f98f03a 100755 --- a/src/main/java/eu/crushedpixel/replaymod/recording/DataListener.java +++ b/src/main/java/eu/crushedpixel/replaymod/recording/DataListener.java @@ -4,6 +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.PacketData; import eu.crushedpixel.replaymod.utils.ReplayFile; import eu.crushedpixel.replaymod.utils.ReplayFileIO; @@ -34,6 +35,7 @@ public abstract class DataListener extends ChannelInboundHandlerAdapter { protected boolean alive = true; protected DataWriter dataWriter; protected Set players = new HashSet(); + protected Set markers = new HashSet(); private boolean singleplayer; private Gson gson = new Gson(); @@ -43,7 +45,6 @@ public abstract class DataListener extends ChannelInboundHandlerAdapter { private final Map requestToHash = new ConcurrentHashMap(); private final Map resourcePacks = new HashMap(); - public DataListener(File file, String name, String worldName, long startTime, boolean singleplayer) throws FileNotFoundException { this.file = file; this.startTime = startTime; @@ -77,7 +78,7 @@ public abstract class DataListener extends ChannelInboundHandlerAdapter { @Override public void channelInactive(ChannelHandlerContext ctx) { - dataWriter.requestFinish(players); + dataWriter.requestFinish(players, markers); } protected void recordResourcePack(File file, int requestId) { @@ -153,7 +154,7 @@ public abstract class DataListener extends ChannelInboundHandlerAdapter { queue.add(data); } - public void requestFinish(Set players) { + public void requestFinish(Set players, Set markers) { active = false; try { @@ -177,7 +178,7 @@ public abstract class DataListener extends ChannelInboundHandlerAdapter { File archive = new File(folder, name + ReplayFile.ZIP_FILE_EXTENSION); archive.createNewFile(); - ReplayFileIO.writeReplayFile(archive, file, metaData, resourcePacks, requestToHash); + ReplayFileIO.writeReplayFile(archive, file, metaData, markers, resourcePacks, requestToHash); file.delete(); FileUtils.deleteDirectory(tempResourcePacksFolder); diff --git a/src/main/java/eu/crushedpixel/replaymod/recording/PacketListener.java b/src/main/java/eu/crushedpixel/replaymod/recording/PacketListener.java index e3aca144..81ad5d81 100755 --- a/src/main/java/eu/crushedpixel/replaymod/recording/PacketListener.java +++ b/src/main/java/eu/crushedpixel/replaymod/recording/PacketListener.java @@ -5,7 +5,11 @@ import com.google.common.io.Files; import com.google.common.util.concurrent.FutureCallback; 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.PacketData; +import eu.crushedpixel.replaymod.holders.Position; import eu.crushedpixel.replaymod.utils.ReplayFileIO; import io.netty.channel.ChannelHandlerContext; import net.minecraft.client.Minecraft; @@ -215,6 +219,17 @@ public class PacketListener extends DataListener { return requestId; } + public void addMarker() { + Position pos = new Position(Minecraft.getMinecraft().getRenderViewEntity()); + int timestamp = (int) (System.currentTimeMillis() - startTime); + + Marker marker = new Marker(pos, timestamp, null); + + markers.add(marker); + + ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.addedmarker", ChatMessageHandler.ChatMessageType.INFORMATION); + } + private void downloadResourcePackFuture(int requestId, String url, final String hash) { Futures.addCallback(downloadResourcePack(requestId, url, hash), new FutureCallback() { public void onSuccess(Object result) { diff --git a/src/main/java/eu/crushedpixel/replaymod/registry/KeybindRegistry.java b/src/main/java/eu/crushedpixel/replaymod/registry/KeybindRegistry.java index f087f820..6a03be28 100755 --- a/src/main/java/eu/crushedpixel/replaymod/registry/KeybindRegistry.java +++ b/src/main/java/eu/crushedpixel/replaymod/registry/KeybindRegistry.java @@ -20,17 +20,19 @@ public class KeybindRegistry { public static final String KEY_ROLL_COUNTERCLOCKWISE = "replaymod.input.rollcounterclockwise"; public static final String KEY_RESET_TILT = "replaymod.input.resettilt"; public static final String KEY_PLAY_PAUSE = "replaymod.input.playpause"; + public static final String KEY_ADD_MARKER = "replaymod.input.marker"; private static Minecraft mc = Minecraft.getMinecraft(); public static void initialize() { List bindings = new ArrayList(Arrays.asList(mc.gameSettings.keyBindings)); - bindings.add(new KeyBinding(KEY_LIGHTING, Keyboard.KEY_M, "replaymod.title")); + bindings.add(new KeyBinding(KEY_ADD_MARKER, Keyboard.KEY_M, "replaymod.title")); bindings.add(new KeyBinding(KEY_THUMBNAIL, Keyboard.KEY_N, "replaymod.title")); bindings.add(new KeyBinding(KEY_PLAYER_OVERVIEW, Keyboard.KEY_B, "replaymod.title")); bindings.add(new KeyBinding(KEY_SYNC_TIMELINE, Keyboard.KEY_V, "replaymod.title")); bindings.add(new KeyBinding(KEY_CLEAR_KEYFRAMES, Keyboard.KEY_C, "replaymod.title")); bindings.add(new KeyBinding(KEY_KEYFRAME_PRESETS, Keyboard.KEY_X, "replaymod.title")); + bindings.add(new KeyBinding(KEY_LIGHTING, Keyboard.KEY_Z, "replaymod.title")); bindings.add(new KeyBinding(KEY_ROLL_CLOCKWISE, Keyboard.KEY_L, "replaymod.title")); bindings.add(new KeyBinding(KEY_RESET_TILT, Keyboard.KEY_K, "replaymod.title")); bindings.add(new KeyBinding(KEY_ROLL_COUNTERCLOCKWISE, Keyboard.KEY_J, "replaymod.title")); diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java index 5eb84b83..bb4a7c2b 100755 --- a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java @@ -40,6 +40,7 @@ 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. @@ -55,7 +56,6 @@ public class ReplayHandler { if(write) { try { File tempFile = File.createTempFile(ReplayFile.ENTRY_PATHS, "json"); - tempFile.deleteOnExit(); ReplayFileIO.writeKeyframeRegistryToFile(repo, tempFile); @@ -66,6 +66,23 @@ public class ReplayHandler { } } + public static Marker[] getMarkers() { return markers; } + + public static void setMarkers(Marker[] m, boolean write) { + markers = m; + if(write) { + try { + File tempFile = File.createTempFile(ReplayFile.ENTRY_MARKERS, "json"); + + ReplayFileIO.writeMarkersToFile(m, tempFile); + + ReplayMod.replayFileAppender.registerModifiedFile(tempFile, ReplayFile.ENTRY_MARKERS, getReplayFile()); + } catch(Exception e) { + e.printStackTrace(); + } + } + } + public static void useKeyframePresetFromRepository(int index) { keyframes = new ArrayList(Arrays.asList(keyframeRepository[index].getKeyframes())); } @@ -145,6 +162,24 @@ public class ReplayHandler { Collections.sort(keyframes, new KeyframeComparator()); } + 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 markerList = new ArrayList(Arrays.asList(markers)); + markerList.add(marker); + markers = markerList.toArray(new Marker[markerList.size()]); + } + + public static void removeMarker(Marker marker) { + List markerList = new ArrayList(Arrays.asList(markers)); + markerList.remove(marker); + markers = markerList.toArray(new Marker[markerList.size()]); + } + public static void addKeyframe(Keyframe keyframe) { keyframes.add(keyframe); selectKeyframe(keyframe); @@ -330,6 +365,11 @@ 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(); @@ -381,6 +421,9 @@ 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); + PlayerVisibility visibility = currentReplayFile.visibility().get(); PlayerHandler.loadPlayerVisibilityConfiguration(visibility); diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFile.java b/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFile.java index fce7433d..aa64f5ab 100644 --- a/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFile.java +++ b/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFile.java @@ -5,6 +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.PlayerVisibility; import eu.crushedpixel.replaymod.recording.ReplayMetaData; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; @@ -30,6 +31,7 @@ public class ReplayFile extends ZipFile { public static final String ENTRY_RESOURCE_PACK_INDEX = "resourcepack/index.json"; public static final String ENTRY_VISIBILITY_OLD = "visibility"; public static final String ENTRY_VISIBILITY = "visibility" + JSON_FILE_EXTENSION; + public static final String ENTRY_MARKERS = "markers" + JSON_FILE_EXTENSION; private final File file; @@ -82,11 +84,6 @@ public class ReplayFile extends ZipFile { return newEntry != null ? newEntry : getEntry(ENTRY_PATHS_OLD); } - public ZipArchiveEntry visibilityEntry() { - ZipArchiveEntry newEntry = getEntry(ENTRY_VISIBILITY); - return newEntry != null ? newEntry : getEntry(ENTRY_VISIBILITY_OLD); - } - public Supplier paths() { return new Supplier() { @Override @@ -105,6 +102,11 @@ public class ReplayFile extends ZipFile { }; } + public ZipArchiveEntry visibilityEntry() { + ZipArchiveEntry newEntry = getEntry(ENTRY_VISIBILITY); + return newEntry != null ? newEntry : getEntry(ENTRY_VISIBILITY_OLD); + } + public Supplier visibility() { return new Supplier() { @Override @@ -123,6 +125,28 @@ public class ReplayFile extends ZipFile { }; } + public ZipArchiveEntry markersEntry() { + return getEntry(ENTRY_MARKERS); + } + + public Supplier markers() { + return new Supplier() { + @Override + public Marker[] 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); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + } + public ZipArchiveEntry thumbEntry() { return getEntry(ENTRY_THUMB); } diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFileIO.java b/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFileIO.java index aefa4d7c..04d9935e 100755 --- a/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFileIO.java +++ b/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFileIO.java @@ -3,6 +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.PacketData; import eu.crushedpixel.replaymod.holders.PlayerVisibility; import eu.crushedpixel.replaymod.recording.PacketSerializer; @@ -57,7 +58,7 @@ public class ReplayFileIO { return files; } - public static void writeReplayFile(File replayFile, File tempFile, ReplayMetaData metaData, + public static void writeReplayFile(File replayFile, File tempFile, ReplayMetaData metaData, Set markers, Map resourcePacks, Map resourcePackRequests) throws IOException { byte[] buffer = new byte[1024]; @@ -86,14 +87,23 @@ public class ReplayFileIO { fis.close(); zos.closeEntry(); - if (!resourcePackRequests.isEmpty()) { + if(!markers.isEmpty()) { + zos.putNextEntry(new ZipEntry(ReplayFile.ENTRY_MARKERS)); + + pw = new PrintWriter(zos); + pw.write(new Gson().toJson(markers.toArray(new Marker[markers.size()]))); + pw.flush(); + zos.closeEntry(); + } + + if(!resourcePackRequests.isEmpty()) { zos.putNextEntry(new ZipEntry(ReplayFile.ENTRY_RESOURCE_PACK_INDEX)); pw = new PrintWriter(zos); pw.write(new Gson().toJson(resourcePackRequests)); pw.flush(); zos.closeEntry(); - for (Map.Entry entry : resourcePacks.entrySet()) { + for(Map.Entry entry : resourcePacks.entrySet()) { zos.putNextEntry(new ZipEntry(String.format(ReplayFile.ENTRY_RESOURCE_PACK, entry.getKey()))); IOUtils.copy(new FileInputStream(entry.getValue()), zos); zos.closeEntry(); @@ -322,6 +332,14 @@ public class ReplayFileIO { FileUtils.write(file, json); } + public static void writeMarkersToFile(Marker[] markers, File file) throws IOException { + file.mkdirs(); + file.createNewFile(); + + String json = gson.toJson(markers); + FileUtils.write(file, json); + } + /** * Adds Files as entries to a ZIP File. * @param zipFile The ZIP File to add the files to. diff --git a/src/main/resources/assets/replaymod/lang/en_US.lang b/src/main/resources/assets/replaymod/lang/en_US.lang index b3a047eb..8060d102 100644 --- a/src/main/resources/assets/replaymod/lang/en_US.lang +++ b/src/main/resources/assets/replaymod/lang/en_US.lang @@ -37,6 +37,8 @@ replaymod.chat.savingthumb=Saving Thumbnail... replaymod.chat.savedthumb=Thumbnail has been successfully saved replaymod.chat.failedthumb=Thumbnail could not be saved +replaymod.chat.addedmarker=Event Marker has been added + #Chat messages displayed in Replay Viewer replaymod.chat.notenoughkeyframes=At least 2 position or time keyframes required replaymod.chat.pathstarted=Camera Path started @@ -156,6 +158,7 @@ replaymod.gui.upload.start=Start Upload replaymod.gui.upload.cancel=Cancel Upload replaymod.gui.upload.tagshint=Tags separated by comma replaymod.gui.upload.namehint=Replay Title +replaymod.gui.upload.descriptionhint=Description replaymod.gui.upload.uploading=Uploading... replaymod.gui.upload.success=File has been successfully uploaded replaymod.gui.upload.canceled=Upload has been canceled @@ -226,6 +229,7 @@ replaymod.input.rollclockwise=Roll Clockwise replaymod.input.rollcounterclockwise=Roll Counterclockwise replaymod.input.resettilt=Reset Camera Tilt replaymod.input.playpause=Play/Pause Replay +replaymod.input.marker=Add Event Marker #Keyframe Presets GUI replaymod.gui.keyframerepository.title=Keyframe Repository diff --git a/src/main/resources/assets/replaymod/replay_gui.png b/src/main/resources/assets/replaymod/replay_gui.png index 535beefe..91edbce3 100755 Binary files a/src/main/resources/assets/replaymod/replay_gui.png and b/src/main/resources/assets/replaymod/replay_gui.png differ