Added Keyframe Repository, which is stored in Replay File itself and allows Camera Path Presets to be saved and loaded

This commit is contained in:
CrushedPixel
2015-05-01 23:20:16 +02:00
parent 2cccbe424a
commit e8320ebd0f
17 changed files with 525 additions and 144 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<KeyframeSet> 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<KeyframeSet>(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<KeyframeSet> copy = new ArrayList<KeyframeSet>(keyframeSetList.getCopyOfElements());
this.keyframeRepository = copy.toArray(new KeyframeSet[copy.size()]);
ReplayHandler.setKeyframeRepository(keyframeRepository, true);
ReplayMod.replaySender.setReplaySpeed(prevSpeed);
}
}

View File

@@ -18,6 +18,8 @@ public class GuiEntryList<T> extends GuiTextField {
private int visibleElements;
private int upperIndex = 0;
private String emptyMessage = null;
private List<SelectionListener> selectionListeners = new ArrayList<SelectionListener>();
private List<T> elements = new ArrayList<T>();
@@ -32,10 +34,20 @@ public class GuiEntryList<T> 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<T> 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<T> extends GuiTextField {
fireSelectionChangeEvent();
}
public List<T> getCopyOfElements() {
return new ArrayList<T>(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);
}

View File

@@ -42,8 +42,7 @@ public class GuiConnectPart extends GuiStudioPart {
@Override
public void applyFilters(File replayFile, File outputFile) {
// TODO Auto-generated method stub
//TODO
}
@Override

View File

@@ -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<PositionKeyframe> posKFList = new ArrayList<PositionKeyframe>();
List<TimeKeyframe> timeKFList = new ArrayList<TimeKeyframe>();
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<Keyframe> kfList = new ArrayList<Keyframe>();
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();
}
}

View File

@@ -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<Pair<File, File>> filesToMove = new ConcurrentLinkedQueue<Pair<File, File>>();
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<File, File> p : new ArrayList<Pair<File, File>>(filesToMove)) {
if(p.equals(tempFile)) {
filesToMove.remove(p);
}
}
}
public void shutdown() {
shutdown = true;
}
@Override
public void run() {
while(!shutdown || !filesToMove.isEmpty()) {
Pair<File, File> 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) {
}
}
}
}

View File

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

View File

@@ -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<Pair<Pair<File, String>, File>> filesToMove = new ConcurrentLinkedQueue<Pair<Pair<File, String>, 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<Pair<File, String>, File> p : new ArrayList<Pair<Pair<File, String>, 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<Pair<File, String>, 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) {}
}
}
}

View File

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

View File

@@ -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<Class> badPackets = new ArrayList<Class>() {
{
@@ -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;
/*

View File

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

View File

@@ -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) {

View File

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