From e8320ebd0f92ab5861fb48360bbd5e2a6ba2ece7 Mon Sep 17 00:00:00 2001 From: CrushedPixel Date: Fri, 1 May 2015 23:20:16 +0200 Subject: [PATCH] Added Keyframe Repository, which is stored in Replay File itself and allows Camera Path Presets to be saved and loaded --- .../eu/crushedpixel/replaymod/ReplayMod.java | 8 +- .../replaymod/events/GuiReplayOverlay.java | 8 +- .../replaymod/events/KeyInputHandler.java | 12 +- .../replaymod/events/MinecraftTicker.java | 29 --- .../replaymod/gui/GuiConstants.java | 6 + .../replaymod/gui/GuiKeyframeRepository.java | 229 ++++++++++++++++++ .../replaymod/gui/elements/GuiEntryList.java | 33 +++ .../gui/replaystudio/GuiConnectPart.java | 3 +- .../replaymod/holders/KeyframeSet.java | 90 +++++++ .../replaymod/registry/FileCopyHandler.java | 59 ----- .../replaymod/registry/KeybindRegistry.java | 2 + .../registry/ReplayFileAppender.java | 61 +++++ .../replaymod/replay/ReplayHandler.java | 32 ++- .../replaymod/replay/ReplaySender.java | 41 ++-- .../replaymod/utils/ReplayFileIO.java | 35 ++- .../replaymod/video/ReplayScreenshot.java | 5 +- .../assets/replaymod/lang/en_US.lang | 16 +- 17 files changed, 525 insertions(+), 144 deletions(-) create mode 100644 src/main/java/eu/crushedpixel/replaymod/gui/GuiKeyframeRepository.java create mode 100644 src/main/java/eu/crushedpixel/replaymod/holders/KeyframeSet.java delete mode 100644 src/main/java/eu/crushedpixel/replaymod/registry/FileCopyHandler.java create mode 100644 src/main/java/eu/crushedpixel/replaymod/registry/ReplayFileAppender.java diff --git a/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java b/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java index 8ebbafad..28aeb7ce 100755 --- a/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java +++ b/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java @@ -6,7 +6,7 @@ import eu.crushedpixel.replaymod.events.*; import eu.crushedpixel.replaymod.localization.LocalizedResourcePack; import eu.crushedpixel.replaymod.recording.ConnectionEventHandler; import eu.crushedpixel.replaymod.reflection.MCPNames; -import eu.crushedpixel.replaymod.registry.FileCopyHandler; +import eu.crushedpixel.replaymod.registry.ReplayFileAppender; import eu.crushedpixel.replaymod.registry.KeybindRegistry; import eu.crushedpixel.replaymod.renderer.SafeEntityRenderer; import eu.crushedpixel.replaymod.replay.ReplaySender; @@ -62,7 +62,7 @@ public class ReplayMod { public static KeyInputHandler keyInputHandler = new KeyInputHandler(); public static ReplaySender replaySender; public static int TP_DISTANCE_LIMIT = 128; - public static FileCopyHandler fileCopyHandler; + public static ReplayFileAppender replayFileAppender; private static Field defaultResourcePacksField; static { @@ -86,8 +86,8 @@ public class ReplayMod { replaySettings = new ReplaySettings(); replaySettings.readValues(); - fileCopyHandler = new FileCopyHandler(); - fileCopyHandler.start(); + replayFileAppender = new ReplayFileAppender(); + replayFileAppender.start(); } @EventHandler diff --git a/src/main/java/eu/crushedpixel/replaymod/events/GuiReplayOverlay.java b/src/main/java/eu/crushedpixel/replaymod/events/GuiReplayOverlay.java index c7a6575d..e46d36b5 100755 --- a/src/main/java/eu/crushedpixel/replaymod/events/GuiReplayOverlay.java +++ b/src/main/java/eu/crushedpixel/replaymod/events/GuiReplayOverlay.java @@ -2,10 +2,7 @@ package eu.crushedpixel.replaymod.events; import eu.crushedpixel.replaymod.ReplayMod; import eu.crushedpixel.replaymod.entities.CameraEntity; -import eu.crushedpixel.replaymod.gui.GuiCancelRender; -import eu.crushedpixel.replaymod.gui.GuiMouseInput; -import eu.crushedpixel.replaymod.gui.GuiReplaySpeedSlider; -import eu.crushedpixel.replaymod.gui.GuiSpectateSelection; +import eu.crushedpixel.replaymod.gui.*; import eu.crushedpixel.replaymod.holders.Keyframe; import eu.crushedpixel.replaymod.holders.Position; import eu.crushedpixel.replaymod.holders.PositionKeyframe; @@ -120,7 +117,8 @@ public class GuiReplayOverlay extends Gui { @SubscribeEvent public void onRenderGui(RenderGameOverlayEvent.Post event) throws IllegalArgumentException, IllegalAccessException { - if(!ReplayHandler.isInReplay() || FMLClientHandler.instance().isGUIOpen(GuiSpectateSelection.class) || VideoWriter.isRecording()) { + if(!ReplayHandler.isInReplay() || FMLClientHandler.instance().isGUIOpen(GuiSpectateSelection.class) + || VideoWriter.isRecording() || FMLClientHandler.instance().isGUIOpen(GuiKeyframeRepository.class)) { return; } diff --git a/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java b/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java index d1ba224e..406ef6dc 100755 --- a/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java @@ -3,6 +3,7 @@ package eu.crushedpixel.replaymod.events; import eu.crushedpixel.replaymod.ReplayMod; import eu.crushedpixel.replaymod.entities.CameraEntity.MoveDirection; import eu.crushedpixel.replaymod.gui.GuiCancelRender; +import eu.crushedpixel.replaymod.gui.GuiKeyframeRepository; import eu.crushedpixel.replaymod.gui.GuiMouseInput; import eu.crushedpixel.replaymod.registry.KeybindRegistry; import eu.crushedpixel.replaymod.replay.ReplayHandler; @@ -87,12 +88,12 @@ public class KeyInputHandler { ReplayHandler.getCameraEntity().speedUp(); } - if(kb.getKeyDescription().equals("key.chat")) { + if(kb.getKeyDescription().equals("key.chat") && kb.isPressed()) { mc.displayGuiScreen(new GuiMouseInput()); break; } - handleCustomKeybindings(kb, found, null); + handleCustomKeybindings(kb, found, -1); } catch(Exception e) { e.printStackTrace(); @@ -101,11 +102,10 @@ public class KeyInputHandler { } } - public void handleCustomKeybindings(KeyBinding kb, boolean found, Integer keyCode) { + public void handleCustomKeybindings(KeyBinding kb, boolean found, int keyCode) { //Custom registered handlers if(kb.getKeyDescription().equals(KeybindRegistry.KEY_THUMBNAIL) && (kb.isPressed() || kb.getKeyCode() == keyCode) && !found) { TickAndRenderListener.requestScreenshot(); - //TODO: Make this properly work } if(kb.getKeyDescription().equals(KeybindRegistry.KEY_SPECTATE) && (kb.isPressed() || kb.getKeyCode() == keyCode) && !found) { @@ -123,5 +123,9 @@ public class KeyInputHandler { if(kb.getKeyDescription().equals(KeybindRegistry.KEY_SYNC_TIMELINE) && (kb.isPressed() || kb.getKeyCode() == keyCode)) { ReplayHandler.syncTimeCursor(Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT)); } + + if(kb.getKeyDescription().equals(KeybindRegistry.KEY_KEYFRAME_PRESETS) && (kb.isPressed() || kb.getKeyCode() == keyCode)) { + mc.displayGuiScreen(new GuiKeyframeRepository(ReplayHandler.getKeyframeRepository())); + } } } diff --git a/src/main/java/eu/crushedpixel/replaymod/events/MinecraftTicker.java b/src/main/java/eu/crushedpixel/replaymod/events/MinecraftTicker.java index 4bd9bc5e..188f779d 100755 --- a/src/main/java/eu/crushedpixel/replaymod/events/MinecraftTicker.java +++ b/src/main/java/eu/crushedpixel/replaymod/events/MinecraftTicker.java @@ -2,15 +2,11 @@ package eu.crushedpixel.replaymod.events; import eu.crushedpixel.replaymod.reflection.MCPNames; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiChat; import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.gui.inventory.GuiInventory; import net.minecraft.client.settings.GameSettings; import net.minecraft.client.settings.KeyBinding; import net.minecraft.crash.CrashReport; import net.minecraft.entity.Entity; -import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.network.play.client.C16PacketClientStatus; import net.minecraft.util.MathHelper; import net.minecraft.util.ReportedException; import org.lwjgl.input.Keyboard; @@ -260,31 +256,6 @@ public class MinecraftTicker { } } - boolean flag = mc.gameSettings.chatVisibility != EntityPlayer.EnumChatVisibility.HIDDEN; - - while(mc.gameSettings.keyBindInventory.isPressed()) { - if(mc.playerController.isRidingHorse()) { - mc.thePlayer.sendHorseInventory(); - } else { - mc.getNetHandler().addToSendQueue(new C16PacketClientStatus(C16PacketClientStatus.EnumState.OPEN_INVENTORY_ACHIEVEMENT)); - mc.displayGuiScreen(new GuiInventory(mc.thePlayer)); - } - } - - while(mc.gameSettings.keyBindDrop.isPressed()) { - if(!mc.thePlayer.isSpectator()) { - mc.thePlayer.dropOneItem(GuiScreen.isCtrlKeyDown()); - } - } - - while(mc.gameSettings.keyBindChat.isPressed() && flag) { - mc.displayGuiScreen(new GuiChat()); - } - - if(mc.currentScreen == null && mc.gameSettings.keyBindCommand.isPressed() && flag) { - mc.displayGuiScreen(new GuiChat("/")); - } - if(mc.thePlayer != null && mc.thePlayer.isUsingItem()) { if(!mc.gameSettings.keyBindUseItem.isKeyDown()) { mc.playerController.onStoppedUsingItem(mc.thePlayer); diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiConstants.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiConstants.java index f3623e9b..a29aec8a 100755 --- a/src/main/java/eu/crushedpixel/replaymod/gui/GuiConstants.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiConstants.java @@ -45,4 +45,10 @@ public class GuiConstants { public static final int REPLAY_EDITOR_REMOVE_BUTTON = 5008; public static final int REPLAY_EDITOR_ADD_BUTTON = 5009; + + public static final int KEYFRAME_REPOSITORY_LIST = 1234; + public static final int KEYFRAME_REPOSTORY_NAME_INPUT = 2345; + public static final int KEYFRAME_REPOSITORY_ADD_BUTTON = 3456; + public static final int KEYFRAME_REPOSITORY_REMOVE_BUTTON = 4567; + public static final int KEYFRAME_REPOSITORY_LOAD_BUTTON = 5678; } diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiKeyframeRepository.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiKeyframeRepository.java new file mode 100644 index 00000000..41963ee3 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiKeyframeRepository.java @@ -0,0 +1,229 @@ +package eu.crushedpixel.replaymod.gui; + +import eu.crushedpixel.replaymod.ReplayMod; +import eu.crushedpixel.replaymod.gui.elements.GuiEntryList; +import eu.crushedpixel.replaymod.gui.elements.listeners.SelectionListener; +import eu.crushedpixel.replaymod.holders.Keyframe; +import eu.crushedpixel.replaymod.holders.KeyframeSet; +import eu.crushedpixel.replaymod.replay.ReplayHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.GuiTextField; +import net.minecraft.client.resources.I18n; +import org.apache.commons.lang3.time.DurationFormatUtils; +import org.lwjgl.input.Keyboard; + +import java.awt.*; +import java.io.IOException; +import java.util.ArrayList; + +public class GuiKeyframeRepository extends GuiScreen { + + private boolean initialized = false; + + private final String screenTitle = I18n.format("replaymod.gui.keyframerepository.title"); + private final Minecraft mc = Minecraft.getMinecraft(); + + private GuiEntryList keyframeSetList; + private KeyframeSet[] keyframeRepository; + + private KeyframeSet currentKeyframeSet = null; + + private GuiTextField nameInput; + private GuiButton removeButton, loadButton, saveButton; + + private String message = null; + + private int currentSetTimeKeyframeCount, currentSetPositionKeyframeCount, currentSetDuration; + + private double prevSpeed; + + public GuiKeyframeRepository(KeyframeSet[] keyframeRepository) { + this.keyframeRepository = keyframeRepository; + prevSpeed = ReplayMod.replaySender.getReplaySpeed(); + ReplayMod.replaySender.setReplaySpeed(0); + } + + @Override + public void initGui() { + int h = (int)Math.floor(((double)this.height-(45+20+15))/14); + + if(!initialized) { + keyframeSetList = new GuiEntryList(GuiConstants.KEYFRAME_REPOSITORY_LIST, mc.fontRendererObj, + 20, 45, this.width / 2, h); + + for(KeyframeSet set : keyframeRepository) + keyframeSetList.addElement(set); + + keyframeSetList.setSelectionIndex(-1); + + keyframeSetList.setEmptyMessage(I18n.format("replaymod.gui.keyframerepository.noentries")); + + keyframeSetList.addSelectionListener(new SelectionListener() { + @Override + public void onSelectionChanged(int selectionIndex) { + removeButton.enabled = selectionIndex >= 0; + loadButton.enabled = selectionIndex >= 0; + nameInput.setEnabled(selectionIndex >= 0); + + if(selectionIndex >= 0) { + currentKeyframeSet = keyframeSetList.getElement(selectionIndex); + nameInput.setText(currentKeyframeSet.getName()); + + currentSetPositionKeyframeCount = currentKeyframeSet.getPositionKeyframeCount(); + currentSetTimeKeyframeCount = currentKeyframeSet.getTimeKeyframeCount(); + currentSetDuration = currentKeyframeSet.getPathDuration(); + } else { + nameInput.setText(""); + currentKeyframeSet = null; + } + + message = null; + } + }); + + nameInput = new GuiTextField(GuiConstants.KEYFRAME_REPOSTORY_NAME_INPUT, mc.fontRendererObj, + 30 + (this.width / 2), 45, this.width - (30 + this.width / 2 + 20), 20); + nameInput.setEnabled(false); + + removeButton = new GuiButton(GuiConstants.KEYFRAME_REPOSITORY_REMOVE_BUTTON, 30 + (this.width / 2), 75, + I18n.format("replaymod.gui.remove")); + removeButton.width = (nameInput.width / 2) - 3; + removeButton.enabled = false; + + loadButton = new GuiButton(GuiConstants.KEYFRAME_REPOSITORY_LOAD_BUTTON, 30 + (this.width / 2) + 5 + removeButton.width, 75, + I18n.format("replaymod.gui.load")); + loadButton.width = removeButton.width + 1; + loadButton.enabled = false; + + saveButton = new GuiButton(GuiConstants.KEYFRAME_REPOSITORY_ADD_BUTTON, 30 + (this.width / 2), + keyframeSetList.yPosition+keyframeSetList.height-20, + I18n.format("replaymod.gui.keyframerepository.savecurrent")); + saveButton.width = nameInput.width; + } else { + keyframeSetList.width = this.width / 2; + keyframeSetList.setVisibleElements(h); + + nameInput.xPosition = 30 + (this.width / 2); + nameInput.width = this.width - (30 + this.width / 2 + 20); + + removeButton.xPosition = 30 + (this.width / 2); + removeButton.width = (nameInput.width / 2) - 3; + + loadButton.xPosition = 30 + (this.width / 2) + 5 + removeButton.width; + loadButton.width = removeButton.width + 1; + + saveButton.xPosition = 30 + (this.width / 2); + saveButton.width = nameInput.width; + saveButton.yPosition = keyframeSetList.yPosition+keyframeSetList.height-20; + } + + buttonList.add(removeButton); + buttonList.add(loadButton); + buttonList.add(saveButton); + + initialized = true; + } + + @Override + public void actionPerformed(GuiButton button) { + if(!button.enabled) return; + switch(button.id) { + case GuiConstants.KEYFRAME_REPOSITORY_ADD_BUTTON: + Keyframe[] keyframes = ReplayHandler.getKeyframes().toArray(new Keyframe[ReplayHandler.getKeyframes().size()]); + KeyframeSet newSet = new KeyframeSet(I18n.format("replaymod.gui.keyframerepository.preset.defaultname"), keyframes); + if(newSet.getPositionKeyframeCount() < 2 && newSet.getTimeKeyframeCount() < 2) { + message = I18n.format("replaymod.chat.notenoughkeyframes"); + break; + } else if(keyframeSetList.getCopyOfElements().contains(newSet)) { + message = I18n.format("replaymod.gui.keyframerepository.duplicate"); + break; + } + message = null; + + keyframeSetList.addElement(newSet); + keyframeSetList.setSelectionIndex(keyframeSetList.getEntryCount()-1); + break; + case GuiConstants.KEYFRAME_REPOSITORY_REMOVE_BUTTON: + keyframeSetList.removeElement(keyframeSetList.getSelectionIndex()); + break; + case GuiConstants.KEYFRAME_REPOSITORY_LOAD_BUTTON: + ReplayHandler.useKeyframePresetFromRepository(keyframeSetList.getSelectionIndex()); + saveOnQuit(); + mc.displayGuiScreen(null); + break; + } + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + this.drawCenteredString(fontRendererObj, screenTitle, this.width / 2, 5, Color.WHITE.getRGB()); + + int leftBorder = 10; + int topBorder = 20; + + drawGradientRect(leftBorder, topBorder, width - leftBorder, this.height - 10, -1072689136, -804253680); + + this.drawCenteredString(fontRendererObj, I18n.format("replaymod.gui.keyframerepository.presets"), 20 + (this.width / 4), 30, Color.WHITE.getRGB()); + + keyframeSetList.drawTextBox(); + + nameInput.drawTextBox(); + + KeyframeSet currentSet = null; + + if(keyframeSetList.getSelectionIndex() >= 0) { + currentSet = keyframeSetList.getElement(keyframeSetList.getSelectionIndex()); + } + + if(currentSet != null) { + this.drawString(fontRendererObj, I18n.format("replaymod.gui.keyframerepository.positionkeyframes") + ": " +currentSetPositionKeyframeCount, + 30 + (this.width / 2), removeButton.yPosition + 30, Color.WHITE.getRGB()); + + this.drawString(fontRendererObj, I18n.format("replaymod.gui.keyframerepository.timekeyframes") + ": " +currentSetTimeKeyframeCount, + 30 + (this.width / 2), removeButton.yPosition + 50, Color.WHITE.getRGB()); + + this.drawString(fontRendererObj, I18n.format("replaymod.gui.duration")+": "+ DurationFormatUtils.formatDurationHMS(currentSetDuration), + 30 + (this.width / 2), removeButton.yPosition + 70, Color.WHITE.getRGB()); + } + + if(message != null) { + this.drawCenteredString(fontRendererObj, message, this.width/2, this.height-25, Color.RED.getRGB()); + } + + super.drawScreen(mouseX, mouseY, partialTicks); + } + + @Override + public void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { + super.mouseClicked(mouseX, mouseY, mouseButton); + keyframeSetList.mouseClicked(mouseX, mouseY, mouseButton); + nameInput.mouseClicked(mouseX, mouseY, mouseButton); + } + + @Override + public void updateScreen() { + nameInput.updateCursorCounter(); + } + + @Override + public void keyTyped(char typedChar, int keyCode) throws IOException { + nameInput.textboxKeyTyped(typedChar, keyCode); + if(keyCode == Keyboard.KEY_ESCAPE) { + saveOnQuit(); + super.keyTyped(typedChar, keyCode); + } + if(currentKeyframeSet != null) { + currentKeyframeSet.setName(nameInput.getText()); + keyframeSetList.replaceElement(keyframeSetList.getSelectionIndex(), currentKeyframeSet); + } + } + + private void saveOnQuit() { + ArrayList copy = new ArrayList(keyframeSetList.getCopyOfElements()); + this.keyframeRepository = copy.toArray(new KeyframeSet[copy.size()]); + ReplayHandler.setKeyframeRepository(keyframeRepository, true); + ReplayMod.replaySender.setReplaySpeed(prevSpeed); + } +} \ No newline at end of file diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiEntryList.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiEntryList.java index 2cd7703d..b4a0ef99 100755 --- a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiEntryList.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiEntryList.java @@ -18,6 +18,8 @@ public class GuiEntryList extends GuiTextField { private int visibleElements; private int upperIndex = 0; + private String emptyMessage = null; + private List selectionListeners = new ArrayList(); private List elements = new ArrayList(); @@ -32,10 +34,20 @@ public class GuiEntryList extends GuiTextField { this.height = elementHeight * visibleElements - 1; } + public void setEmptyMessage(String emptyMessage) { + this.emptyMessage = emptyMessage; + } + @Override public void drawTextBox() { try { super.drawTextBox(); + + if(elements.size() == 0 && emptyMessage != null) { + drawCenteredString(mc.fontRendererObj, emptyMessage, + xPosition+(width/2), yPosition+(height/2)-(mc.fontRendererObj.FONT_HEIGHT/2), Color.RED.getRGB()); + } + //drawing the entries for(int i = 0; i - upperIndex < visibleElements; i++) { if(i < upperIndex) continue; @@ -125,6 +137,14 @@ public class GuiEntryList extends GuiTextField { return null; } + public void removeElement(int index) { + elements.remove(index); + if(selectionIndex >= elements.size()) { + selectionIndex = elements.size()-1; + } + fireSelectionChangeEvent(); + } + public int getSelectionIndex() { return selectionIndex; } @@ -135,6 +155,19 @@ public class GuiEntryList extends GuiTextField { fireSelectionChangeEvent(); } + public List getCopyOfElements() { + return new ArrayList(elements); + } + + public void replaceElement(int index, T replace) { + elements.set(index, replace); + fireSelectionChangeEvent(); + } + + public int getEntryCount() { + return elements.size(); + } + public void addSelectionListener(SelectionListener listener) { this.selectionListeners.add(listener); } diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/replaystudio/GuiConnectPart.java b/src/main/java/eu/crushedpixel/replaymod/gui/replaystudio/GuiConnectPart.java index 18f38aa7..a82f43da 100755 --- a/src/main/java/eu/crushedpixel/replaymod/gui/replaystudio/GuiConnectPart.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/replaystudio/GuiConnectPart.java @@ -42,8 +42,7 @@ public class GuiConnectPart extends GuiStudioPart { @Override public void applyFilters(File replayFile, File outputFile) { - // TODO Auto-generated method stub - + //TODO } @Override diff --git a/src/main/java/eu/crushedpixel/replaymod/holders/KeyframeSet.java b/src/main/java/eu/crushedpixel/replaymod/holders/KeyframeSet.java new file mode 100644 index 00000000..377465fc --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/holders/KeyframeSet.java @@ -0,0 +1,90 @@ +package eu.crushedpixel.replaymod.holders; + +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import java.util.ArrayList; +import java.util.List; + +public class KeyframeSet { + private String name; + private PositionKeyframe[] positionKeyframes; + private TimeKeyframe[] timeKeyframes; + + public KeyframeSet(String name, Keyframe[] keyframes) { + this.name = name; + setKeyframes(keyframes); + } + + public void setName(String name) { + this.name = name; + } + + public void setKeyframes(Keyframe[] keyframes) { + List posKFList = new ArrayList(); + List timeKFList = new ArrayList(); + + for(Keyframe kf : keyframes) { + if(kf instanceof PositionKeyframe) + posKFList.add((PositionKeyframe)kf); + else if(kf instanceof TimeKeyframe) + timeKFList.add((TimeKeyframe) kf); + } + + positionKeyframes = posKFList.toArray(new PositionKeyframe[posKFList.size()]); + timeKeyframes = timeKFList.toArray(new TimeKeyframe[timeKFList.size()]); + } + + public String getName() { + return name; + } + + public Keyframe[] getKeyframes() { + List kfList = new ArrayList(); + for(Keyframe kf : positionKeyframes) kfList.add(kf); + for(Keyframe kf : timeKeyframes) kfList.add(kf); + return kfList.toArray(new Keyframe[kfList.size()]); + } + + public int getTimeKeyframeCount() { + return timeKeyframes.length; + } + + public int getPositionKeyframeCount() { + return positionKeyframes.length; + } + + public int getPathDuration() { + int first = 0; + int last = 0; + + for(Keyframe k : getKeyframes()) { + if(k.getRealTimestamp() < first) { + first = k.getRealTimestamp(); + } else if(k.getRealTimestamp() > last) { + last = k.getRealTimestamp(); + } + } + + return last-first; + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object o2) { + if(o2 == null) return false; + if(!(o2 instanceof KeyframeSet)) return false; + KeyframeSet set2 = (KeyframeSet)o2; + return hashCode() == set2.hashCode(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(getKeyframes()) + .toHashCode(); + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/registry/FileCopyHandler.java b/src/main/java/eu/crushedpixel/replaymod/registry/FileCopyHandler.java deleted file mode 100644 index 11b81d81..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/registry/FileCopyHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -package eu.crushedpixel.replaymod.registry; - -import org.apache.commons.lang3.tuple.Pair; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - -public class FileCopyHandler extends Thread { - - private Queue> filesToMove = new ConcurrentLinkedQueue>(); - private boolean shutdown = false; - - public FileCopyHandler() { - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - @Override - public void run() { - FileCopyHandler.this.shutdown(); - } - })); - } - - public void registerModifiedFile(File tempFile, File destination) { - filesToMove.add(Pair.of(tempFile, destination)); - - for(Pair p : new ArrayList>(filesToMove)) { - if(p.equals(tempFile)) { - filesToMove.remove(p); - } - } - } - - public void shutdown() { - shutdown = true; - } - - @Override - public void run() { - while(!shutdown || !filesToMove.isEmpty()) { - Pair mv = filesToMove.poll(); - if(mv != null) { - try { - Files.move(mv.getLeft().toPath(), mv.getRight().toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch(IOException e) { - filesToMove.add(mv); - } - } - try { - Thread.sleep(1000); - } catch(Exception e) { - } - } - } - -} diff --git a/src/main/java/eu/crushedpixel/replaymod/registry/KeybindRegistry.java b/src/main/java/eu/crushedpixel/replaymod/registry/KeybindRegistry.java index 0c9a41a6..6e5ea395 100755 --- a/src/main/java/eu/crushedpixel/replaymod/registry/KeybindRegistry.java +++ b/src/main/java/eu/crushedpixel/replaymod/registry/KeybindRegistry.java @@ -15,6 +15,7 @@ public class KeybindRegistry { public static final String KEY_SPECTATE = "replaymod.input.spectate"; public static final String KEY_CLEAR_KEYFRAMES = "replaymod.input.clearkeyframes"; public static final String KEY_SYNC_TIMELINE = "replaymod.input.synctimeline"; + public static final String KEY_KEYFRAME_PRESETS = "replaymod.input.keyframerepository"; private static Minecraft mc = Minecraft.getMinecraft(); public static void initialize() { @@ -25,6 +26,7 @@ public class KeybindRegistry { bindings.add(new KeyBinding(KEY_SPECTATE, 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")); mc.gameSettings.keyBindings = bindings.toArray(new KeyBinding[bindings.size()]); } diff --git a/src/main/java/eu/crushedpixel/replaymod/registry/ReplayFileAppender.java b/src/main/java/eu/crushedpixel/replaymod/registry/ReplayFileAppender.java new file mode 100644 index 00000000..e3590029 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/registry/ReplayFileAppender.java @@ -0,0 +1,61 @@ +package eu.crushedpixel.replaymod.registry; + +import eu.crushedpixel.replaymod.utils.ReplayFileIO; +import org.apache.commons.lang3.tuple.Pair; + +import java.io.File; +import java.util.ArrayList; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +public class ReplayFileAppender extends Thread { + + private Queue, File>> filesToMove = new ConcurrentLinkedQueue, File>>(); + private boolean shutdown = false; + + public ReplayFileAppender() { + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + @Override + public void run() { + ReplayFileAppender.this.shutdown(); + } + })); + } + + public void registerModifiedFile(File toAdd, String name, File replayFile) { + for(Pair, File> p : new ArrayList, File>>(filesToMove)) { + if(p.getLeft().getRight().equals(name) && p.getRight().equals(replayFile)) { + filesToMove.remove(p); + } + } + + filesToMove.add(Pair.of(Pair.of(toAdd, name), replayFile)); + } + + public void shutdown() { + shutdown = true; + } + + @Override + public void run() { + while(!shutdown || !filesToMove.isEmpty()) { + Pair, File> mv = filesToMove.poll(); + if(mv != null) { + if(mv.getRight().canWrite()) { + try { + ReplayFileIO.addFileToZip(mv.getRight(), mv.getLeft().getLeft(), mv.getLeft().getRight()); + } catch(Exception e) { + e.printStackTrace(); + filesToMove.add(mv); + } + } else { + filesToMove.add(mv); + } + } + try { + Thread.sleep(1000); + } catch(Exception e) {} + } + } + +} diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java index d6c1d8c8..9c336d7f 100755 --- a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java @@ -4,6 +4,7 @@ import com.mojang.authlib.GameProfile; import eu.crushedpixel.replaymod.ReplayMod; import eu.crushedpixel.replaymod.entities.CameraEntity; import eu.crushedpixel.replaymod.holders.*; +import eu.crushedpixel.replaymod.utils.ReplayFileIO; import io.netty.channel.embedded.EmbeddedChannel; import net.minecraft.client.Minecraft; import net.minecraft.client.network.NetHandlerPlayClient; @@ -13,10 +14,7 @@ import net.minecraft.network.NetworkManager; import net.minecraft.network.play.INetHandlerPlayClient; import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.UUID; +import java.util.*; public class ReplayHandler { @@ -34,6 +32,32 @@ public class ReplayHandler { private static Entity currentEntity = null; private static Position lastPosition = null; + private static KeyframeSet[] keyframeRepository = new KeyframeSet[]{}; + + public static KeyframeSet[] getKeyframeRepository() { + return keyframeRepository; + } + + public static void setKeyframeRepository(KeyframeSet[] repo, boolean write) { + keyframeRepository = repo; + if(write) { + try { + File tempFile = File.createTempFile("paths", "json"); + tempFile.deleteOnExit(); + + ReplayFileIO.writeKeyframeRegistryToFile(repo, tempFile); + + ReplayMod.replayFileAppender.registerModifiedFile(tempFile, "paths", ReplayMod.replaySender.getReplayFile()); + } catch(Exception e) { + e.printStackTrace(); + } + } + } + + public static void useKeyframePresetFromRepository(int index) { + keyframes = Arrays.asList(keyframeRepository[index].getKeyframes()); + } + public static void spectateEntity(Entity e) { currentEntity = e; mc.setRenderViewEntity(currentEntity); diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/ReplaySender.java b/src/main/java/eu/crushedpixel/replaymod/replay/ReplaySender.java index a832d94a..42e9dd07 100755 --- a/src/main/java/eu/crushedpixel/replaymod/replay/ReplaySender.java +++ b/src/main/java/eu/crushedpixel/replaymod/replay/ReplaySender.java @@ -4,6 +4,7 @@ import com.google.gson.Gson; import eu.crushedpixel.replaymod.ReplayMod; import eu.crushedpixel.replaymod.entities.CameraEntity; import eu.crushedpixel.replaymod.events.RecordingHandler; +import eu.crushedpixel.replaymod.holders.KeyframeSet; import eu.crushedpixel.replaymod.holders.PacketData; import eu.crushedpixel.replaymod.holders.Position; import eu.crushedpixel.replaymod.recording.ConnectionEventHandler; @@ -15,7 +16,6 @@ import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import net.minecraft.client.Minecraft; -import net.minecraft.client.particle.EffectRenderer; import net.minecraft.client.resources.ResourcePackRepository; import net.minecraft.entity.Entity; import net.minecraft.network.EnumConnectionState; @@ -76,14 +76,9 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { joinPacketDimension, joinPacketDifficulty, joinPacketMaxPlayers; private Field effectPacketEntityId; private Field metadataPacketEntityId, metadataPacketList; - private Field animationPacketEntityId; - private Field entityDataWatcher; - private Field chatPacketPosition; private Minecraft mc = Minecraft.getMinecraft(); - private long now = System.currentTimeMillis(); private int replayLength = 0; private int actualID = -1; - private EffectRenderer old = mc.effectRenderer; private ZipArchiveEntry replayEntry; private ArrayList badPackets = new ArrayList() { { @@ -187,7 +182,6 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { } } catch(EOFException eof) { - System.out.println("End of File encountered!"); dis = new DataInputStream(archive.getInputStream(replayEntry)); setReplaySpeed(0); } catch(IOException e) { @@ -226,16 +220,6 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { metadataPacketList = S1CPacketEntityMetadata.class.getDeclaredField(MCPNames.field("field_149378_b")); metadataPacketList.setAccessible(true); - - animationPacketEntityId = S0BPacketAnimation.class.getDeclaredField(MCPNames.field("field_148981_a")); - animationPacketEntityId.setAccessible(true); - - entityDataWatcher = Entity.class.getDeclaredField(MCPNames.field("field_70180_af")); - entityDataWatcher.setAccessible(true); - - chatPacketPosition = S02PacketChat.class.getDeclaredField(MCPNames.field("field_179842_b")); - chatPacketPosition.setAccessible(true); - } catch(Exception e) { e.printStackTrace(); } @@ -253,10 +237,24 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { String json = br.readLine(); - ReplayMetaData metaData = new Gson().fromJson(json, ReplayMetaData.class); + Gson gson = new Gson(); + ReplayMetaData metaData = gson.fromJson(json, ReplayMetaData.class); this.replayLength = metaData.getDuration(); + ZipArchiveEntry paths = archive.getEntry("paths"); + if(paths != null) { + InputStream is2 = archive.getInputStream(paths); + BufferedReader br2 = new BufferedReader(new InputStreamReader(is2)); + + String json2 = br2.readLine(); + KeyframeSet[] repo = gson.fromJson(json2, KeyframeSet[].class); + + ReplayHandler.setKeyframeRepository(repo, false); + } else { + ReplayHandler.setKeyframeRepository(new KeyframeSet[]{}, false); + } + sender.start(); } catch(Exception e) { e.printStackTrace(); @@ -364,13 +362,6 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { return; } - if(p instanceof S02PacketChat) { - byte pos = (Byte) chatPacketPosition.get(p); - if(pos == 1) { //Ignores command block output sent - return; - } - } - if(badPackets.contains(p.getClass())) return; /* diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFileIO.java b/src/main/java/eu/crushedpixel/replaymod/utils/ReplayFileIO.java index 5ebec2bf..3e4c3065 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 akka.japi.Pair; import com.google.gson.Gson; import eu.crushedpixel.replaymod.ReplayMod; +import eu.crushedpixel.replaymod.holders.KeyframeSet; import eu.crushedpixel.replaymod.holders.PacketData; import eu.crushedpixel.replaymod.recording.ConnectionEventHandler; import eu.crushedpixel.replaymod.recording.PacketSerializer; @@ -20,6 +21,7 @@ import org.apache.commons.compress.archivers.zip.ZipFile; import org.apache.commons.io.FilenameUtils; import java.io.*; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -305,7 +307,21 @@ public class ReplayFileIO { return false; } - public static void addThumbToZip(File zipFile, File thumb) throws IOException { + private static final Gson gson = new Gson(); + + public static void writeKeyframeRegistryToFile(KeyframeSet[] keyframeRegistry, File file) throws IOException { + file.mkdirs(); + file.createNewFile(); + + String json = gson.toJson(keyframeRegistry); + Files.write(file.toPath(), json.getBytes()); + } + + public static KeyframeSet[] getKeyframeRegistryFromFile(File file) throws Exception { + return gson.fromJson(Files.readAllLines(file.toPath()).get(0), KeyframeSet[].class); + } + + public static void addFileToZip(File zipFile, File toAdd, String fileName) throws IOException { // get a temp file File tempFile = File.createTempFile(zipFile.getName(), null, zipFile.getParentFile()); // delete it, otherwise you cannot rename your existing zip to it. @@ -318,8 +334,8 @@ public class ReplayFileIO { ZipEntry entry = zin.getNextEntry(); while(entry != null) { String name = entry.getName(); - boolean isThumb = name.contains("thumb"); - if(!isThumb) { + boolean isFile = name.equalsIgnoreCase(fileName); + if(!isFile) { // Add ZIP entry to output stream. out.putNextEntry(new ZipEntry(name)); // Transfer bytes from the ZIP file to the output file @@ -334,13 +350,13 @@ public class ReplayFileIO { zin.close(); // Compress the files - InputStream in = new FileInputStream(thumb); + InputStream in = new FileInputStream(toAdd); // Add ZIP entry to output stream. - out.putNextEntry(new ZipEntry("thumb")); + out.putNextEntry(new ZipEntry(fileName)); // Transfer bytes from the file to the ZIP file - int len; - out.write(uniqueBytes); + int len; + if(fileName.equals("thumb")) out.write(uniqueBytes); while((len = in.read(buf)) > 0) { out.write(buf, 0, len); @@ -352,6 +368,9 @@ public class ReplayFileIO { // Complete the ZIP file out.close(); - ReplayMod.fileCopyHandler.registerModifiedFile(tempFile, zipFile); + zipFile.delete(); + tempFile.renameTo(zipFile); + + toAdd.delete(); } } diff --git a/src/main/java/eu/crushedpixel/replaymod/video/ReplayScreenshot.java b/src/main/java/eu/crushedpixel/replaymod/video/ReplayScreenshot.java index 628c6c36..ee960c66 100755 --- a/src/main/java/eu/crushedpixel/replaymod/video/ReplayScreenshot.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/ReplayScreenshot.java @@ -6,7 +6,6 @@ import eu.crushedpixel.replaymod.events.TickAndRenderListener; import eu.crushedpixel.replaymod.gui.GuiReplaySaving; import eu.crushedpixel.replaymod.replay.ReplayHandler; import eu.crushedpixel.replaymod.utils.ImageUtils; -import eu.crushedpixel.replaymod.utils.ReplayFileIO; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; @@ -84,9 +83,9 @@ public class ReplayScreenshot { ImageIO.write(img, "jpg", tempImage); - ReplayFileIO.addThumbToZip(replayFile, tempImage); + ReplayMod.replayFileAppender.registerModifiedFile(tempImage, "thumb", replayFile); - tempImage.delete(); + tempImage.deleteOnExit(); ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.savedthumb", ChatMessageType.INFORMATION); } catch(Exception e) { diff --git a/src/main/resources/assets/replaymod/lang/en_US.lang b/src/main/resources/assets/replaymod/lang/en_US.lang index 9d68d2e0..252687a9 100644 --- a/src/main/resources/assets/replaymod/lang/en_US.lang +++ b/src/main/resources/assets/replaymod/lang/en_US.lang @@ -144,4 +144,18 @@ replaymod.input.lighting=Toggle Lighting replaymod.input.thumbnail=Capture Thumbnail replaymod.input.spectate=Spectate Players replaymod.input.clearkeyframes=Clear Keyframes -replaymod.input.synctimeline=Synchronize Timeline \ No newline at end of file +replaymod.input.synctimeline=Synchronize Timeline +replaymod.input.keyframerepository=Open Keyframe Presets + +#Keyframe Presets GUI +replaymod.gui.keyframerepository.title=Keyframe Repository +replaymod.gui.keyframerepository.presets=Keyframe Presets +replaymod.gui.keyframerepository.positionkeyframes=Position Keyframes +replaymod.gui.keyframerepository.timekeyframes=Time Keyframes + +replaymod.gui.keyframerepository.savecurrent=Save current Path + +replaymod.gui.keyframerepository.noentries=No Presets available + +replaymod.gui.keyframerepository.preset.defaultname=New Keyframe Preset +replaymod.gui.keyframerepository.duplicate=This Preset already exists \ No newline at end of file