diff --git a/build.gradle b/build.gradle index 0c79a520..71f4d2e7 100644 --- a/build.gradle +++ b/build.gradle @@ -17,9 +17,9 @@ buildscript { apply plugin: 'forge' -version = "1.0" -group= "com.yourname.modid" // http://maven.apache.org/guides/mini/guide-naming-conventions.html -archivesBaseName = "modid" +version = "0.4" +group= "eu.crushedpixel.replaymod" +archivesBaseName = "replaymod" minecraft { version = "1.8-11.14.0.1255-1.8" @@ -34,6 +34,7 @@ minecraft { } dependencies { + compile fileTree(dir: 'lib', includes: ['*.jar']) // you may put jars on which you depend on in ./libs // or you may define them like so.. //compile "some.group:artifact:version:classifier" diff --git a/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java b/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java index d49718c6..5c702cd7 100644 --- a/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java +++ b/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java @@ -27,6 +27,8 @@ public class ReplayMod { //TODO: Set ReplayHandler replaying to false when replay is exited + //TODO: Hide Titles upon hurrying + //TODO: Override Enchantment Rendering for items when replaying (to adjust speed of animation) //XXX //Known Bugs @@ -44,7 +46,7 @@ public class ReplayMod public static GuiReplayOverlay overlay = new GuiReplayOverlay(); - public static ReplaySettings replaySettings = new ReplaySettings(0, true, true, true, false, false); + public static ReplaySettings replaySettings = new ReplaySettings(true, true, true, false, false, 30, 0.5f); public static Configuration config; public static boolean firstMainMenu = true; @@ -66,13 +68,14 @@ public class ReplayMod Property recServer = config.get("settings", "enableRecordingServer", true, "Defines whether a recording should be started upon joining a server."); Property recSP = config.get("settings", "enableRecordingSingleplayer", true, "Defines whether a recording should be started upon joining a singleplayer world."); - Property maxFileSize = config.get("settings", "maximumFileSize", 0, "The maximum File size (in MB) of a recording. 0 means unlimited."); Property showNot = ReplayMod.instance.config.get("settings", "showNotifications", true, "Defines whether notifications should be sent to the player."); Property linear = ReplayMod.instance.config.get("settings", "forceLinearPath", false, "Defines whether travelling paths should be linear instead of interpolated."); Property lighting = ReplayMod.instance.config.get("settings", "enableLighting", false, "If enabled, the whole map is lighted."); + Property vq = ReplayMod.instance.config.get("settings", "videoQuality", 0.5f, "The quality of the exported video files from 0.1 to 0.9"); + Property framerate = ReplayMod.instance.config.get("settings", "videoFramerate", 30, "The framerate of the exported video files from 10 to 120"); - replaySettings = new ReplaySettings(maxFileSize.getInt(0), recServer.getBoolean(true), recSP.getBoolean(true), showNot.getBoolean(true), - linear.getBoolean(false), lighting.getBoolean(false)); + replaySettings = new ReplaySettings(recServer.getBoolean(true), recSP.getBoolean(true), showNot.getBoolean(true), + linear.getBoolean(false), lighting.getBoolean(false), framerate.getInt(30), (float)vq.getDouble(0.5)); config.save(); } diff --git a/src/main/java/eu/crushedpixel/replaymod/api/client/ApiClient.java b/src/main/java/eu/crushedpixel/replaymod/api/client/ApiClient.java index dcdd5f5e..b79e432c 100644 --- a/src/main/java/eu/crushedpixel/replaymod/api/client/ApiClient.java +++ b/src/main/java/eu/crushedpixel/replaymod/api/client/ApiClient.java @@ -9,12 +9,7 @@ import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.List; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.FileEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.util.EntityUtils; +import org.apache.commons.io.FileUtils; import com.google.gson.Gson; import com.google.gson.JsonElement; @@ -22,7 +17,6 @@ import com.google.gson.JsonParser; import eu.crushedpixel.replaymod.api.client.holders.ApiError; import eu.crushedpixel.replaymod.api.client.holders.AuthKey; -import eu.crushedpixel.replaymod.api.client.holders.Category; import eu.crushedpixel.replaymod.api.client.holders.FileInfo; import eu.crushedpixel.replaymod.api.client.holders.Success; import eu.crushedpixel.replaymod.api.client.holders.UserFiles; @@ -77,6 +71,13 @@ public class ApiClient { FileInfo[] info = invokeAndReturn(builder, FileInfo[].class); //TODO: Test if that works return info; } + + public void downloadThumbnail(int file, File target) throws IOException { + QueryBuilder builder = new QueryBuilder(ApiMethods.get_thumbnail); + builder.put("id", file); + URL url = new URL(builder.toString()); + FileUtils.copyURLToFile(url, target); + } public void downloadFile(String auth, int file, File target) throws IOException { QueryBuilder builder = new QueryBuilder(ApiMethods.download_file); diff --git a/src/main/java/eu/crushedpixel/replaymod/api/client/ApiMethods.java b/src/main/java/eu/crushedpixel/replaymod/api/client/ApiMethods.java index 0b4f4cb6..dfced8d0 100644 --- a/src/main/java/eu/crushedpixel/replaymod/api/client/ApiMethods.java +++ b/src/main/java/eu/crushedpixel/replaymod/api/client/ApiMethods.java @@ -8,6 +8,7 @@ public class ApiMethods { public static final String file_details = "file_details"; public static final String upload_file = "upload_file"; public static final String download_file = "download_file"; + public static final String get_thumbnail = "get_thumbnail"; public static final String remove_file = "remove_file"; public static final String rate_file = "rate_file"; diff --git a/src/main/java/eu/crushedpixel/replaymod/api/client/holders/FileInfo.java b/src/main/java/eu/crushedpixel/replaymod/api/client/holders/FileInfo.java index d9ffc3c1..636e407e 100644 --- a/src/main/java/eu/crushedpixel/replaymod/api/client/holders/FileInfo.java +++ b/src/main/java/eu/crushedpixel/replaymod/api/client/holders/FileInfo.java @@ -10,6 +10,9 @@ public class FileInfo { private Rating ratings; private int size; private int category; + private int downloads; + private String name; + private boolean thumbnail; public int getId() { return id; @@ -29,5 +32,15 @@ public class FileInfo { public int getCategory() { return category; } + public int getDownloads() { + return downloads; + } + public String getName() { + return name; + } + public boolean hasThumbnail() { + return thumbnail; + } + } diff --git a/src/main/java/eu/crushedpixel/replaymod/entities/CameraEntity.java b/src/main/java/eu/crushedpixel/replaymod/entities/CameraEntity.java index a6316f80..9906dd64 100644 --- a/src/main/java/eu/crushedpixel/replaymod/entities/CameraEntity.java +++ b/src/main/java/eu/crushedpixel/replaymod/entities/CameraEntity.java @@ -5,6 +5,7 @@ import java.lang.reflect.Field; import net.minecraft.client.Minecraft; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.ItemStack; +import net.minecraft.network.play.server.S03PacketTimeUpdate; import net.minecraft.util.MathHelper; import net.minecraft.util.MovingObjectPosition; import net.minecraft.util.Vec3; @@ -15,6 +16,7 @@ import org.lwjgl.Sys; import eu.crushedpixel.replaymod.holders.Position; import eu.crushedpixel.replaymod.replay.LesserDataWatcher; import eu.crushedpixel.replaymod.replay.ReplayHandler; +import eu.crushedpixel.replaymod.replay.TimeHandler; public class CameraEntity extends EntityPlayer { @@ -37,14 +39,19 @@ public class CameraEntity extends EntityPlayer { mc.thePlayer.rotationPitch = mc.getRenderViewEntity().rotationPitch; mc.thePlayer.rotationYaw = mc.getRenderViewEntity().rotationYaw; + /* mc.thePlayer.posX = mc.getRenderViewEntity().posX; mc.thePlayer.posY = mc.getRenderViewEntity().posY; mc.thePlayer.posZ = mc.getRenderViewEntity().posZ; + TimeHandler.setDesiredDaytime(18000); + TimeHandler.setTimeOverridden(true); + */ + //removes water/suffocation/shadow overlays in screen - //mc.thePlayer.posX = 0; - //mc.thePlayer.posY = 500; - //mc.thePlayer.posZ = 0; + mc.thePlayer.posX = 0; + mc.thePlayer.posY = 500; + mc.thePlayer.posZ = 0; } if(direction == null || motion < 0.1) { diff --git a/src/main/java/eu/crushedpixel/replaymod/events/GuiEventHandler.java b/src/main/java/eu/crushedpixel/replaymod/events/GuiEventHandler.java index bd10de96..869b2647 100644 --- a/src/main/java/eu/crushedpixel/replaymod/events/GuiEventHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/events/GuiEventHandler.java @@ -15,11 +15,11 @@ import net.minecraft.client.gui.GuiVideoSettings; import net.minecraft.client.gui.inventory.GuiInventory; import net.minecraft.client.resources.I18n; import net.minecraft.client.settings.GameSettings.Options; -import net.minecraft.util.Timer; import net.minecraftforge.client.event.GuiOpenEvent; import net.minecraftforge.client.event.GuiScreenEvent.ActionPerformedEvent; import net.minecraftforge.client.event.GuiScreenEvent.DrawScreenEvent; import net.minecraftforge.client.event.GuiScreenEvent.InitGuiEvent; +import net.minecraftforge.fml.common.eventhandler.Event.Result; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import eu.crushedpixel.replaymod.ReplayMod; import eu.crushedpixel.replaymod.gui.GuiConstants; @@ -32,8 +32,9 @@ import eu.crushedpixel.replaymod.gui.replaymanager.GuiReplayManager; import eu.crushedpixel.replaymod.gui.replaymanager.ResourceHelper; import eu.crushedpixel.replaymod.online.authentication.AuthenticationHandler; import eu.crushedpixel.replaymod.registry.ReplayGuiRegistry; +import eu.crushedpixel.replaymod.replay.MCTimerHandler; import eu.crushedpixel.replaymod.replay.ReplayHandler; -import eu.crushedpixel.replaymod.replay.ReplaySender; +import eu.crushedpixel.replaymod.video.VideoWriter; public class GuiEventHandler { @@ -51,6 +52,11 @@ public class GuiEventHandler { @SubscribeEvent public void onGui(GuiOpenEvent event) { + if(VideoWriter.isRecording()) { + event.gui = null; + return; + } + if(!(event.gui instanceof GuiReplayManager || event.gui instanceof GuiUploadFile)) ResourceHelper.freeAllResources(); if(event.gui instanceof GuiMainMenu) { @@ -60,8 +66,7 @@ public class GuiEventHandler { return; } else { try { - Timer timer = (Timer)ReplaySender.mcTimer.get(mc); - timer.timerSpeed = 1f; + MCTimerHandler.setTimerSpeed(1); } catch (Exception e) { e.printStackTrace(); } diff --git a/src/main/java/eu/crushedpixel/replaymod/events/GuiReplayOverlay.java b/src/main/java/eu/crushedpixel/replaymod/events/GuiReplayOverlay.java index 9c6be1ea..8831f5fa 100644 --- a/src/main/java/eu/crushedpixel/replaymod/events/GuiReplayOverlay.java +++ b/src/main/java/eu/crushedpixel/replaymod/events/GuiReplayOverlay.java @@ -46,9 +46,11 @@ import eu.crushedpixel.replaymod.holders.PositionKeyframe; import eu.crushedpixel.replaymod.holders.TimeKeyframe; import eu.crushedpixel.replaymod.reflection.MCPNames; import eu.crushedpixel.replaymod.registry.ReplayGuiRegistry; +import eu.crushedpixel.replaymod.replay.MCTimerHandler; import eu.crushedpixel.replaymod.replay.ReplayHandler; import eu.crushedpixel.replaymod.replay.ReplayProcess; -import eu.crushedpixel.replaymod.replay.screenshot.ReplayScreenshot; +import eu.crushedpixel.replaymod.video.ReplayScreenshot; +import eu.crushedpixel.replaymod.video.VideoWriter; public class GuiReplayOverlay extends Gui { @@ -58,7 +60,7 @@ public class GuiReplayOverlay extends Gui { private int sliderY = 10; private int timelineX = sliderX+100+5; - private int realTimelineX = 10 + 3*25; + private int realTimelineX = 10 + 4*25; private int realTimelineY = 33+10; private int ppButtonX = 10; @@ -67,10 +69,13 @@ public class GuiReplayOverlay extends Gui { private int r_ppButtonX = 10; private int r_ppButtonY = realTimelineY+1; - private int place_ButtonX = 35; + private int exportButtonX = 35; + private int exportButtonY = realTimelineY+1; + + private int place_ButtonX = 60; private int place_ButtonY = realTimelineY+1; - private int time_ButtonX = 60; + private int time_ButtonX = 85; private int time_ButtonY = realTimelineY+1; private long lastSystemTime = System.currentTimeMillis(); @@ -109,11 +114,12 @@ public class GuiReplayOverlay extends Gui { @SubscribeEvent public void tick(TickEvent event) { if(!ReplayHandler.replayActive()) return; + if(ReplayHandler.isReplaying() && !ReplayProcess.isVideoRecording()) ReplayProcess.tickReplay(); + ReplayProcess.unblock(); if(ReplayHandler.getCameraEntity() != null) ReplayHandler.getCameraEntity().updateMovement(); - onMouseMove(new MouseEvent()); + if(!ReplayHandler.isReplaying()) onMouseMove(new MouseEvent()); FMLCommonHandler.instance().bus().post(new InputEvent.KeyInputEvent()); - if(ReplayHandler.isReplaying()) ReplayProcess.tickReplay(); } private double lastX, lastY, lastZ; @@ -129,7 +135,7 @@ public class GuiReplayOverlay extends Gui { if(mc != null && mc.thePlayer != null) MinecraftTicker.runMouseKeyboardTick(mc); } - if(mc.getRenderViewEntity() == mc.thePlayer) { + if(mc.getRenderViewEntity() == mc.thePlayer || !mc.getRenderViewEntity().isEntityAlive()) { ReplayHandler.spectateCamera(); ReplayHandler.getCameraEntity().movePath(new Position(lastX, lastY, lastZ, lastPitch, lastYaw)); } else if(!ReplayHandler.isCamera()) { @@ -153,12 +159,22 @@ public class GuiReplayOverlay extends Gui { speedSlider = new GuiReplaySpeedSlider(1, sliderX, sliderY, "Speed"); } + @SubscribeEvent + public void onRenderGui(RenderGameOverlayEvent event) { + if(VideoWriter.isRecording()) { + event.setCanceled(true); + } + } + + @SubscribeEvent public void onRenderGui(RenderGameOverlayEvent.Post event) throws IllegalArgumentException, IllegalAccessException { - if(!ReplayHandler.replayActive() || FMLClientHandler.instance().isGUIOpen(GuiSpectateSelection.class)) { + if(!ReplayHandler.replayActive() || FMLClientHandler.instance().isGUIOpen(GuiSpectateSelection.class) || VideoWriter.isRecording()) { return; } - + + //System.out.println(System.currentTimeMillis()+" | "+MCTimerHandler.getTicks()+" | "+MCTimerHandler.getPartialTicks()); + if(!ReplayGuiRegistry.hidden) ReplayGuiRegistry.hide(); if(event.type == ElementType.PLAYER_LIST) { @@ -227,6 +243,8 @@ public class GuiReplayOverlay extends Gui { ReplayHandler.setSpeed(speedSlider.getSliderValue()); } + } else if(mouseX >= exportButtonX && mouseX <= exportButtonX+20 && mouseY >= exportButtonY && exportButtonY <= exportButtonY+20) { + ReplayHandler.startPath(true); } if(mouseX >= timelineX+4 && mouseX <= width - 18 && mouseY >= 11 && mouseY <= 29) { @@ -256,6 +274,28 @@ public class GuiReplayOverlay extends Gui { } catch(Exception e) {} } + //TODO: Save Video Button + hover = false; + x = 0; + y = 18; + + if(FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { + if(mouseX >= exportButtonX && mouseX <= exportButtonX+20 + && mouseY >= exportButtonY && mouseY <= exportButtonY+20) { + hover = true; + } + } + + if(hover) { + x = 20; + } + + mc.renderEngine.bindTexture(timelineLocation); + + GlStateManager.resetColor(); + this.drawModalRectWithCustomSizedTexture(exportButtonX, exportButtonY, x, y, 20, 20, 64, 64); + + //GlStateManager.resetColor(); //Place Keyframe Button hover = false; @@ -703,7 +743,7 @@ public class GuiReplayOverlay extends Gui { if(ReplayHandler.isReplaying()) { ReplayHandler.interruptReplay(); } else { - ReplayHandler.startPath(); + ReplayHandler.startPath(false); } } diff --git a/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java b/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java index d0f3848f..a6249be7 100644 --- a/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java @@ -11,8 +11,8 @@ import eu.crushedpixel.replaymod.entities.CameraEntity.MoveDirection; import eu.crushedpixel.replaymod.gui.GuiMouseInput; import eu.crushedpixel.replaymod.registry.KeybindRegistry; import eu.crushedpixel.replaymod.replay.ReplayHandler; -import eu.crushedpixel.replaymod.replay.screenshot.ReplayScreenshot; import eu.crushedpixel.replaymod.replay.spectate.SpectateHandler; +import eu.crushedpixel.replaymod.video.ReplayScreenshot; public class KeyInputHandler { @@ -25,7 +25,9 @@ public class KeyInputHandler { if(mc.currentScreen != null) { return; } - + + boolean found = false; + KeyBinding[] keyBindings = Minecraft.getMinecraft().gameSettings.keyBindings; for(KeyBinding kb : keyBindings) { if(!kb.isKeyDown()) { @@ -36,27 +38,22 @@ public class KeyInputHandler { if(ReplayHandler.isCamera()) { if(kb.getKeyDescription().equals("key.forward")) { ReplayHandler.getCameraEntity().setMovement(MoveDirection.FORWARD); - continue; } if(kb.getKeyDescription().equals("key.back")) { ReplayHandler.getCameraEntity().setMovement(MoveDirection.BACKWARD); - continue; } if(kb.getKeyDescription().equals("key.jump")) { ReplayHandler.getCameraEntity().setMovement(MoveDirection.UP); - continue; } if(kb.getKeyDescription().equals("key.left")) { ReplayHandler.getCameraEntity().setMovement(MoveDirection.LEFT); - continue; } if(kb.getKeyDescription().equals("key.right")) { ReplayHandler.getCameraEntity().setMovement(MoveDirection.RIGHT); - continue; } } if(kb.getKeyDescription().equals("key.sneak")) { @@ -65,35 +62,31 @@ public class KeyInputHandler { } else { ReplayHandler.spectateCamera(); } - continue; } if(kb.getKeyDescription().equals("key.chat")) { mc.displayGuiScreen(new GuiMouseInput()); - continue; } //Custom registered handlers - if(kb.getKeyDescription().equals(KeybindRegistry.KEY_THUMBNAIL) && kb.isPressed()) { + if(kb.getKeyDescription().equals(KeybindRegistry.KEY_THUMBNAIL) && kb.isPressed() && !found) { + System.out.println("thumbnail key pressed"); ReplayScreenshot.prepareScreenshot(); GuiReplayOverlay.requestScreenshot(); - continue; } - if(kb.getKeyDescription().equals(KeybindRegistry.KEY_SPECTATE) && kb.isPressed()) { + if(kb.getKeyDescription().equals(KeybindRegistry.KEY_SPECTATE) && kb.isPressed() && !found) { SpectateHandler.openSpectateSelection(); - continue; } - if(kb.getKeyDescription().equals(KeybindRegistry.KEY_LIGHTING) && kb.isPressed()) { + if(kb.getKeyDescription().equals(KeybindRegistry.KEY_LIGHTING) && kb.isPressed() && !found) { ReplayMod.replaySettings.setLightingEnabled(!ReplayMod.replaySettings.isLightingEnabled()); - continue; } } catch(Exception e) { e.printStackTrace(); } - + found = true; } } } diff --git a/src/main/java/eu/crushedpixel/replaymod/events/MinecraftTicker.java b/src/main/java/eu/crushedpixel/replaymod/events/MinecraftTicker.java index 0358a709..83a76390 100644 --- a/src/main/java/eu/crushedpixel/replaymod/events/MinecraftTicker.java +++ b/src/main/java/eu/crushedpixel/replaymod/events/MinecraftTicker.java @@ -76,6 +76,7 @@ public class MinecraftTicker { public static void runMouseKeyboardTick(Minecraft mc) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException { + if(mc.thePlayer == null) return; try { mc.mcProfiler.endStartSection("mouse"); int i; @@ -435,8 +436,6 @@ public class MinecraftTicker { if(mc != null) systemTime.set(mc, getSystemTime.invoke(mc)); - } catch(Exception e) { - e.printStackTrace(); - } + } catch(Exception e) {} } } diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/GuiReplayListEntry.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiReplayListEntry.java similarity index 93% rename from src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/GuiReplayListEntry.java rename to src/main/java/eu/crushedpixel/replaymod/gui/GuiReplayListEntry.java index 35db84ac..df6fca7a 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/GuiReplayListEntry.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiReplayListEntry.java @@ -1,4 +1,4 @@ -package eu.crushedpixel.replaymod.gui.replaymanager; +package eu.crushedpixel.replaymod.gui; import java.awt.image.BufferedImage; import java.io.File; @@ -18,6 +18,7 @@ import net.minecraft.client.gui.GuiListExtended.IGuiListEntry; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.util.ResourceLocation; +import eu.crushedpixel.replaymod.gui.replaymanager.ResourceHelper; import eu.crushedpixel.replaymod.recording.ReplayMetaData; public class GuiReplayListEntry implements IGuiListEntry { @@ -66,7 +67,7 @@ public class GuiReplayListEntry implements IGuiListEntry { public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected) { try { minecraft.fontRendererObj.drawString(fileName, x + 3, y + 1, 16777215); - + if(y < -slotHeight || y > parent.height) { if(registered) { registered = false; @@ -79,7 +80,11 @@ public class GuiReplayListEntry implements IGuiListEntry { } else { if(!registered) { textureResource = new ResourceLocation("thumbs/"+fileName); - image = ImageIO.read(imageFile); + if(imageFile == null) { + image = ResourceHelper.getDefaultThumbnail(); + } else { + image = ImageIO.read(imageFile); + } dynTex = new DynamicTexture(image); minecraft.getTextureManager().loadTexture(textureResource, dynTex); dynTex.updateDynamicTexture(); diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/GuiReplayListExtended.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiReplayListExtended.java similarity index 95% rename from src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/GuiReplayListExtended.java rename to src/main/java/eu/crushedpixel/replaymod/gui/GuiReplayListExtended.java index b3ae5f86..6d0540c3 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/GuiReplayListExtended.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiReplayListExtended.java @@ -1,4 +1,4 @@ -package eu.crushedpixel.replaymod.gui.replaymanager; +package eu.crushedpixel.replaymod.gui; import java.io.File; import java.util.ArrayList; @@ -15,29 +15,21 @@ import org.lwjgl.input.Mouse; import eu.crushedpixel.replaymod.recording.ReplayMetaData; -public class GuiReplayListExtended extends GuiListExtended { +public abstract class GuiReplayListExtended extends GuiListExtended { - private GuiReplayManager parent; - public int selected = -1; - - public GuiReplayListExtended(GuiReplayManager parent, Minecraft mcIn, int p_i45010_2_, + public GuiReplayListExtended(Minecraft mcIn, int p_i45010_2_, int p_i45010_3_, int p_i45010_4_, int p_i45010_5_, int p_i45010_6_) { super(mcIn, p_i45010_2_, p_i45010_3_, p_i45010_4_, p_i45010_5_, p_i45010_6_); - this.parent = parent; } + public int selected = -1; @Override protected void elementClicked(int slotIndex, boolean isDoubleClick, int mouseX, int mouseY) { super.elementClicked(slotIndex, isDoubleClick, mouseX, mouseY); this.selected = slotIndex; - parent.setButtonsEnabled(true); - if(isDoubleClick) { - parent.loadReplay(slotIndex); - } } - @Override protected void drawSelectionBox(int p_148120_1_, int p_148120_2_, int p_148120_3_, int p_148120_4_) diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiReplaySettings.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiReplaySettings.java index 2e4ee053..3ae0f70f 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/GuiReplaySettings.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiReplaySettings.java @@ -22,12 +22,14 @@ public class GuiReplaySettings extends GuiScreen { private GuiScreen parentGuiScreen; protected String screenTitle = "Replay Mod Settings"; - private static final int MAXSIZE_SLIDER_ID = 9003; + //TODO: Move to GuiConstants + private static final int QUALITY_SLIDER_ID = 9003; private static final int RECORDSERVER_ID = 9004; private static final int RECORDSP_ID = 9005; private static final int SEND_CHAT = 9006; private static final int FORCE_LINEAR = 9007; private static final int ENABLE_LIGHTING = 9008; + private static final int FRAMERATE_SLIDER_ID = 9009; private GuiButton recordServerButton, recordSPButton, sendChatButton, linearButton, lightingButton; @@ -47,18 +49,11 @@ public class GuiReplaySettings extends GuiScreen { int i = 0; for (Entry e : aoptions.entrySet()) { - /* - if(e.getKey().equals("Maximum File Size")) { - float minValue = -1; - float maxValue = 10000; - float valueSteps = 1; - - int val = (Integer)e.getValue(); - this.buttonList.add(new GuiSizeLimitOptionSlider(MAXSIZE_SLIDER_ID, this.width / 2 - 155 + i % 2 * 160, this.height / 6 + 24 * (i >> 1), 0, 39, 1, val, e.getKey())); - - } else - */ - if(e.getKey().equals("Enable Notifications")) { + if(e.getKey().equals("Video Quality")) { + this.buttonList.add(new GuiVideoQualitySlider(QUALITY_SLIDER_ID, this.width / 2 - 155 + i % 2 * 160, this.height / 6 + 24 * (i >> 1), (Float)e.getValue(), "Video Quality")); + } else if(e.getKey().equals("Video Framerate")) { + this.buttonList.add(new GuiVideoFramerateSlider(FRAMERATE_SLIDER_ID, this.width / 2 - 155 + i % 2 * 160, this.height / 6 + 24 * (i >> 1), (Integer)e.getValue(), "Video Framerate")); + } else if(e.getKey().equals("Enable Notifications")) { sendChatButton = new GuiButton(SEND_CHAT, this.width / 2 - 155 + i % 2 * 160, this.height / 6 + 24 * (i >> 1), 150, 20, "Enable Notifications: "+onOff((Boolean)e.getValue())); this.buttonList.add(sendChatButton); } else if(e.getKey().equals("Record Server")) { diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiSizeLimitOptionSlider.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiSizeLimitOptionSlider.java deleted file mode 100644 index 9f8406b8..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/gui/GuiSizeLimitOptionSlider.java +++ /dev/null @@ -1,185 +0,0 @@ -package eu.crushedpixel.replaymod.gui; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiButton; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.settings.GameSettings; -import net.minecraft.util.MathHelper; -import eu.crushedpixel.replaymod.ReplayMod; - -public class GuiSizeLimitOptionSlider extends GuiButton { - - public GuiSizeLimitOptionSlider(int buttonId, int p_i45017_2_, int p_i45017_3_, float valueMin, float valueMax, float valueStep, int sliderValue, String displayKey) - { - super(buttonId, p_i45017_2_, p_i45017_3_, 150, 20, ""); - this.valueMin = valueMin; - this.valueMax = valueMax; - this.valueStep = valueStep; - this.sliderValue = sliderValue; - this.displayString = displayKey+": "+translate(sliderValue); - this.displayKey = displayKey; - - float val = realToNormalized(convertScaleRet(sliderValue)); - - - this.sliderValue = val; - } - - private float sliderValue; - public boolean dragging; - private GameSettings.Options options; - - private float valueStep, valueMin, valueMax; - private String displayKey; - - - protected int getHoverState(boolean mouseOver) { - return 0; - } - - - @Override - protected void mouseDragged(Minecraft mc, int mouseX, int mouseY) { - if (this.visible) { - if (this.dragging) { - sliderValue = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8); - sliderValue = MathHelper.clamp_float(sliderValue, 0.0F, 1.0F); - float f = denormalizeValue(sliderValue); - sliderValue = normalizeValue(f); - this.displayString = displayKey+": "+translate(convertScale(normalizedToReal(sliderValue))); - ReplayMod.replaySettings.setMaximumFileSize(convertScale(normalizedToReal(sliderValue))); - } - - mc.getTextureManager().bindTexture(buttonTextures); - GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); - this.drawTexturedModalRect(this.xPosition + (int)(sliderValue * (float)(this.width - 8)), this.yPosition, 0, 66, 4, 20); - this.drawTexturedModalRect(this.xPosition + (int)(sliderValue * (float)(this.width - 8)) + 4, this.yPosition, 196, 66, 4, 20); - } - } - - public static String translate(int value) { - if(value == 0) { - return "Unlimited"; - } else { - return convertToStringRepresentation(value); - } - } - - public static int convertScale(float value) { - if(value == 0) { - return 0; - } else { - if(value > 20) { - value = 1024+((value-20)*1024); - } else { - value = value*50; - } - if(value == 1000) { - value = 1024; - } - return Math.round(value); - } - } - - public static int convertScaleRet(float value) { - if(value == 0) { - return 0; - } else { - if(value >= 1024) { - value = 20+((value-1024)/1024); - } else { - value = value/50; - } - if(value == 1024) { - value = 1000; - } - return Math.round(value); - } - } - - public static String convertToStringRepresentation(final long value){ - long M = 1; - long G = 1024; - long T = G * 1024; - - final long[] dividers = new long[] { T, G, M}; - final String[] units = new String[] { "TB", "GB", "MB"}; - if(value < 1) - throw new IllegalArgumentException("Invalid file size: " + value); - String result = null; - for(int i = 0; i < dividers.length; i++){ - final long divider = dividers[i]; - if(value >= divider){ - result = format(value, divider, units[i]); - break; - } - } - return result; - } - - private static String format(final long value, - final long divider, - final String unit){ - final double result = - divider > 1 ? (double) value / (double) divider : (double) value; - return String.format("%.0f %s", Double.valueOf(result), unit); - } - - public float normalizedToReal(float value) { - float min = 0 - valueMin; - float max = valueMax + min; - - return Math.round(value*(max) - min); - } - - public float realToNormalized(float value) { - float min = 0 - valueMin; - float max = valueMax + min; - - return value/(max) - min; - } - - public float normalizeValue(float p_148266_1_) - { - return MathHelper.clamp_float((this.snapToStepClamp(p_148266_1_) - this.valueMin) / (this.valueMax - this.valueMin), 0.0F, 1.0F); - } - - public float denormalizeValue(float p_148262_1_) - { - return this.snapToStepClamp(this.valueMin + (this.valueMax - this.valueMin) * MathHelper.clamp_float(p_148262_1_, 0.0F, 1.0F)); - } - - public float snapToStepClamp(float p_148268_1_) - { - p_148268_1_ = this.snapToStep(p_148268_1_); - return MathHelper.clamp_float(p_148268_1_, this.valueMin, this.valueMax); - } - - protected float snapToStep(float p_148264_1_) - { - if (this.valueStep > 0.0F) - { - p_148264_1_ = this.valueStep * (float)Math.round(p_148264_1_ / this.valueStep); - } - - return p_148264_1_; - } - - public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) - { - if (super.mousePressed(mc, mouseX, mouseY)) - { - this.dragging = true; - return true; - } - else - { - return false; - } - } - - public void mouseReleased(int mouseX, int mouseY) - { - this.dragging = false; - } -} diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiVideoFramerateSlider.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiVideoFramerateSlider.java new file mode 100644 index 00000000..5d839709 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiVideoFramerateSlider.java @@ -0,0 +1,71 @@ +package eu.crushedpixel.replaymod.gui; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.settings.GameSettings; +import net.minecraft.util.MathHelper; +import eu.crushedpixel.replaymod.ReplayMod; + +public class GuiVideoFramerateSlider extends GuiButton { + + public GuiVideoFramerateSlider(int buttonId, int p_i45017_2_, int p_i45017_3_, int initialFramerate, String displayKey) { + super(buttonId, p_i45017_2_, p_i45017_3_, 150, 20, ""); + this.sliderValue = normalizeValue(initialFramerate); + this.displayString = displayKey+": "+translate(initialFramerate); + this.displayKey = displayKey; + } + + private String displayKey; + private float sliderValue; + public boolean dragging; + + private String translate(int value) { + return String.valueOf(value); + } + + protected int getHoverState(boolean mouseOver) { + return 0; + } + + @Override + protected void mouseDragged(Minecraft mc, int mouseX, int mouseY) { + if (this.visible) { + if (this.dragging) { + sliderValue = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8); + sliderValue = MathHelper.clamp_float(sliderValue, 0.0F, 1.0F); + int f = denormalizeValue(sliderValue); + this.displayString = displayKey+": "+translate(f); + ReplayMod.replaySettings.setVideoFramerate(f); + } + + mc.getTextureManager().bindTexture(buttonTextures); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + this.drawTexturedModalRect(this.xPosition + (int)(sliderValue * (float)(this.width - 8)), this.yPosition, 0, 66, 4, 20); + this.drawTexturedModalRect(this.xPosition + (int)(sliderValue * (float)(this.width - 8)) + 4, this.yPosition, 196, 66, 4, 20); + } + } + + private float normalizeValue(int val) { + return (val-10)/110f; + } + + private int denormalizeValue(float val) { + //Transfers the value ranging from 0.0 to 1.0 to the scale of 10 to 120 + float r = 110f*val; + return Math.round(10+r); + } + + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { + if (super.mousePressed(mc, mouseX, mouseY)) { + this.dragging = true; + return true; + } else { + return false; + } + } + + public void mouseReleased(int mouseX, int mouseY) { + this.dragging = false; + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiVideoQualitySlider.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiVideoQualitySlider.java new file mode 100644 index 00000000..30a253eb --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiVideoQualitySlider.java @@ -0,0 +1,85 @@ +package eu.crushedpixel.replaymod.gui; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.settings.GameSettings; +import net.minecraft.util.MathHelper; +import eu.crushedpixel.replaymod.ReplayMod; + +public class GuiVideoQualitySlider extends GuiButton { + + public GuiVideoQualitySlider(int buttonId, int p_i45017_2_, int p_i45017_3_, float initialQuality, String displayKey) { + super(buttonId, p_i45017_2_, p_i45017_3_, 150, 20, ""); + this.sliderValue = normalizeValue(initialQuality); + this.displayString = displayKey+": "+translate(initialQuality); + this.displayKey = displayKey; + } + + private String displayKey; + private float sliderValue; + public boolean dragging; + + private String translate(float value) { + if(value <= 0.3) { + return "Draft"; + } else if(value <= 0.5) { + return "Normal"; + } else if(value <= 0.7) { + return "Good"; + } + return "Best"; + } + + protected int getHoverState(boolean mouseOver) { + return 0; + } + + @Override + protected void mouseDragged(Minecraft mc, int mouseX, int mouseY) { + if (this.visible) { + if (this.dragging) { + sliderValue = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8); + sliderValue = MathHelper.clamp_float(sliderValue, 0.0F, 1.0F); + float f = denormalizeValue(sliderValue); + f = snapValue(f); + sliderValue = normalizeValue(f); + this.displayString = displayKey+": "+translate(f); + ReplayMod.replaySettings.setVideoQuality(f); + } + + mc.getTextureManager().bindTexture(buttonTextures); + GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F); + this.drawTexturedModalRect(this.xPosition + (int)(sliderValue * (float)(this.width - 8)), this.yPosition, 0, 66, 4, 20); + this.drawTexturedModalRect(this.xPosition + (int)(sliderValue * (float)(this.width - 8)) + 4, this.yPosition, 196, 66, 4, 20); + } + } + + private float snapValue(float val) { + int i = Math.round(val*10); + return i/10f; + } + + private float normalizeValue(float val) { + return (val-0.1f)/0.8f; + } + + private float denormalizeValue(float val) { + //Transfers the value ranging from 0.0 to 1.0 to the scale of 0.1 to 0.9 + float r = 0.8f*val; + return 0.1f+r; + } + + public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { + if (super.mousePressed(mc, mouseX, mouseY)) { + this.dragging = true; + return true; + } else { + return false; + } + } + + public void mouseReleased(int mouseX, int mouseY) { + this.dragging = false; + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/online/GuiReplayCenter.java b/src/main/java/eu/crushedpixel/replaymod/gui/online/GuiReplayCenter.java index 68f1e259..ed0ce425 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/online/GuiReplayCenter.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/online/GuiReplayCenter.java @@ -1,6 +1,7 @@ package eu.crushedpixel.replaymod.gui.online; import java.awt.Color; +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -14,19 +15,26 @@ import net.minecraft.client.resources.I18n; import org.lwjgl.input.Keyboard; -import scala.actors.threadpool.Arrays; - -import com.mojang.realmsclient.gui.GuiCallback; - import eu.crushedpixel.replaymod.ReplayMod; import eu.crushedpixel.replaymod.api.client.ApiException; import eu.crushedpixel.replaymod.api.client.holders.FileInfo; import eu.crushedpixel.replaymod.gui.GuiConstants; +import eu.crushedpixel.replaymod.gui.GuiReplayListExtended; import eu.crushedpixel.replaymod.gui.replaymanager.GuiReplayManager; import eu.crushedpixel.replaymod.online.authentication.AuthenticationHandler; public class GuiReplayCenter extends GuiScreen implements GuiYesNoCallback { + private enum Tab { + RECENT_FILES, BEST_FILES, MY_FILES, SEARCH; + } + + private GuiReplayListExtended currentList; + + private ReplayFileList recentFileList, bestFileList, myFileList, searchFileList; + + private Tab currentTab = Tab.RECENT_FILES; + @Override public void initGui() { Keyboard.enableRepeatEvents(true); @@ -40,7 +48,7 @@ public class GuiReplayCenter extends GuiScreen implements GuiYesNoCallback { GuiButton recentButton = new GuiButton(GuiConstants.CENTER_RECENT_BUTTON, 20, 30, "Newest Replays"); buttonBar.add(recentButton); - + GuiButton bestButton = new GuiButton(GuiConstants.CENTER_BEST_BUTTON, 20, 30, "Best Replays"); buttonBar.add(bestButton); @@ -70,7 +78,7 @@ public class GuiReplayCenter extends GuiScreen implements GuiYesNoCallback { GuiButton exitButton = new GuiButton(GuiConstants.CENTER_BACK_BUTTON, 20, 20, "Main Menu"); bottomBar.add(exitButton); - + GuiButton managerButton = new GuiButton(GuiConstants.CENTER_MANAGER_BUTTON, 20, 20, "Replay Manager"); bottomBar.add(managerButton); @@ -91,7 +99,7 @@ public class GuiReplayCenter extends GuiScreen implements GuiYesNoCallback { i++; } - + showOnlineRecent(); } @@ -107,11 +115,11 @@ public class GuiReplayCenter extends GuiScreen implements GuiYesNoCallback { } else if(button.id == GuiConstants.CENTER_RECENT_BUTTON) { showOnlineRecent(); } else if(button.id == GuiConstants.CENTER_BEST_BUTTON) { - + } else if(button.id == GuiConstants.CENTER_MY_REPLAYS_BUTTON) { - + } else if(button.id == GuiConstants.CENTER_SEARCH_BUTTON) { - + } } @@ -144,22 +152,66 @@ public class GuiReplayCenter extends GuiScreen implements GuiYesNoCallback { this.drawDefaultBackground(); this.drawCenteredString(fontRendererObj, "Replay Center", this.width/2, 8, Color.WHITE.getRGB()); + if(currentList != null) { + currentList.drawScreen(mouseX, mouseY, partialTicks); + } + super.drawScreen(mouseX, mouseY, partialTicks); } + @Override + public void handleMouseInput() throws IOException { + super.handleMouseInput(); + if(currentList != null) { + this.currentList.handleMouseInput(); + } + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { + super.mouseClicked(mouseX, mouseY, mouseButton); + if(currentList != null) { + this.currentList.mouseClicked(mouseX, mouseY, mouseButton); + } + } + + @Override + protected void mouseReleased(int mouseX, int mouseY, int state) { + super.mouseReleased(mouseX, mouseY, state); + if(currentList != null) { + this.currentList.mouseReleased(mouseX, mouseY, state); + } + } + @Override public void onGuiClosed() { Keyboard.enableRepeatEvents(false); } public void showOnlineRecent() { - mc.addScheduledTask(new Runnable() { + Thread t = new Thread(new Runnable() { @Override public void run() { try { + if(recentFileList == null) { + recentFileList = new ReplayFileList(mc, width, height, 50, height-40, 36); + } else { + recentFileList.clearEntries(); + recentFileList.width = width; + recentFileList.height = height; + recentFileList.top = 50; + recentFileList.bottom = height-40; + } + currentTab = Tab.RECENT_FILES; + currentList = recentFileList; FileInfo[] files = ReplayMod.apiClient.getRecentFiles(); for(FileInfo i : files) { - //TODO: Display the Files + File tmp = null; + if(i.hasThumbnail()) { + tmp = File.createTempFile("thumb_online_"+i.getId(), "jpg"); + ReplayMod.apiClient.downloadThumbnail(i.getId(), tmp); + } + recentFileList.addEntry(i.getName(), i.getMetadata(), tmp); } } catch (IOException e) { e.printStackTrace(); @@ -168,7 +220,8 @@ public class GuiReplayCenter extends GuiScreen implements GuiYesNoCallback { } } }); - + t.start(); + } } diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/online/ReplayFileList.java b/src/main/java/eu/crushedpixel/replaymod/gui/online/ReplayFileList.java new file mode 100644 index 00000000..cbb660ad --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/gui/online/ReplayFileList.java @@ -0,0 +1,12 @@ +package eu.crushedpixel.replaymod.gui.online; + +import net.minecraft.client.Minecraft; +import eu.crushedpixel.replaymod.gui.GuiReplayListExtended; + +public class ReplayFileList extends GuiReplayListExtended { + + public ReplayFileList(Minecraft mcIn, int p_i45010_2_, int p_i45010_3_, + int p_i45010_4_, int p_i45010_5_, int p_i45010_6_) { + super(mcIn, p_i45010_2_, p_i45010_3_, p_i45010_4_, p_i45010_5_, p_i45010_6_); + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/GuiReplayManager.java b/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/GuiReplayManager.java index 828953dc..541ee5c6 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/GuiReplayManager.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/GuiReplayManager.java @@ -37,6 +37,7 @@ import org.lwjgl.input.Keyboard; import com.google.gson.Gson; import com.mojang.realmsclient.util.Pair; +import eu.crushedpixel.replaymod.gui.GuiReplayListExtended; import eu.crushedpixel.replaymod.gui.GuiReplaySettings; import eu.crushedpixel.replaymod.gui.online.GuiUploadFile; import eu.crushedpixel.replaymod.online.authentication.AuthenticationHandler; @@ -95,16 +96,14 @@ public class GuiReplayManager extends GuiScreen implements GuiYesNoCallback { img = ImageUtils.scaleImage(bimg, new Dimension(1280, 720)); } } - - //If thumb is null, set image to placeholder - if(img == null) { - img = ImageIO.read(MCPNames.class.getClassLoader().getResourceAsStream("default_thumb.jpg")); + + File tmp = null; + if(img != null) { + tmp = File.createTempFile(FilenameUtils.getBaseName(file.getAbsolutePath()), "jpg"); + tmp.deleteOnExit(); + + ImageIO.write(img, "jpg", tmp); } - - File tmp = File.createTempFile(FilenameUtils.getBaseName(file.getAbsolutePath()), "jpg"); - tmp.deleteOnExit(); - - ImageIO.write(img, "jpg", tmp); InputStream is = archive.getInputStream(metadata); BufferedReader br = new BufferedReader(new InputStreamReader(is)); @@ -116,9 +115,7 @@ public class GuiReplayManager extends GuiScreen implements GuiYesNoCallback { replayFileList.add(Pair.of(Pair.of(file, metaData), tmp)); archive.close(); - } catch(Exception e) { - e.printStackTrace(); - } + } catch(Exception e) {} } } @@ -144,7 +141,7 @@ public class GuiReplayManager extends GuiScreen implements GuiYesNoCallback { @Override public void initGui() { - replayGuiList = new GuiReplayListExtended(this, this.mc, this.width, this.height, 32, this.height - 64, 36); + replayGuiList = new ReplayManagerReplayList(this, this.mc, this.width, this.height, 32, this.height - 64, 36); Keyboard.enableRepeatEvents(true); this.buttonList.clear(); @@ -170,26 +167,26 @@ public class GuiReplayManager extends GuiScreen implements GuiYesNoCallback { setButtonsEnabled(false); } - public void handleMouseInput() throws IOException - { + @Override + public void handleMouseInput() throws IOException { super.handleMouseInput(); this.replayGuiList.handleMouseInput(); } - protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException - { + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { super.mouseClicked(mouseX, mouseY, mouseButton); this.replayGuiList.mouseClicked(mouseX, mouseY, mouseButton); } - protected void mouseReleased(int mouseX, int mouseY, int state) - { + @Override + protected void mouseReleased(int mouseX, int mouseY, int state) { super.mouseReleased(mouseX, mouseY, state); this.replayGuiList.mouseReleased(mouseX, mouseY, state); } - public void drawScreen(int mouseX, int mouseY, float partialTicks) - { + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { this.hoveringText = null; this.drawDefaultBackground(); this.replayGuiList.drawScreen(mouseX, mouseY, partialTicks); @@ -198,8 +195,7 @@ public class GuiReplayManager extends GuiScreen implements GuiYesNoCallback { } @Override - protected void actionPerformed(GuiButton button) throws IOException - { + protected void actionPerformed(GuiButton button) throws IOException { if(button.enabled) { if(button.id == LOAD_BUTTON_ID) { loadReplay(replayGuiList.selected); diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/ReplayManagerReplayList.java b/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/ReplayManagerReplayList.java new file mode 100644 index 00000000..5e9d6d5b --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/ReplayManagerReplayList.java @@ -0,0 +1,29 @@ +package eu.crushedpixel.replaymod.gui.replaymanager; + +import eu.crushedpixel.replaymod.gui.GuiReplayListExtended; +import net.minecraft.client.Minecraft; + +public class ReplayManagerReplayList extends GuiReplayListExtended { + + private GuiReplayManager parent; + + public ReplayManagerReplayList(GuiReplayManager parent, Minecraft mcIn, + int p_i45010_2_, int p_i45010_3_, int p_i45010_4_, int p_i45010_5_, + int p_i45010_6_) { + super(mcIn, p_i45010_2_, p_i45010_3_, p_i45010_4_, p_i45010_5_, + p_i45010_6_); + + this.parent = parent; + } + + @Override + protected void elementClicked(int slotIndex, boolean isDoubleClick, + int mouseX, int mouseY) { + super.elementClicked(slotIndex, isDoubleClick, mouseX, mouseY); + parent.setButtonsEnabled(true); + if(isDoubleClick) { + parent.loadReplay(slotIndex); + } + } + +} diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/ResourceHelper.java b/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/ResourceHelper.java index 2e27fd99..9a629782 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/ResourceHelper.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/replaymanager/ResourceHelper.java @@ -1,29 +1,47 @@ package eu.crushedpixel.replaymod.gui.replaymanager; +import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; +import javax.imageio.ImageIO; + +import eu.crushedpixel.replaymod.reflection.MCPNames; import net.minecraft.client.Minecraft; import net.minecraft.util.ResourceLocation; public class ResourceHelper { + private static BufferedImage defaultThumb; + + static { + try { + defaultThumb = ImageIO.read(MCPNames.class.getClassLoader().getResourceAsStream("default_thumb.jpg")); + } catch(Exception e) { + defaultThumb = new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR); + e.printStackTrace(); + } + } private static List openResources = new ArrayList(); - + public static void registerResource(ResourceLocation loc) { openResources.add(loc); } - + public static void freeResource(ResourceLocation loc) { Minecraft.getMinecraft().getTextureManager().deleteTexture(loc); openResources.remove(loc); } - + public static void freeAllResources() { for(ResourceLocation loc : openResources) { Minecraft.getMinecraft().getTextureManager().deleteTexture(loc); } - + openResources = new ArrayList(); } + + public static BufferedImage getDefaultThumbnail() { + return defaultThumb; + } } diff --git a/src/main/java/eu/crushedpixel/replaymod/holders/Position.java b/src/main/java/eu/crushedpixel/replaymod/holders/Position.java index 51df03c0..3de615ac 100644 --- a/src/main/java/eu/crushedpixel/replaymod/holders/Position.java +++ b/src/main/java/eu/crushedpixel/replaymod/holders/Position.java @@ -63,5 +63,8 @@ public class Position { this.yaw = yaw; } - + @Override + public String toString() { + return "X="+x+", Y="+y+", Z="+z+", Yaw="+yaw+", Pitch="+pitch; + } } diff --git a/src/main/java/eu/crushedpixel/replaymod/interpolation/LinearInterpolation.java b/src/main/java/eu/crushedpixel/replaymod/interpolation/LinearInterpolation.java index eba6e0cc..e0125449 100644 --- a/src/main/java/eu/crushedpixel/replaymod/interpolation/LinearInterpolation.java +++ b/src/main/java/eu/crushedpixel/replaymod/interpolation/LinearInterpolation.java @@ -7,6 +7,10 @@ import akka.japi.Pair; public abstract class LinearInterpolation { + public LinearInterpolation() { + points = new ArrayList(); + } + protected List points = new ArrayList(); public abstract K getPoint(float position); @@ -21,9 +25,9 @@ public abstract class LinearInterpolation { protected Pair> getCurrentPoints(float position) { if(points.size() == 0) return null; - float pos = position * (points.size()-1); - int cubicNum = Math.round(pos); - float cubicPos = (pos - cubicNum); + position = position * (points.size()-1); + int cubicNum = (int)Math.min(points.size()-1, position); + float cubicPos = (position - cubicNum); if(cubicNum == points.size()-1) { cubicNum--; diff --git a/src/main/java/eu/crushedpixel/replaymod/interpolation/LinearPoint.java b/src/main/java/eu/crushedpixel/replaymod/interpolation/LinearPoint.java index 262ae07b..c41b878e 100644 --- a/src/main/java/eu/crushedpixel/replaymod/interpolation/LinearPoint.java +++ b/src/main/java/eu/crushedpixel/replaymod/interpolation/LinearPoint.java @@ -6,11 +6,16 @@ import eu.crushedpixel.replaymod.holders.Position; public class LinearPoint extends LinearInterpolation { @Override - public Position getPoint(float position) { - Pair> pair = getCurrentPoints(position); + public Position getPoint(float positionIn) { + Pair> pair = getCurrentPoints(positionIn); if(pair == null) return null; float perc = pair.first(); + //float position = positionIn * (points.size()-1); + //int cubicNum = (int)Math.min(points.size()-1, position); + //float perc = (position - cubicNum); + + //System.out.println(cubicNum+" | "+perc+" | "+positionIn); Position first = pair.second().first(); Position second = pair.second().second(); @@ -23,7 +28,7 @@ public class LinearPoint extends LinearInterpolation { float yaw = (float)getInterpolatedValue(first.getYaw(), second.getYaw(), perc); Position inter = new Position(x, y, z, pitch, yaw); + //System.out.println(position+" | "+cubicNum+" | "+perc+" | "+first+" | "+second+" | "+inter); return inter; } - } diff --git a/src/main/java/eu/crushedpixel/replaymod/recording/ConnectionEventHandler.java b/src/main/java/eu/crushedpixel/replaymod/recording/ConnectionEventHandler.java index 82480086..fac81bc0 100644 --- a/src/main/java/eu/crushedpixel/replaymod/recording/ConnectionEventHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/recording/ConnectionEventHandler.java @@ -99,12 +99,10 @@ public class ConnectionEventHandler { currentFile.createNewFile(); - int maxFileSize = ReplayMod.replaySettings.getMaximumFileSize(); - PacketListener insert = null; pipeline.addBefore(packetHandlerKey, "replay_recorder", insert = new PacketListener - (currentFile, fileName, worldName, System.currentTimeMillis(), maxFileSize, event.isLocal)); + (currentFile, fileName, worldName, System.currentTimeMillis(), event.isLocal)); ChatMessageRequests.addChatMessage("Recording started!", ChatMessageType.INFORMATION); isRecording = true; diff --git a/src/main/java/eu/crushedpixel/replaymod/recording/DataListener.java b/src/main/java/eu/crushedpixel/replaymod/recording/DataListener.java index 19015835..16a4fa98 100644 --- a/src/main/java/eu/crushedpixel/replaymod/recording/DataListener.java +++ b/src/main/java/eu/crushedpixel/replaymod/recording/DataListener.java @@ -31,8 +31,6 @@ public abstract class DataListener extends ChannelInboundHandlerAdapter { protected File file; protected Long startTime = null; - protected long maxSize; - protected long totalBytes = 0; protected String name; protected String worldName; @@ -53,10 +51,9 @@ public abstract class DataListener extends ChannelInboundHandlerAdapter { System.out.println(worldName); } - public DataListener(File file, String name, String worldName, long startTime, int maxSize, boolean singleplayer) throws FileNotFoundException { + public DataListener(File file, String name, String worldName, long startTime, boolean singleplayer) throws FileNotFoundException { this.file = file; this.startTime = startTime; - this.maxSize = maxSize*1024*1024; this.name = name; this.worldName = worldName; this.singleplayer = singleplayer; diff --git a/src/main/java/eu/crushedpixel/replaymod/recording/PacketListener.java b/src/main/java/eu/crushedpixel/replaymod/recording/PacketListener.java index 81380155..d12954c1 100644 --- a/src/main/java/eu/crushedpixel/replaymod/recording/PacketListener.java +++ b/src/main/java/eu/crushedpixel/replaymod/recording/PacketListener.java @@ -24,8 +24,8 @@ import eu.crushedpixel.replaymod.reflection.MCPNames; public class PacketListener extends DataListener { - public PacketListener(File file, String name, String worldName, long startTime, int maxSize, boolean singleplayer) throws FileNotFoundException { - super(file, name, worldName, startTime, maxSize, singleplayer); + public PacketListener(File file, String name, String worldName, long startTime, boolean singleplayer) throws FileNotFoundException { + super(file, name, worldName, startTime, singleplayer); } private static final Minecraft mc = Minecraft.getMinecraft(); @@ -132,13 +132,6 @@ public class PacketListener extends DataListener { byte[] array = new byte[bb.readableBytes()]; bb.readBytes(array); - totalBytes += (array.length + (2*4)); //two Integer values and the Packet size - if(totalBytes >= maxSize && maxSize > 0) { - ChatMessageRequests.addChatMessage("Maximum file size exceeded", ChatMessageType.WARNING); - ChatMessageRequests.addChatMessage("The Recording has been stopped", ChatMessageType.WARNING); - alive = false; - } - bb.readerIndex(0); return new PacketData(array, timestamp); diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/MCTimerHandler.java b/src/main/java/eu/crushedpixel/replaymod/replay/MCTimerHandler.java new file mode 100644 index 00000000..e1507f3c --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/replay/MCTimerHandler.java @@ -0,0 +1,175 @@ +package eu.crushedpixel.replaymod.replay; + +import java.lang.reflect.Field; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.MathHelper; +import net.minecraft.util.Timer; +import eu.crushedpixel.replaymod.reflection.MCPNames; +import eu.crushedpixel.replaymod.video.ReplayTimer; + +public class MCTimerHandler { + + private static Field mcTimer; + private static Minecraft mc = Minecraft.getMinecraft(); + + private static ReplayTimer rpt = new ReplayTimer(20); + private static Timer timerBefore; + + static { + try { + mcTimer = Minecraft.class.getDeclaredField(MCPNames.field("field_71428_T")); + mcTimer.setAccessible(true); + } catch(Exception e) { + e.printStackTrace(); + } + } + + public static void setActiveTimer() { + try { + if(timerBefore != null) { + mcTimer.set(mc, timerBefore); + } + } catch(Exception e) { + e.printStackTrace(); + } + } + + public static void setPassiveTimer() { + try { + if(!(getTimer() instanceof ReplayTimer)) { + timerBefore = getTimer(); + System.out.println("here"); + mcTimer.set(mc, rpt); + } + } catch(Exception e) { + e.printStackTrace(); + } + } + + public static int getTicks() { + try { + Timer t = getTimer(); + return t.elapsedTicks; + } catch(Exception e) { + e.printStackTrace(); + } + return 0; + } + + public static float getPartialTicks() { + try { + Timer t = getTimer(); + return t.elapsedPartialTicks; + } catch(Exception e) { + e.printStackTrace(); + } + return 0; + } + + public static float getRenderTicks() { + try { + Timer t = getTimer(); + return t.renderPartialTicks; + } catch(Exception e) { + e.printStackTrace(); + } + return 0; + } + + private static Timer getTimer() throws IllegalArgumentException, IllegalAccessException { + return (Timer)mcTimer.get(mc); + } + + public static void advanceTicks(int ticks) { + try { + Timer t = getTimer(); + t.elapsedTicks += ticks; + } catch(Exception e) { + e.printStackTrace(); + } + } + + public static void advancePartialTicks(float ticks) { + try { + Timer t = getTimer(); + t.elapsedPartialTicks += ticks; + } catch(Exception e) { + e.printStackTrace(); + } + } + + public static void advanceRenderPartialTicks(float ticks) { + try { + Timer t = getTimer(); + t.renderPartialTicks += ticks; + } catch(Exception e) { + e.printStackTrace(); + } + } + + public static void setTimerSpeed(float speed) { + try { + Timer t = getTimer(); + t.timerSpeed = speed; + if(timerBefore != null) { + timerBefore.timerSpeed = speed; + } + } catch(Exception e) { + e.printStackTrace(); + } + } + + public static void setRenderPartialTicks(float ticks) { + try { + getTimer().renderPartialTicks = ticks; + } catch(Exception e) { + e.printStackTrace(); + } + } + + public static void setPartialTicks(float ticks) { + try { + getTimer().elapsedPartialTicks = ticks; + } catch(Exception e) { + e.printStackTrace(); + } + } + + public static void setTicks(int ticks) { + try { + getTimer().elapsedTicks = ticks; + } catch(Exception e) { + e.printStackTrace(); + } + } + + public static float getTimerSpeed() { + try { + return getTimer().timerSpeed; + } catch(Exception e) { + e.printStackTrace(); + } + return 1; + } + + public static void updateTimer(double d) { + try{ + Timer t = getTimer(); + double d2 = d; + d2 = MathHelper.clamp_double(d2, 0.0D, 1.0D); + t.elapsedPartialTicks = (float)((double)t.elapsedPartialTicks + d2 * (double)t.timerSpeed * 20); + t.elapsedTicks = (int)t.elapsedPartialTicks; + t.elapsedPartialTicks -= (float)t.elapsedTicks; + + if (t.elapsedTicks > 10) + { + t.elapsedTicks = 10; + } + + t.renderPartialTicks = t.elapsedPartialTicks; + } catch(Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java index 85bc3d1e..77194cda 100644 --- a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayHandler.java @@ -15,6 +15,7 @@ import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.entity.Entity; import net.minecraft.network.EnumPacketDirection; import net.minecraft.network.NetworkManager; +import net.minecraft.network.Packet; import net.minecraft.network.play.INetHandlerPlayClient; import com.mojang.authlib.GameProfile; @@ -27,7 +28,6 @@ import eu.crushedpixel.replaymod.holders.KeyframeComparator; import eu.crushedpixel.replaymod.holders.Position; import eu.crushedpixel.replaymod.holders.PositionKeyframe; import eu.crushedpixel.replaymod.holders.TimeKeyframe; -import eu.crushedpixel.replaymod.registry.ReplayGuiRegistry; public class ReplayHandler { @@ -54,6 +54,15 @@ public class ReplayHandler { private static Entity currentEntity = null; + public static void insertPacketInstantly(Packet p) { + if(replaySender != null) { + try { + replaySender.channelRead(null, p); + } catch (Exception e) { + e.printStackTrace(); + } + } + } public static void spectateEntity(Entity e) { currentEntity = e; mc.setRenderViewEntity(currentEntity); @@ -88,8 +97,8 @@ public class ReplayHandler { isReplaying = replaying; } - public static void startPath() { - ReplayProcess.startReplayProcess(); + public static void startPath(boolean save) { + if(!ReplayHandler.isReplaying()) ReplayProcess.startReplayProcess(save); } public static void interruptReplay() { diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayProcess.java b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayProcess.java index dd9fe554..1e45fafa 100644 --- a/src/main/java/eu/crushedpixel/replaymod/replay/ReplayProcess.java +++ b/src/main/java/eu/crushedpixel/replaymod/replay/ReplayProcess.java @@ -1,5 +1,7 @@ package eu.crushedpixel.replaymod.replay; +import java.io.File; + import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; import eu.crushedpixel.replaymod.ReplayMod; @@ -12,6 +14,8 @@ import eu.crushedpixel.replaymod.holders.TimeKeyframe; import eu.crushedpixel.replaymod.interpolation.LinearPoint; import eu.crushedpixel.replaymod.interpolation.LinearTimestamp; import eu.crushedpixel.replaymod.interpolation.SplinePoint; +import eu.crushedpixel.replaymod.video.ScreenCapture; +import eu.crushedpixel.replaymod.video.VideoWriter; public class ReplayProcess { @@ -31,15 +35,23 @@ public class ReplayProcess { private static LinearTimestamp timeLinear = null; private static double previousReplaySpeed = 0; - + private static boolean calculated = false; - public static void startReplayProcess() { + private static boolean isVideoRecording = false; + + public static boolean isVideoRecording() { + return isVideoRecording; + } + + public static void startReplayProcess(boolean record) { + isVideoRecording = record; lastPosition = null; motionSpline = null; timeLinear = null; calculated = false; - + requestFinish = false; + ChatMessageRequests.initialize(); if(ReplayHandler.getPosKeyframeCount() < 2) { ChatMessageRequests.addChatMessage("At least 2 position keyframes required!", ChatMessageType.WARNING); @@ -64,17 +76,69 @@ public class ReplayProcess { } ChatMessageRequests.addChatMessage("Replay started!", ChatMessageType.INFORMATION); + + if(isVideoRecording()) { + ticks = 0; + MCTimerHandler.setTimerSpeed(1); + MCTimerHandler.setPassiveTimer(); + Thread t = new Thread(new Runnable() { + + @Override + public void run() { + while(ReplayHandler.isReplaying()) { + if(!blocked) { + mc.addScheduledTask(new Runnable() { + @Override + public void run() { + ReplayProcess.tickReplay(); + } + }); + } else { + try { + Thread.sleep(10); + } catch(Exception e) { + e.printStackTrace(); + } + } + } + } + }); + + t.start(); + } } public static void stopReplayProcess(boolean finished) { + if(!ReplayHandler.isReplaying()) return; if(finished) ChatMessageRequests.addChatMessage("Replay finished!", ChatMessageType.INFORMATION); else ChatMessageRequests.addChatMessage("Replay stopped!", ChatMessageType.INFORMATION); ReplayHandler.setReplaying(false); + MCTimerHandler.setActiveTimer(); ReplayHandler.setSpeed(previousReplaySpeed); ReplayHandler.setSpeed(0); } - public static void tickReplay() { + private static boolean blocked = false; + private static boolean deepBlock = false; + + private static float ticks = 0; + + private static boolean requestFinish = false; + + public static void unblock() { + if(!deepBlock) blocked = false; + } + + public static void tickReplay() { + if(isVideoRecording()) { + recordingTick(); + } else { + normalTick(); + } + } + + private static void normalTick() { + if(ReplayHandler.isHurrying()) { lastRealTime = System.currentTimeMillis(); return; @@ -91,7 +155,7 @@ public class ReplayProcess { } } } - + if(linear && motionLinear == null) { //set up linear path motionLinear = new LinearPoint(); @@ -115,9 +179,9 @@ public class ReplayProcess { if(!calculated) { calculated = true; - motionSpline.calcSpline(); + if(motionSpline != null) motionSpline.calcSpline(); } - + long curTime = System.currentTimeMillis(); long timeStep = curTime - lastRealTime; @@ -169,7 +233,165 @@ public class ReplayProcess { if(!(nextTime == null || lastTime == null)) { curSpeed = ((double)((nextTime.getTimestamp()-lastTime.getTimestamp())))/((double)((nextTimeStamp-lastTimeStamp))); } - + + if(lastTimeStamp == nextTimeStamp) { + curSpeed = 0f; + } + } + + int currentDiff = nextPosStamp - lastPosStamp; + int current = curRealReplayTime - lastPosStamp; + + float currentStepPerc = (float)current/(float)currentDiff; //The percentage of the travelled path between the current positions + if(Float.isInfinite(currentStepPerc)) currentStepPerc = 0; + + float splinePos = ((float)ReplayHandler.getKeyframeIndex(lastPos) + currentStepPerc)/(float)(ReplayHandler.getPosKeyframeCount()-1); + float timePos = ((float)ReplayHandler.getKeyframeIndex(lastTime) + currentStepPerc)/(float)(ReplayHandler.getTimeKeyframeCount()-1); + + Position pos = null; + if(!linear) { + pos = motionSpline.getPoint(Math.max(0, Math.min(1, splinePos))); + } else { + pos = motionLinear.getPoint(Math.max(0, Math.min(1, splinePos))); + } + + Integer curPos = null; + if(timeLinear != null) { + curPos = timeLinear.getPoint(Math.max(0, Math.min(1, timePos))); + } + + if(pos != null) ReplayHandler.getCameraEntity().movePath(pos); + + ReplayHandler.setSpeed(curSpeed); + + if(curPos != null) ReplayHandler.setReplayPos(curPos); + + //splinePos = (index of last entry + add) / total entries + + lastRealReplayTime = curRealReplayTime; + lastRealTime = curTime; + + if(requestFinish) { + stopReplayProcess(true); + requestFinish = false; + } + + if(splinePos >= 1) { + requestFinish = true; + } + } + + private static void recordingTick() { + if(ReplayHandler.isHurrying()) { + if(!isVideoRecording()) { + lastRealTime = System.currentTimeMillis(); + } + return; + } + + if(blocked && isVideoRecording()) { + return; + } + deepBlock = true; + blocked = true; + + if(!linear && motionSpline == null) { + //set up spline path + motionSpline = new SplinePoint(); + for(Keyframe kf : ReplayHandler.getKeyframes()) { + if(kf instanceof PositionKeyframe) { + PositionKeyframe pkf = (PositionKeyframe)kf; + Position pos = pkf.getPosition(); + motionSpline.addPoint(pos); + } + } + } + + if(linear && motionLinear == null) { + //set up linear path + motionLinear = new LinearPoint(); + for(Keyframe kf : ReplayHandler.getKeyframes()) { + if(kf instanceof PositionKeyframe) { + PositionKeyframe pkf = (PositionKeyframe)kf; + Position pos = pkf.getPosition(); + motionLinear.addPoint(pos); + } + } + } + if(timeLinear == null) { + timeLinear = new LinearTimestamp(); + for(Keyframe kf : ReplayHandler.getKeyframes()) { + if(kf instanceof TimeKeyframe) { + timeLinear.addPoint(((TimeKeyframe)kf).getTimestamp()); + } + } + + } + + if(!calculated) { + calculated = true; + motionSpline.calcSpline(); + } + + long timeStep; + + long curTime = System.currentTimeMillis(); + + if(isVideoRecording()) { + timeStep = 1000/ReplayMod.replaySettings.getVideoFramerate(); + } else { + timeStep = curTime - lastRealTime; + } + + int curRealReplayTime = (int)(lastRealReplayTime + timeStep); + + PositionKeyframe lastPos = ReplayHandler.getPreviousPositionKeyframe(curRealReplayTime); + PositionKeyframe nextPos = ReplayHandler.getNextPositionKeyframe(curRealReplayTime); + + ReplayHandler.setRealTimelineCursor(curRealReplayTime); + + int lastPosStamp = 0; + int nextPosStamp = 0; + + if(nextPos != null || lastPos != null) { + if(nextPos != null) { + nextPosStamp = nextPos.getRealTimestamp(); + } else { + nextPosStamp = lastPos.getRealTimestamp(); + } + + if(lastPos != null) { + lastPosStamp = lastPos.getRealTimestamp(); + } else { + lastPosStamp = nextPos.getRealTimestamp(); + } + } + + TimeKeyframe lastTime = ReplayHandler.getPreviousTimeKeyframe(curRealReplayTime); + TimeKeyframe nextTime = ReplayHandler.getNextTimeKeyframe(curRealReplayTime); + + int lastTimeStamp = 0; + int nextTimeStamp = 0; + + double curSpeed = 0f; + + if(nextTime != null || lastTime != null) { + if(nextTime != null) { + nextTimeStamp = nextTime.getRealTimestamp(); + } else { + nextTimeStamp = lastTime.getRealTimestamp(); + } + + if(lastTime != null) { + lastTimeStamp = lastTime.getRealTimestamp(); + } else { + lastTimeStamp = nextTime.getRealTimestamp(); + } + + if(!(nextTime == null || lastTime == null)) { + curSpeed = ((double)((nextTime.getTimestamp()-lastTime.getTimestamp())))/((double)((nextTimeStamp-lastTimeStamp))); + } + if(lastTimeStamp == nextTimeStamp) { curSpeed = 0f; } @@ -198,8 +420,10 @@ public class ReplayProcess { if(pos != null) ReplayHandler.getCameraEntity().movePath(pos); - ReplayHandler.setSpeed(curSpeed); - + ReplayHandler.setSpeed((float)curSpeed); + if(isVideoRecording()) { + MCTimerHandler.updateTimer((1f/ReplayMod.replaySettings.getVideoFramerate())); + } if(curPos != null) ReplayHandler.setReplayPos(curPos); //splinePos = (index of last entry + add) / total entries @@ -207,6 +431,28 @@ public class ReplayProcess { lastRealReplayTime = curRealReplayTime; lastRealTime = curTime; - if(splinePos >= 1) stopReplayProcess(true); + //Video capturing, for testing purposes + if(isVideoRecording()) { + try { + if(!VideoWriter.isRecording() && ReplayHandler.isReplaying()) { + VideoWriter.startRecording(mc.displayWidth, mc.displayHeight); + } else { + VideoWriter.writeImage(ScreenCapture.captureScreen()); + } + } catch(Exception e) { + e.printStackTrace(); + } + } + + if(requestFinish) { + stopReplayProcess(true); + VideoWriter.endRecording(); + } + + if(splinePos >= 1) { + requestFinish = true; + } + + deepBlock = false; } } diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/ReplaySender.java b/src/main/java/eu/crushedpixel/replaymod/replay/ReplaySender.java index b9451d51..98880848 100644 --- a/src/main/java/eu/crushedpixel/replaymod/replay/ReplaySender.java +++ b/src/main/java/eu/crushedpixel/replaymod/replay/ReplaySender.java @@ -15,6 +15,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.UUID; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiDownloadTerrain; @@ -27,10 +28,12 @@ import net.minecraft.network.Packet; import net.minecraft.network.PacketBuffer; import net.minecraft.network.play.server.S01PacketJoinGame; import net.minecraft.network.play.server.S02PacketChat; +import net.minecraft.network.play.server.S03PacketTimeUpdate; import net.minecraft.network.play.server.S06PacketUpdateHealth; import net.minecraft.network.play.server.S07PacketRespawn; import net.minecraft.network.play.server.S08PacketPlayerPosLook; import net.minecraft.network.play.server.S0BPacketAnimation; +import net.minecraft.network.play.server.S0CPacketSpawnPlayer; import net.minecraft.network.play.server.S1CPacketEntityMetadata; import net.minecraft.network.play.server.S1DPacketEntityEffect; import net.minecraft.network.play.server.S1FPacketSetExperience; @@ -44,11 +47,12 @@ import net.minecraft.network.play.server.S2FPacketSetSlot; import net.minecraft.network.play.server.S30PacketWindowItems; import net.minecraft.network.play.server.S36PacketSignEditorOpen; import net.minecraft.network.play.server.S37PacketStatistics; +import net.minecraft.network.play.server.S38PacketPlayerListItem; +import net.minecraft.network.play.server.S38PacketPlayerListItem.AddPlayerData; import net.minecraft.network.play.server.S39PacketPlayerAbilities; import net.minecraft.network.play.server.S43PacketCamera; import net.minecraft.network.play.server.S45PacketTitle; import net.minecraft.network.play.server.S48PacketResourcePackSend; -import net.minecraft.util.Timer; import net.minecraft.world.EnumDifficulty; import net.minecraft.world.WorldSettings.GameType; import net.minecraft.world.WorldType; @@ -59,6 +63,7 @@ import org.apache.commons.compress.archivers.zip.ZipFile; import org.apache.commons.io.FilenameUtils; import com.google.gson.Gson; +import com.mojang.authlib.GameProfile; import eu.crushedpixel.replaymod.ReplayMod; import eu.crushedpixel.replaymod.entities.CameraEntity; @@ -106,16 +111,6 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { private Field chatPacketPosition; private Minecraft mc = Minecraft.getMinecraft(); - public static Field mcTimer; - - static { - try { - mcTimer = Minecraft.class.getDeclaredField(MCPNames.field("field_71428_T")); - mcTimer.setAccessible(true); - } catch(Exception e) { - e.printStackTrace(); - } - } private long now = System.currentTimeMillis(); @@ -168,13 +163,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { public void setReplaySpeed(final double d) { if(d != 0) this.replaySpeed = d; - try { - Timer timer = (Timer)mcTimer.get(mc); - timer.timerSpeed = (float)d; - } catch (Exception e) { - e.printStackTrace(); - } - + MCTimerHandler.setTimerSpeed((float)d); } public ReplaySender(final File replayFile, NetworkManager nm) { @@ -246,7 +235,6 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { @Override public void run() { - try { dis = new DataInputStream(archive.getInputStream(replayEntry)); } catch(Exception e) { @@ -267,7 +255,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { lastPacketSent = System.currentTimeMillis(); ReplayHandler.restartReplay(); } - while(!startFromBeginning && (!paused() || FMLClientHandler.instance().isGUIOpen(GuiDownloadTerrain.class))) { + while(!terminate && !startFromBeginning && (!paused() || FMLClientHandler.instance().isGUIOpen(GuiDownloadTerrain.class))) { try { /* @@ -316,9 +304,9 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { if(hurryToTimestamp && currentTimeStamp >= desiredTimeStamp && !startFromBeginning) { hurryToTimestamp = false; if(!ReplayHandler.isReplaying() || hasRestarted) { - ((Timer)mcTimer.get(mc)).elapsedPartialTicks += 5; - ((Timer)mcTimer.get(mc)).elapsedTicks += 5; - ((Timer)mcTimer.get(mc)).renderPartialTicks += 5; + MCTimerHandler.advanceRenderPartialTicks(5); + MCTimerHandler.advancePartialTicks(5); + MCTimerHandler.advanceTicks(5); } if(!ReplayHandler.isReplaying()) { Position pos = ReplayHandler.getLastPosition(); @@ -339,8 +327,11 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { } } catch(EOFException eof) { + dis = new DataInputStream(archive.getInputStream(replayEntry)); setReplaySpeed(0); - } catch(IOException e) {} + } catch(IOException e) { + e.printStackTrace(); + } } } } catch(Exception e) { @@ -369,6 +360,21 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { private boolean allowMovement = false; + private static Field playerUUIDField; + private static Field gameProfileField; + + static { + try { + playerUUIDField = S0CPacketSpawnPlayer.class.getDeclaredField(MCPNames.field("field_179820_b")); + playerUUIDField.setAccessible(true); + + gameProfileField = S38PacketPlayerListItem.AddPlayerData.class.getDeclaredField("field_179964_d"); + gameProfileField.setAccessible(true); + } catch(Exception e) { + e.printStackTrace(); + } + } + @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { @@ -376,6 +382,10 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { return; } + if(ctx == null) { + ctx = this.ctx; + } + if(msg instanceof Packet) { super.channelRead(ctx, msg); return; @@ -396,6 +406,10 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { p instanceof S2APacketParticles) return; } + if(p instanceof S03PacketTimeUpdate) { + p = TimeHandler.getTimePacket((S03PacketTimeUpdate)p); + } + if(p instanceof S02PacketChat) { byte pos = (Byte)chatPacketPosition.get(p); if(pos == 1) { //Ignores command block output sent @@ -408,22 +422,6 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { try { p.readPacketData(pb); - /* - if(p instanceof S0CPacketSpawnPlayer) { - S0CPacketSpawnPlayer sp = (S0CPacketSpawnPlayer)p; - System.out.println("PACKET SPAWN PLAYER ----------"); - System.out.println("ENTITY ID: "+sp.func_148943_d()); - System.out.println("UUID: "+sp.func_179819_c()); - System.out.println("X: "+sp.func_148942_f()); - System.out.println("Y: "+sp.func_148949_g()); - System.out.println("Z: "+sp.func_148946_h()); - System.out.println("YAW: "+sp.func_148941_i()); - System.out.println("PITCH: "+sp.func_148945_j()); - System.out.println("ITEM: "+sp.func_148947_k()); - System.out.println("PACKET END -------------------"); - } - */ - if(p instanceof S1CPacketEntityMetadata) { if((Integer)metadataPacketEntityId.get(p) == actualID) { metadataPacketEntityId.set(p, RecordingHandler.entityID); @@ -444,6 +442,33 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { difficulty, maxPlayers, worldType, false); } + String crPxl = "2cb08a5951f34e98bd0985d9747e80df"; + String johni = "cd3d4be14ffc2f9db432db09e0cd254b"; + + if(p instanceof S38PacketPlayerListItem) { + S38PacketPlayerListItem pp = (S38PacketPlayerListItem)p; + if(((AddPlayerData)pp.func_179767_a().get(0)).func_179962_a().getId().toString().replace("-", "").equals(crPxl)) { + GameProfile johniGP = new GameProfile(UUID.fromString(johni.replaceAll( + "(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", + "$1-$2-$3-$4-$5")), "Johni0702"); + gameProfileField.set(pp.func_179767_a().get(0), johniGP); + //pp.func_179767_a().set(0, johniGP); + p = pp; + } + + } + + if(p instanceof S0CPacketSpawnPlayer) { + S0CPacketSpawnPlayer sp = (S0CPacketSpawnPlayer)p; + + if(sp.func_179819_c().toString().replace("-", "").equals(crPxl)) { + playerUUIDField.set(sp, UUID.fromString(johni.replaceAll( + "(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", + "$1-$2-$3-$4-$5"))); + } + + p = sp; + } if(p instanceof S07PacketRespawn) { allowMovement = true; @@ -491,8 +516,6 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { if(p instanceof S43PacketCamera) { return; } - //if(ReplayHandler.isReplaying()) - // System.out.println("packet arrived"); super.channelRead(ctx, p); } catch(Exception e) { System.out.println(p.getClass()); @@ -520,7 +543,7 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { public boolean paused() { try { - return ((Timer)mcTimer.get(mc)).timerSpeed == 0; + return MCTimerHandler.getTimerSpeed() == 0; } catch(Exception e) {} return true; } @@ -528,7 +551,6 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { public double getReplaySpeed() { if(!paused()) return replaySpeed; else return 0; - //return timeInfo.get().getSpeed(); } public File getReplayFile() { diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/TimeHandler.java b/src/main/java/eu/crushedpixel/replaymod/replay/TimeHandler.java new file mode 100644 index 00000000..437b03f9 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/replay/TimeHandler.java @@ -0,0 +1,28 @@ +package eu.crushedpixel.replaymod.replay; + +import net.minecraft.network.play.server.S03PacketTimeUpdate; + +public class TimeHandler { + + private static long actualDaytime; + private static long desiredDaytime; + + private static boolean timeOverridden = false; + + public static boolean isTimeOverridden() { + return timeOverridden; + } + + public static void setDesiredDaytime(long ddt) { + desiredDaytime = ddt; + } + + public static void setTimeOverridden(boolean overridden) { + timeOverridden = overridden; + } + + public static S03PacketTimeUpdate getTimePacket(S03PacketTimeUpdate packet) { + if(!timeOverridden) return packet; + return new S03PacketTimeUpdate(packet.func_149366_c(), desiredDaytime, true); + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/settings/ReplaySettings.java b/src/main/java/eu/crushedpixel/replaymod/settings/ReplaySettings.java index a09394d4..1a990fed 100644 --- a/src/main/java/eu/crushedpixel/replaymod/settings/ReplaySettings.java +++ b/src/main/java/eu/crushedpixel/replaymod/settings/ReplaySettings.java @@ -14,12 +14,13 @@ import eu.crushedpixel.replaymod.replay.ReplayHandler; public class ReplaySettings { - private int maximumFileSize = 0; private boolean enableRecordingServer = true; private boolean enableRecordingSingleplayer = true; private boolean showNotifications = true; private boolean forceLinearPath = false; private boolean lightingEnabled = false; + private float videoQuality = 0.5f; + private int videoFramerate = 30; private static Field mcTimer; @@ -33,21 +34,29 @@ public class ReplaySettings { } } - public ReplaySettings(int maximumFileSize, boolean enableRecordingServer, - boolean enableRecordingSingleplayer, boolean showNotifications, boolean forceLinearPath, boolean lightingEnabled) { - this.maximumFileSize = maximumFileSize; + public ReplaySettings(boolean enableRecordingServer, + boolean enableRecordingSingleplayer, boolean showNotifications, boolean forceLinearPath, boolean lightingEnabled, int framerate, float videoQuality) { this.enableRecordingServer = enableRecordingServer; this.enableRecordingSingleplayer = enableRecordingSingleplayer; this.showNotifications = showNotifications; this.forceLinearPath = forceLinearPath; this.lightingEnabled = lightingEnabled; + this.videoFramerate = Math.min(120, Math.max(10, framerate)); + this.videoQuality = Math.min(0.9f, Math.max(0.1f, videoQuality)); } - public int getMaximumFileSize() { - return maximumFileSize; + public int getVideoFramerate() { + return videoFramerate; } - public void setMaximumFileSize(int maximumFileSize) { - this.maximumFileSize = maximumFileSize; + public void setVideoFramerate(int framerate) { + this.videoFramerate = Math.min(120, Math.max(10, framerate)); + rewriteSettings(); + } + public float getVideoQuality() { + return videoQuality; + } + public void setVideoQuality(float videoQuality) { + this.videoQuality = Math.min(0.9f, Math.max(0.1f, videoQuality)); rewriteSettings(); } public boolean isEnableRecordingServer() { @@ -108,11 +117,13 @@ public class ReplaySettings { Map map = new LinkedHashMap(); map.put("Enable Notifications", showNotifications); - map.put("Maximum File Size", maximumFileSize); map.put("Record Server", enableRecordingServer); map.put("Record Singleplayer", enableRecordingSingleplayer); + map.put("Placeholder 1", null); map.put("Force Linear Movement", forceLinearPath); map.put("Enable Lighting", lightingEnabled); + map.put("Video Quality", videoQuality); + map.put("Video Framerate", videoFramerate); return map; } @@ -120,12 +131,13 @@ public class ReplaySettings { public void setOptions(Map map) { try { - maximumFileSize = (Integer)map.get("Maximum File Size"); showNotifications = (Boolean)map.get("Enable Notifications"); enableRecordingServer = (Boolean)map.get("Record Server"); enableRecordingSingleplayer = (Boolean)map.get("Record Singleplayer"); forceLinearPath = (Boolean)map.get("Force Linear Movement"); lightingEnabled = (Boolean)map.get("Enable Lighting"); + videoQuality = (Float)map.get("Video Quality"); + videoFramerate = (Integer)map.get("Video Framerate"); rewriteSettings(); } catch(Exception e) { @@ -139,11 +151,12 @@ public class ReplaySettings { ReplayMod.instance.config.removeCategory(ReplayMod.instance.config.getCategory("settings")); Property recServer = ReplayMod.instance.config.get("settings", "enableRecordingServer", enableRecordingServer, "Defines whether a recording should be started upon joining a server."); Property recSP = ReplayMod.instance.config.get("settings", "enableRecordingSingleplayer", enableRecordingSingleplayer, "Defines whether a recording should be started upon joining a singleplayer world."); - Property maxFileSize = ReplayMod.instance.config.get("settings", "maximumFileSize", maximumFileSize, "The maximum File size (in MB) of a recording. 0 means unlimited."); Property showNot = ReplayMod.instance.config.get("settings", "showNotifications", showNotifications, "Defines whether notifications should be sent to the player."); Property linear = ReplayMod.instance.config.get("settings", "forceLinearPath", forceLinearPath, "Defines whether travelling paths should be linear instead of interpolated."); Property lighting = ReplayMod.instance.config.get("settings", "enableLighting", lightingEnabled, "If enabled, the whole map is lighted."); - + Property vq = ReplayMod.instance.config.get("settings", "videoQuality", videoQuality, "The quality of the exported video files from 0.1 to 0.9"); + Property framerate = ReplayMod.instance.config.get("settings", "videoFramerate", videoFramerate, "The framerate of the exported video files from 10 to 120"); + ReplayMod.instance.config.save(); } } diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/screenshot/ReplayScreenshot.java b/src/main/java/eu/crushedpixel/replaymod/video/ReplayScreenshot.java similarity index 72% rename from src/main/java/eu/crushedpixel/replaymod/replay/screenshot/ReplayScreenshot.java rename to src/main/java/eu/crushedpixel/replaymod/video/ReplayScreenshot.java index f8318b67..f3e1e22d 100644 --- a/src/main/java/eu/crushedpixel/replaymod/replay/screenshot/ReplayScreenshot.java +++ b/src/main/java/eu/crushedpixel/replaymod/video/ReplayScreenshot.java @@ -1,4 +1,4 @@ -package eu.crushedpixel.replaymod.replay.screenshot; +package eu.crushedpixel.replaymod.video; import java.awt.Dimension; import java.awt.Rectangle; @@ -33,8 +33,6 @@ import eu.crushedpixel.replaymod.utils.ImageUtils; public class ReplayScreenshot { private static Minecraft mc = Minecraft.getMinecraft(); - private static IntBuffer pixelBuffer; - private static int[] pixelValues; private static final byte[] uniqueBytes = new byte[]{0,1,1,2,3,5,8}; @@ -45,13 +43,20 @@ public class ReplayScreenshot { private static GuiScreen beforeScreen; public static void prepareScreenshot() { + System.out.println("thumbnail preparing"); before = mc.gameSettings.hideGUI; beforeSpeed = ReplayHandler.getSpeed(); beforeScreen = mc.currentScreen; } + + private static boolean locked = false; public static void saveScreenshot(Framebuffer buffer) { + if(locked) return; + locked = true; + + System.out.println("thumbnail started"); try { GuiReplaySaving.replaySaving = true; @@ -61,59 +66,7 @@ public class ReplayScreenshot { mc.entityRenderer.updateCameraAndRender(0); ReplayHandler.setSpeed(0); - int width = mc.displayWidth; - int height = mc.displayHeight; - - if (OpenGlHelper.isFramebufferEnabled()) - { - width = buffer.framebufferTextureWidth; - height = buffer.framebufferTextureHeight; - } - - int k = width * height; - - if (pixelBuffer == null || pixelBuffer.capacity() < k) - { - pixelBuffer = BufferUtils.createIntBuffer(k); - pixelValues = new int[k]; - } - - GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1); - GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); - pixelBuffer.clear(); - - if (OpenGlHelper.isFramebufferEnabled()) { - GlStateManager.bindTexture(buffer.framebufferTexture); - GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer); - } - else { - GL11.glReadPixels(0, 0, width, height, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer); - } - - pixelBuffer.get(pixelValues); - TextureUtil.processPixelValues(pixelValues, width, height); - BufferedImage bufferedimage = null; - - - if (OpenGlHelper.isFramebufferEnabled()) - { - bufferedimage = new BufferedImage(buffer.framebufferWidth, buffer.framebufferHeight, 1); - int l = buffer.framebufferTextureHeight - buffer.framebufferHeight; - - for (int i1 = l; i1 < buffer.framebufferTextureHeight; ++i1) - { - for (int j1 = 0; j1 < buffer.framebufferWidth; ++j1) - { - bufferedimage.setRGB(j1, i1 - l, pixelValues[i1 * buffer.framebufferTextureWidth + j1]); - } - } - } - else { - bufferedimage = new BufferedImage(width, height, 1); - bufferedimage.setRGB(0, 0, width, height, pixelValues, 0, width); - } - - final BufferedImage fbi = bufferedimage; + final BufferedImage fbi = ScreenCapture.captureScreen(); mc.gameSettings.hideGUI = before; mc.currentScreen = beforeScreen; @@ -125,15 +78,13 @@ public class ReplayScreenshot { @Override public void run() { try { - - + System.out.println("thumbnail saving started"); float aspect = 1280f/720f; Rectangle rect; if((float)fbi.getWidth()/(float)fbi.getHeight() <= aspect) { int h = Math.round(fbi.getWidth()/aspect); int y = (fbi.getHeight()/2) - (h/2); - System.out.println(h+" | "+y); rect = new Rectangle(0, y, fbi.getWidth(), h); } else { int w = Math.round(fbi.getHeight()*aspect); @@ -152,7 +103,6 @@ public class ReplayScreenshot { int h = 720; int w = 1280; - //int w = width*(h/height); BufferedImage img = ImageUtils.scaleImage(nbi, new Dimension(w, h)); @@ -207,11 +157,13 @@ public class ReplayScreenshot { zout.close(); tempFile.delete(); temp.delete(); + System.out.println("thumbnail saving finished"); ChatMessageRequests.addChatMessage("Thumbnail has been successfully saved", ChatMessageType.INFORMATION); } catch(Exception e) {} finally { GuiReplaySaving.replaySaving = false; + locked = false; } } }); diff --git a/src/main/java/eu/crushedpixel/replaymod/video/ReplayTimer.java b/src/main/java/eu/crushedpixel/replaymod/video/ReplayTimer.java new file mode 100644 index 00000000..b0e14cba --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/video/ReplayTimer.java @@ -0,0 +1,17 @@ +package eu.crushedpixel.replaymod.video; + +import eu.crushedpixel.replaymod.replay.ReplayProcess; +import net.minecraft.util.Timer; + +public class ReplayTimer extends Timer { + + public ReplayTimer(float p_i1018_1_) { + super(p_i1018_1_); + } + + @Override + public void updateTimer() { + if(ReplayProcess.isVideoRecording()) return; + super.updateTimer(); + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/ScreenCapture.java b/src/main/java/eu/crushedpixel/replaymod/video/ScreenCapture.java new file mode 100644 index 00000000..d23f06e9 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/video/ScreenCapture.java @@ -0,0 +1,85 @@ +package eu.crushedpixel.replaymod.video; + +import java.awt.AWTException; +import java.awt.Rectangle; +import java.awt.Robot; +import java.awt.image.BufferedImage; +import java.nio.IntBuffer; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.texture.TextureUtil; +import net.minecraft.client.shader.Framebuffer; + +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; +import org.monte.screenrecorder.ScreenRecorder; + +public class ScreenCapture { + + private static final Minecraft mc = Minecraft.getMinecraft(); + + private static IntBuffer pixelBuffer; + private static int[] pixelValues; + + public static BufferedImage captureScreen() { + int width = mc.displayWidth; + int height = mc.displayHeight; + + Framebuffer buffer = mc.getFramebuffer(); + + if (OpenGlHelper.isFramebufferEnabled()) + { + width = buffer.framebufferTextureWidth; + height = buffer.framebufferTextureHeight; + } + + int k = width * height; + + if (pixelBuffer == null || pixelBuffer.capacity() < k) + { + pixelBuffer = BufferUtils.createIntBuffer(k); + pixelValues = new int[k]; + } + + GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1); + GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1); + pixelBuffer.clear(); + + if (OpenGlHelper.isFramebufferEnabled()) { + GlStateManager.bindTexture(buffer.framebufferTexture); + GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer); + } + else { + GL11.glReadPixels(0, 0, width, height, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer); + } + + pixelBuffer.get(pixelValues); + TextureUtil.processPixelValues(pixelValues, width, height); + BufferedImage bufferedimage = null; + + + if (OpenGlHelper.isFramebufferEnabled()) + { + bufferedimage = new BufferedImage(buffer.framebufferWidth, buffer.framebufferHeight, 1); + int l = buffer.framebufferTextureHeight - buffer.framebufferHeight; + + for (int i1 = l; i1 < buffer.framebufferTextureHeight; ++i1) + { + for (int j1 = 0; j1 < buffer.framebufferWidth; ++j1) + { + bufferedimage.setRGB(j1, i1 - l, pixelValues[i1 * buffer.framebufferTextureWidth + j1]); + } + } + } + else { + bufferedimage = new BufferedImage(width, height, 1); + bufferedimage.setRGB(0, 0, width, height, pixelValues, 0, width); + } + + + return bufferedimage; + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/video/VideoWriter.java b/src/main/java/eu/crushedpixel/replaymod/video/VideoWriter.java new file mode 100644 index 00000000..bfc72e12 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/video/VideoWriter.java @@ -0,0 +1,98 @@ +package eu.crushedpixel.replaymod.video; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Calendar; + +import org.monte.media.Buffer; +import org.monte.media.Format; +import org.monte.media.FormatKeys; +import org.monte.media.FormatKeys.MediaType; +import org.monte.media.MovieWriter; +import org.monte.media.Registry; +import org.monte.media.VideoFormatKeys; +import org.monte.media.math.Rational; + +import eu.crushedpixel.replaymod.ReplayMod; + +public class VideoWriter { + + private static final String DATE_FORMAT = "yyyy_MM_dd_HH_mm_ss"; + private static final SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); + + private static final String VIDEO_EXTENSION = ".avi"; + + private static MovieWriter out; + private static boolean isRecording = false; + private static boolean requestFinish = false; + + private static Buffer buf; + private static int track; + + public static boolean isRecording() { + return isRecording; + } + + public static void startRecording(int width, int height) { + if(isRecording) { + IllegalStateException up = new IllegalStateException("VideoWriter is already recording!"); + throw up; //lolololo + } + isRecording = true; + + try { + File folder = new File("./replay_videos/"); + folder.mkdirs(); + + String fileName = sdf.format(Calendar.getInstance().getTime()); + + File file = new File(folder, fileName+VIDEO_EXTENSION); + file.createNewFile(); + + out = Registry.getInstance().getWriter(file); + Format format = new Format(FormatKeys.MediaTypeKey, MediaType.VIDEO, + FormatKeys.EncodingKey, VideoFormatKeys.ENCODING_AVI_MJPG, + FormatKeys.FrameRateKey, new Rational(ReplayMod.replaySettings.getVideoFramerate(), 1), + VideoFormatKeys.WidthKey, width, + VideoFormatKeys.HeightKey, height, + VideoFormatKeys.DepthKey, 24, + VideoFormatKeys.QualityKey, ReplayMod.replaySettings.getVideoQuality()); + + + track = out.addTrack(format); + + buf = new Buffer(); + buf.format = new Format(VideoFormatKeys.DataClassKey, BufferedImage.class); + buf.sampleDuration = out.getFormat(track).get(VideoFormatKeys.FrameRateKey).inverse(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void writeImage(BufferedImage image) { + if(requestFinish || !isRecording) { + IllegalStateException up = new IllegalStateException( + "The VideoWriter is currently not available. Please try again later."); + throw up; //lolololo^2 + } + + try { + buf.data = image; + out.write(track, buf); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void endRecording() { + if(!isRecording) return; + isRecording = false; + try { + out.close(); + } catch(Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/resources/assets/replaymod/steve.png b/src/main/resources/assets/replaymod/steve.png deleted file mode 100644 index e407f801..00000000 Binary files a/src/main/resources/assets/replaymod/steve.png and /dev/null differ diff --git a/src/main/resources/assets/replaymod/timeline_icons.png b/src/main/resources/assets/replaymod/timeline_icons.png index a71e9355..a3127b15 100644 Binary files a/src/main/resources/assets/replaymod/timeline_icons.png and b/src/main/resources/assets/replaymod/timeline_icons.png differ