From 9f3a97f65c66152d987b88e5937894e9cd6b36a8 Mon Sep 17 00:00:00 2001 From: johni0702 Date: Tue, 26 May 2015 01:07:30 +0200 Subject: [PATCH] Cleanup, relocate and split up GuiReplayOverlay Merge gui resources into one texture --- .../eu/crushedpixel/replaymod/ReplayMod.java | 5 +- .../replaymod/events/GuiEventHandler.java | 1 + .../replaymod/events/GuiReplayOverlay.java | 830 ------------------ .../replaymod/events/KeyInputHandler.java | 2 +- .../replaymod/gui/GuiKeyframeRepository.java | 3 +- .../replaymod/gui/GuiMouseInput.java | 22 + .../replaymod/gui/GuiPlayerOverview.java | 5 +- .../replaymod/gui/GuiReplaySpeedSlider.java | 7 +- .../gui/elements/GuiKeyframeTimeline.java | 125 +++ .../replaymod/gui/elements/GuiScrollbar.java | 142 +++ .../gui/elements/GuiTexturedButton.java | 38 + .../replaymod/gui/elements/GuiTimeline.java | 194 ++++ .../gui/overlay/GuiRecordingOverlay.java | 37 + .../gui/overlay/GuiReplayOverlay.java | 411 +++++++++ .../gui/replayviewer/GuiReplayViewer.java | 1 + .../recording/ConnectionEventHandler.java | 7 + .../replaymod/replay/ReplaySender.java | 14 + .../replaymod/utils/MouseUtils.java | 3 +- .../assets/replaymod/extended_gui.png | Bin 1972 -> 0 bytes .../resources/assets/replaymod/replay_gui.png | Bin 2995 -> 6258 bytes .../assets/replaymod/timeline_icons.png | Bin 2264 -> 0 bytes 21 files changed, 1002 insertions(+), 845 deletions(-) delete mode 100755 src/main/java/eu/crushedpixel/replaymod/events/GuiReplayOverlay.java create mode 100644 src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiKeyframeTimeline.java create mode 100644 src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiScrollbar.java create mode 100644 src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiTexturedButton.java create mode 100644 src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiTimeline.java create mode 100644 src/main/java/eu/crushedpixel/replaymod/gui/overlay/GuiRecordingOverlay.java create mode 100755 src/main/java/eu/crushedpixel/replaymod/gui/overlay/GuiReplayOverlay.java delete mode 100755 src/main/resources/assets/replaymod/extended_gui.png delete mode 100755 src/main/resources/assets/replaymod/timeline_icons.png diff --git a/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java b/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java index 9528ff21..ac225995 100755 --- a/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java +++ b/src/main/java/eu/crushedpixel/replaymod/ReplayMod.java @@ -4,6 +4,7 @@ import com.google.common.util.concurrent.ListenableFutureTask; import eu.crushedpixel.replaymod.api.ApiClient; import eu.crushedpixel.replaymod.chat.ChatMessageHandler; import eu.crushedpixel.replaymod.events.*; +import eu.crushedpixel.replaymod.gui.overlay.GuiReplayOverlay; import eu.crushedpixel.replaymod.holders.KeyframeSet; import eu.crushedpixel.replaymod.localization.LocalizedResourcePack; import eu.crushedpixel.replaymod.online.authentication.AuthenticationHandler; @@ -130,9 +131,7 @@ public class ReplayMod { public void postInit(FMLPostInitializationEvent event) throws IOException { GameSettings.Options.RENDER_DISTANCE.setValueMax(64f); - overlay = new GuiReplayOverlay(); - FMLCommonHandler.instance().bus().register(overlay); - MinecraftForge.EVENT_BUS.register(overlay); + overlay.register(); TickAndRenderListener tarl = new TickAndRenderListener(); FMLCommonHandler.instance().bus().register(tarl); diff --git a/src/main/java/eu/crushedpixel/replaymod/events/GuiEventHandler.java b/src/main/java/eu/crushedpixel/replaymod/events/GuiEventHandler.java index cff870b2..3b618302 100755 --- a/src/main/java/eu/crushedpixel/replaymod/events/GuiEventHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/events/GuiEventHandler.java @@ -27,6 +27,7 @@ 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.SubscribeEvent; +import org.lwjgl.util.Point; import java.awt.*; import java.util.ArrayList; diff --git a/src/main/java/eu/crushedpixel/replaymod/events/GuiReplayOverlay.java b/src/main/java/eu/crushedpixel/replaymod/events/GuiReplayOverlay.java deleted file mode 100755 index 955f7d52..00000000 --- a/src/main/java/eu/crushedpixel/replaymod/events/GuiReplayOverlay.java +++ /dev/null @@ -1,830 +0,0 @@ -package eu.crushedpixel.replaymod.events; - -import eu.crushedpixel.replaymod.ReplayMod; -import eu.crushedpixel.replaymod.entities.CameraEntity; -import eu.crushedpixel.replaymod.gui.*; -import eu.crushedpixel.replaymod.holders.Keyframe; -import eu.crushedpixel.replaymod.holders.Position; -import eu.crushedpixel.replaymod.holders.PositionKeyframe; -import eu.crushedpixel.replaymod.holders.TimeKeyframe; -import eu.crushedpixel.replaymod.recording.ConnectionEventHandler; -import eu.crushedpixel.replaymod.registry.ReplayGuiRegistry; -import eu.crushedpixel.replaymod.replay.ReplayHandler; -import eu.crushedpixel.replaymod.replay.ReplayProcess; -import eu.crushedpixel.replaymod.utils.MouseUtils; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Gui; -import net.minecraft.client.gui.GuiChat; -import net.minecraft.client.gui.GuiScreen; -import net.minecraft.client.gui.ScaledResolution; -import net.minecraft.client.gui.inventory.GuiInventory; -import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.resources.I18n; -import net.minecraft.entity.Entity; -import net.minecraft.util.ResourceLocation; -import net.minecraftforge.client.event.RenderGameOverlayEvent; -import net.minecraftforge.fml.client.FMLClientHandler; -import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; -import org.lwjgl.input.Mouse; -import org.lwjgl.opengl.Display; - -import java.awt.Color; -import java.awt.*; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import static net.minecraft.client.renderer.GlStateManager.*; -import static org.lwjgl.opengl.GL11.*; - -public class GuiReplayOverlay extends Gui { - - private final Minecraft mc = Minecraft.getMinecraft(); - int sl_begin_x = 0; - int sl_end_x = 63; - int sl_y = 40; - int plus_x = 0; - int plus_y = 0; - int minus_x = 0; - int minus_y = 9; - int slider_begin_x = 1; - int slider_begin_width = 1; - int slider_end_x = 62; - int slider_end_width = 1; - int slider_y = 50; - int slider_height = 7; - private int sliderX = 35; - private int sliderY = 10; - private int timelineX = sliderX + 100 + 5; - private int realTimelineX = 10 + 4 * 25; - private int realTimelineY = 33 + 10; - private int ppButtonX = 10; - private int ppButtonY = 10; - private int r_ppButtonX = 10; - private int r_ppButtonY = realTimelineY + 1; - private int exportButtonX = 35; - private int exportButtonY = realTimelineY + 1; - private int place_ButtonX = 60; - private int place_ButtonY = realTimelineY + 1; - private int time_ButtonX = 85; - private int time_ButtonY = realTimelineY + 1; - private ResourceLocation replay_gui = new ResourceLocation("replaymod", "replay_gui.png"); - private ResourceLocation extended_gui = new ResourceLocation("replaymod", "extended_gui.png"); - private ResourceLocation timeline_icons = new ResourceLocation("replaymod", "timeline_icons.png"); - private GuiReplaySpeedSlider speedSlider; - private boolean mouseDown = false; - private int tl_begin_x = 0; - private int tl_begin_width = 4; - private int tl_end_x = 60; - private int tl_end_width = 4; - private int tl_middle_x = 4; - private int tl_y = 40; - private float zoom_scale = 0.1f; //can see 1/10th of the timeline - private float pos_left = 0f; //left border of timeline is at 0% - private long timelineLength = 10 * 60 * 1000; //10 min of timeline - private float zoom_steps = 0.05f; - private boolean wasSliding = false; - private boolean mouseDwn = false; - - public void resetUI(boolean slider) throws Exception { - if(FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { - mc.displayGuiScreen(null); - } - ReplayHandler.setRealTimelineCursor(0); - if(slider) - speedSlider = new GuiReplaySpeedSlider(1, sliderX, sliderY, I18n.format("replaymod.gui.speed")); - } - - @SubscribeEvent - public void onRenderGui(RenderGameOverlayEvent event) { - if(ReplayProcess.isVideoRecording() && ReplayHandler.isInPath() && !(mc.currentScreen instanceof GuiCancelRender)) { - if(event.isCancelable()) event.setCanceled(true); - } - } - - - @SubscribeEvent - public void onRenderTabList(RenderGameOverlayEvent.Pre event) { //cancelling tab list rendering and rendering help instead - if(ReplayHandler.isInReplay() && event.type == RenderGameOverlayEvent.ElementType.PLAYER_LIST) { - event.setCanceled(true); - } - } - - @SubscribeEvent - public void renderRecordingIndicator(RenderGameOverlayEvent.Text event) { - if(!ReplayHandler.isInReplay() && ReplayMod.replaySettings.showRecordingIndicator() && ConnectionEventHandler.isRecording()) { - this.drawString(mc.fontRendererObj, I18n.format("replaymod.gui.recording").toUpperCase(), 30, 18 - (mc.fontRendererObj.FONT_HEIGHT / 2), Color.WHITE.getRGB()); - mc.renderEngine.bindTexture(replay_gui); - GlStateManager.resetColor(); - GlStateManager.enableAlpha(); - this.drawModalRectWithCustomSizedTexture(10, 10, 40, 21, 16, 16, 64, 64); - } - } - - @SubscribeEvent - public void onRenderGui(RenderGameOverlayEvent.Post event) throws IllegalArgumentException, IllegalAccessException { - if(!ReplayHandler.isInReplay() || FMLClientHandler.instance().isGUIOpen(GuiPlayerOverview.class) - || FMLClientHandler.instance().isGUIOpen(GuiKeyframeRepository.class)) { - return; - } - - if(!ReplayGuiRegistry.hidden && ReplayHandler.isCamera()) ReplayGuiRegistry.hide(); - else if(!ReplayHandler.isCamera() && ReplayGuiRegistry.hidden) ReplayGuiRegistry.show(); - - if(FMLClientHandler.instance().isGUIOpen(GuiChat.class) || FMLClientHandler.instance().isGUIOpen(GuiInventory.class)) { - mc.displayGuiScreen(new GuiMouseInput()); - } - - glEnable(GL_BLEND); - - Point mousePoint = MouseUtils.getMousePos(); - final int mouseX = (int) mousePoint.getX(); - final int mouseY = (int) mousePoint.getY(); - - Point scaled = MouseUtils.getScaledDimensions(); - final int width = (int) scaled.getX(); - final int height = (int) scaled.getY(); - - //Draw Timeline - drawTimeline(timelineX, width - 14, 9); - drawRealTimeline(realTimelineX, width - 14 - 11, realTimelineY, mouseX, mouseY); - - //Play/Pause button - int x = 0; - int y = 0; - - boolean play = !ReplayMod.replaySender.paused(); - boolean hover = false; - - if(FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { - if(mouseX >= ppButtonX && mouseX <= ppButtonX + 20 - && mouseY >= ppButtonY && mouseY <= ppButtonY + 20) { - hover = true; - } - } - - if(play) { - y = 20; - } - if(hover) { - x = 20; - } - - mc.renderEngine.bindTexture(replay_gui); - - GlStateManager.resetColor(); - this.drawModalRectWithCustomSizedTexture(ppButtonX, ppButtonY, x, y, 20, 20, 64, 64); - - //When hurrying, no Timeline jumping etc. is possible - if(Mouse.isButtonDown(0) && FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { //clicking the Button - if(!ReplayMod.replaySender.isHurrying()) { - speedSlider.mousePressed(mc, mouseX, mouseY); - if(!mouseDown) { - mouseDown = true; - if(hover) { - playOrPause(); - } else if(mouseX >= exportButtonX && mouseX <= exportButtonX + 20 && mouseY >= exportButtonY && exportButtonY <= exportButtonY + 20) { - mc.displayGuiScreen(new GuiRenderSettings()); - } - - if(mouseX >= timelineX + 4 && mouseX <= width - 18 && mouseY >= 11 && mouseY <= 29) { - double tot = (width - 18) - (timelineX + 4); - double perc = (mouseX - (timelineX + 4)) / tot; - final double time = perc * (double) ReplayMod.replaySender.replayLength(); - - if(time < ReplayMod.replaySender.currentTimeStamp()) { - mc.displayGuiScreen(null); - } - - CameraEntity cam = ReplayHandler.getCameraEntity(); - if(cam != null) { - ReplayHandler.setLastPosition(new Position(cam.posX, cam.posY, cam.posZ, cam.rotationPitch, cam.rotationYaw)); - } else { - ReplayHandler.setLastPosition(null); - } - - long diff = (long) time - ReplayMod.replaySender.getDesiredTimestamp(); - if(diff != 0) { - if (diff > 0 && diff < 5000) { // Small difference and no time travel - ReplayMod.replaySender.jumpToTime((int) time); - } else { // We either have to restart the replay or send a significant amount of packets - // Render our please-wait-screen - GuiScreen guiScreen = new GuiScreen() { - @Override - public void drawScreen(int mouseX, int mouseY, float partialTicks) { - drawBackground(0); - drawCenteredString(fontRendererObj, I18n.format("replaymod.gui.pleasewait"), - width / 2, height / 2, 0xffffffff); - } - }; - - // Make sure that the replaysender changes into sync mode - ReplayMod.replaySender.setAsyncMode(false); - synchronized (ReplayMod.replaySender) { - // This will make sure that the async thread has stopped - } - - // Perform the rendering using OpenGL - pushMatrix(); - clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - enableTexture2D(); - mc.getFramebuffer().bindFramebuffer(true); - mc.entityRenderer.setupOverlayRendering(); - - guiScreen.setWorldAndResolution(mc, width, height); - guiScreen.drawScreen(0, 0, 0); - - mc.getFramebuffer().unbindFramebuffer(); - popMatrix(); - pushMatrix(); - mc.getFramebuffer().framebufferRender(mc.displayWidth, mc.displayHeight); - popMatrix(); - - Display.update(); - - // Send the packets - ReplayMod.replaySender.sendPacketsTill((int) time); - ReplayMod.replaySender.setAsyncMode(true); - ReplayMod.replaySender.setReplaySpeed(0); - - // Tick twice to process all packets and position interpolation - try { - mc.runTick(); - mc.runTick(); - } catch (IOException e) { - e.printStackTrace(); // This should never be thrown but whatever - } - - // No need to remove our please-wait-screen. It'll vanish with the next - // render pass as it's never been a real GuiScreen in the first place. - } - } - } - } - } - - } else { - try { - speedSlider.mouseReleased(mouseX, mouseY); - mouseDown = false; - } catch(Exception e) { - } - } - - 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(timeline_icons); - - GlStateManager.resetColor(); - this.drawModalRectWithCustomSizedTexture(exportButtonX, exportButtonY, x, y, 20, 20, 64, 64); - - //GlStateManager.resetColor(); - - //Place Keyframe Button - hover = false; - x = 0; - y = 0; - - if(FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { - if(mouseX >= place_ButtonX && mouseX <= place_ButtonX + 20 - && mouseY >= place_ButtonY && mouseY <= place_ButtonY + 20) { - hover = true; - } - } - - if(hover) { - x = 20; - } - - if(ReplayHandler.getSelectedKeyframe() != null && ReplayHandler.getSelectedKeyframe() instanceof PositionKeyframe) { - y += 20; - } - - mc.renderEngine.bindTexture(extended_gui); - - if(hover && Mouse.isButtonDown(0) && isClick() && FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class) - && !ReplayHandler.isInPath()) { - if(ReplayHandler.getSelectedKeyframe() == null || !(ReplayHandler.getSelectedKeyframe() instanceof PositionKeyframe)) { - addPlaceKeyframe(); - } else { - ReplayHandler.removeKeyframe(ReplayHandler.getSelectedKeyframe()); - } - } - - GlStateManager.resetColor(); - this.drawModalRectWithCustomSizedTexture(place_ButtonX, place_ButtonY, x, y, 20, 20, 64, 64); - - - //Time Keyframe Button - hover = false; - x = 0; - y = 40; - - boolean timeSelected = false; - if(ReplayHandler.getSelectedKeyframe() != null && ReplayHandler.getSelectedKeyframe() instanceof TimeKeyframe) { - timeSelected = true; - x = 40; - y = 0; - } - - if(FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { - if(mouseX >= time_ButtonX && mouseX <= time_ButtonX + 20 - && mouseY >= time_ButtonY && mouseY <= time_ButtonY + 20) { - hover = true; - } - } - - if(hover) { - if(timeSelected) { - y = 20; - } else { - x = 20; - } - } - - mc.renderEngine.bindTexture(extended_gui); - - if(hover && Mouse.isButtonDown(0) && isClick() && FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { - if(ReplayHandler.getSelectedKeyframe() == null || !(ReplayHandler.getSelectedKeyframe() instanceof TimeKeyframe) && !ReplayHandler.isInPath()) { - addTimeKeyframe(); - } else { - ReplayHandler.removeKeyframe(ReplayHandler.getSelectedKeyframe()); - } - } - - GlStateManager.resetColor(); - this.drawModalRectWithCustomSizedTexture(time_ButtonX, time_ButtonY, x, y, 20, 20, 64, 64); - - if(mouseX >= (timelineX + 4) && mouseX <= width - 18 && mouseY >= 11 && mouseY <= 29 && FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { - double tot = (width - 18) - (timelineX + 4); - double perc = (mouseX - (timelineX + 4)) / tot; - long time = Math.round(perc * (double) ReplayMod.replaySender.replayLength()); - - String timestamp = (String.format("%02d:%02ds", - TimeUnit.MILLISECONDS.toMinutes(time), - TimeUnit.MILLISECONDS.toSeconds(time) - - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(time)) - )); - - this.drawCenteredString(mc.fontRendererObj, timestamp, mouseX, mouseY + 5, Color.WHITE.getRGB()); - } - - if(mc.inGameHasFocus) { - Mouse.setCursorPosition(width / 2, height / 2); - } - - try { - speedSlider.drawButton(mc, mouseX, mouseY); - } catch(Exception e) { - } - - GlStateManager.resetColor(); - - Entity player = ReplayHandler.getCameraEntity(); - if(player != null) { - player.setVelocity(0, 0, 0); - } - - if(!Mouse.isButtonDown(0)) isClick(); - } - - private void drawTimeline(int minX, int maxX, int y) { - int zero = minX + tl_begin_width; - int full = maxX - tl_end_width; - - GlStateManager.resetColor(); - mc.renderEngine.bindTexture(replay_gui); - this.drawModalRectWithCustomSizedTexture(minX, y, tl_begin_x, tl_y, tl_begin_width, 22, 64, 64); - - for(int i = minX + tl_begin_width; i < maxX - tl_end_width; i += tl_end_x - tl_begin_width) { - this.drawModalRectWithCustomSizedTexture(i, y, tl_begin_x + tl_begin_width - , tl_y, Math.min(tl_end_x - tl_begin_width, maxX - tl_end_width - i) - , 22, 64, 64); - } - - this.drawModalRectWithCustomSizedTexture(maxX - tl_end_width, y, tl_end_x, tl_y, tl_end_width, 22, 64, 64); - - //Cursor - double width = full - zero; - double perc = (double) ReplayMod.replaySender.currentTimeStamp() / (double) ReplayMod.replaySender.replayLength(); - - int cursorX = (int) Math.round(zero + (perc * width)); - this.drawModalRectWithCustomSizedTexture(cursorX - 3, y + 3, 44, 0, 8, 16, 64, 64); - } - - private long keyframeSelectionTime = 0; - - private boolean liftedSinceSelection = false; - private int selectionTimestamp = 0; - private boolean overcameToleranceSinceSelection = false; - - private void drawRealTimeline(int minX, int maxX, int y, int mouseX, int mouseY) { - int zero = minX + tl_begin_width; - int full = maxX - tl_end_width; - - //the real timeline - GlStateManager.resetColor(); - mc.renderEngine.bindTexture(replay_gui); - this.drawModalRectWithCustomSizedTexture(minX, y, tl_begin_x, tl_y, tl_begin_width, 22, 64, 64); - - for(int i = minX + tl_begin_width; i < maxX - tl_end_width; i += tl_end_x - tl_begin_width) { - this.drawModalRectWithCustomSizedTexture(i, y, tl_begin_x + tl_begin_width - , tl_y, Math.min(tl_end_x - tl_begin_width, maxX - tl_end_width - i) - , 22, 64, 64); - } - - this.drawModalRectWithCustomSizedTexture(maxX - tl_end_width, y, tl_end_x, tl_y, tl_end_width, 22, 64, 64); - - //Time Slider - int yo = y + 22 + 1; - GlStateManager.resetColor(); - mc.renderEngine.bindTexture(timeline_icons); - this.drawModalRectWithCustomSizedTexture(minX, yo, sl_begin_x, sl_y, 2, 9, 64, 64); - - for(int i = minX + 2; i < maxX - 1; i += sl_end_x - 2) { - this.drawModalRectWithCustomSizedTexture(i, yo, 2, sl_y, - Math.min(sl_end_x - 2, maxX - 1 - i), 9, 64, 64); - } - - this.drawModalRectWithCustomSizedTexture(maxX - 1, yo, sl_end_x, sl_y, 1, 9, 64, 64); - - //Timeline Pos Slider - int sl_y = yo + 1; - int minPos = minX + 1; - int maxPos = maxX - 2; - int tlWidth = maxPos - minPos; - - int slider_min = minPos + Math.round(pos_left * tlWidth); - int slider_width = Math.round(zoom_scale * tlWidth); - - int sl_max = slider_min + slider_width; - - this.drawModalRectWithCustomSizedTexture(slider_min, sl_y, slider_begin_x, slider_y, slider_begin_width, slider_height, 64, 64); - - for(int i = slider_min + slider_begin_width; i < sl_max - slider_end_width; i += slider_end_x - slider_begin_width - slider_begin_x) { - this.drawModalRectWithCustomSizedTexture(i, sl_y, slider_begin_x + slider_begin_width, slider_y, - Math.min(slider_end_x - slider_end_width - slider_begin_x, sl_max - slider_end_width - i), slider_height, 64, 64); - } - - this.drawModalRectWithCustomSizedTexture(sl_max - slider_end_width, sl_y, slider_end_x, slider_y, - slider_end_width, slider_height, 64, 64); - - //Slider dragging - if(Mouse.isButtonDown(0) && FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class) && (mouseX >= slider_min && mouseX <= sl_max && mouseY >= sl_y && mouseY <= sl_y + slider_height || wasSliding)) { - wasSliding = true; - float dx = ((float) Mouse.getDX() * (float) new ScaledResolution(mc, mc.displayWidth, mc.displayHeight).getScaledWidth() / mc.displayWidth); - this.pos_left = Math.min(1f - this.zoom_scale, Math.max(0f, this.pos_left + (dx / (float) tlWidth))); - } - - if(!Mouse.isButtonDown(0)) { - wasSliding = false; - } - - //Timeline Buttons - //+- Buttons - boolean hover = false; - int px = plus_x; - if(FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { - if(mouseX >= maxX + 2 && mouseX <= maxX + 2 + 9 - && mouseY >= y + 1 && mouseY <= y + 1 + 9) { - hover = true; - } - } - - if(hover) { - px += 9; - } - - this.drawModalRectWithCustomSizedTexture(maxX + 2, y + 1, px, plus_y, 9, 9, 64, 64); - - if(hover && Mouse.isButtonDown(0) && FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { - zoomIn(); - } - - hover = false; - int mx = minus_x; - - if(FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { - if(mouseX >= maxX + 2 && mouseX <= maxX + 2 + 9 - && mouseY >= y + 9 + 3 && mouseY <= y + 9 + 3 + 9) { - hover = true; - } - } - - if(hover) { - mx += 9; - } - - this.drawModalRectWithCustomSizedTexture(maxX + 2, y + 9 + 3, mx, minus_y, 9, 9, 64, 64); - - if(hover && Mouse.isButtonDown(0) && FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { - zoomOut(); - } - - //show Time String - if(mouseX >= zero && mouseX <= full && mouseY >= y && mouseY <= y + 22 && FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { - long tot = Math.round((double) timelineLength * zoom_scale); - double perc = (mouseX - (realTimelineX + 4)) / (double) (full - zero); - - long time = Math.round(this.pos_left * (double) timelineLength) + Math.round(perc * (double) tot); - - String timestamp = (String.format("%02d:%02ds", - TimeUnit.MILLISECONDS.toMinutes(time), - TimeUnit.MILLISECONDS.toSeconds(time) - - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(time)) - )); - this.drawCenteredString(mc.fontRendererObj, timestamp, mouseX, mouseY + 5, Color.WHITE.getRGB()); - } - - //draw Markers on timeline - MarkerType mt = MarkerType.getMarkerType(zoom_scale, timelineLength); - - //every x seconds, draw small marker - long left_real = Math.round(pos_left * (double) timelineLength); - long right_real = left_real + (Math.round(zoom_scale * timelineLength)); - long tot = Math.round((double) timelineLength * zoom_scale); - - for(int s = 0; s <= timelineLength; s += mt.getSmallDistance()) { - if(s > right_real) break; - if(s >= left_real) { - //calculate absolute position on screen - long relative = (s) - (left_real); - double perc = ((double) relative / (double) tot); - - long real_width = full - zero; - long rel_x = Math.round(perc * (double) real_width); - - long real_x = zero + rel_x; - - this.drawVerticalLine((int) real_x, y + 19 - 3, y + 19, Color.WHITE.getRGB()); - } - } - - //every x seconds, draw big marker - for(int s = 0; s <= timelineLength; s += mt.getDistance()) { - if(s > right_real) break; - if(s >= left_real) { - //calculate absolute position on screen - long relative = s - (left_real); - double perc = ((double) relative / (double) tot); - - long real_width = full - zero; - long rel_x = Math.round(perc * (double) real_width); - - long real_x = zero + rel_x; - - this.drawVerticalLine((int) real_x, y + 19 - 7, y + 19, Color.LIGHT_GRAY.getRGB()); - - //write text - int time = s; - String timestamp = (String.format("%02d:%02d", - TimeUnit.MILLISECONDS.toMinutes(time), - TimeUnit.MILLISECONDS.toSeconds(time) - - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(time)) - )); - - this.drawCenteredString(mc.fontRendererObj, timestamp, (int) real_x, y - 8, Color.WHITE.getRGB()); - } - } - - //handle Mouse clicks on realTimeLine - if(Mouse.isButtonDown(0) && FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class) && !wasSliding && mouseX >= minX + tl_begin_width && mouseX <= maxX - tl_end_width && - mouseY >= y && mouseY <= y + 22 && !ReplayHandler.isInPath()) { - - //calculate real time and set cursor accordingly - int width = (maxX - tl_end_width) - (minX + tl_begin_width); - int rel_x = mouseX - (minX + tl_begin_width); - - float rel_pos = (float) rel_x / (float) width; - - float abs_width = (zoom_scale * (float) timelineLength); - int real_pos = (int)Math.floor(left_real + ((rel_pos) * abs_width)); - - if(liftedSinceSelection) { - ReplayHandler.setRealTimelineCursor(real_pos); - } else if(ReplayHandler.getSelectedKeyframe() != null) { - int tolerance = 2 * Math.round(abs_width / (float) width); - - if(Math.abs(selectionTimestamp - real_pos) > tolerance || overcameToleranceSinceSelection) { - ReplayHandler.setRealTimelineCursor(real_pos); - ReplayHandler.getSelectedKeyframe().setRealTimestamp(real_pos); - ReplayHandler.sortKeyframes(); - overcameToleranceSinceSelection = true; - } - } - - //Keyframe click handling here - if(isClick()) { - //tolerance is 2 pixels multiplied with the timespan of one pixel - int tolerance = 2 * Math.round(abs_width / (float) width); - - Keyframe close; - if(mouseY >= y + 9) { - close = ReplayHandler.getClosestTimeKeyframeForRealTime(ReplayHandler.getRealTimelineCursor(), tolerance); - ReplayHandler.selectKeyframe(close); //can be null, deselects keyframe - } else { - close = ReplayHandler.getClosestPlaceKeyframeForRealTime(ReplayHandler.getRealTimelineCursor(), tolerance); - ReplayHandler.selectKeyframe(close); //can be null, deselects keyframe - } - - if(close != null) { - liftedSinceSelection = false; - overcameToleranceSinceSelection = false; - selectionTimestamp = close.getRealTimestamp(); - float perc = (selectionTimestamp - left_real) / abs_width; - int newX = Math.round(minX + tl_begin_width + (perc * width)); - MouseUtils.moveMouse(newX, mouseY); - ReplayHandler.setRealTimelineCursor(selectionTimestamp); - - long cur = System.currentTimeMillis(); - if(cur - keyframeSelectionTime < 500) { //if double click on Keyframe - mc.displayGuiScreen(new GuiEditKeyframe(close)); - } else { - keyframeSelectionTime = cur; - } - } - } - } else if(!Mouse.isButtonDown(0)) { - liftedSinceSelection = true; - } - - //Draw Realtime Cursor - if(ReplayHandler.getRealTimelineCursor() >= left_real && ReplayHandler.getRealTimelineCursor() <= right_real) { - long rel_pos = ReplayHandler.getRealTimelineCursor() - left_real; - long rel_width = right_real - left_real; - double perc = (double) rel_pos / (double) rel_width; - - int real_width = (maxX - tl_end_width) - (minX + tl_begin_width); - double rel_x = (float) real_width * perc; - - int real_x = (int) Math.round((minX + tl_begin_width) + rel_x); - mc.renderEngine.bindTexture(this.replay_gui); - - glEnable(GL_BLEND); - this.drawModalRectWithCustomSizedTexture(real_x - 3, y + 3, 44, 0, 8, 16, 64, 64); - //this.drawModalRectWithCustomSizedTexture(real_x, sl_y, u, v, width, height, textureWidth, textureHeight) - } - - - //Draw Keyframe logos - mc.renderEngine.bindTexture(timeline_icons); - for(Keyframe kf : ReplayHandler.getKeyframes()) { - if(kf.getRealTimestamp() > right_real) break; - if(kf.getRealTimestamp() >= left_real) { - int dx = 18; - int dy = 0; - - int ry = y + 3; - - if(kf instanceof TimeKeyframe) { - dy = 5; - ry += 5; - } - if(ReplayHandler.isSelected(kf)) { - dx += 5; - } - - long relative = kf.getRealTimestamp() - (left_real); - double perc = ((double) relative / (double) tot); - - long real_width = full - zero; - long rel_x = Math.round(perc * (double) real_width); - - long real_x = zero + rel_x - 2; - - this.drawModalRectWithCustomSizedTexture((int) real_x, ry, dx, dy, 5, 5, 64, 64); - } - } - - //Draw Play/Pause Button - //Play/Pause button - - int dx = 0; - int dy = 0; - - boolean play = ReplayHandler.isInPath(); - hover = false; - - if(FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { - if(mouseX >= r_ppButtonX && mouseX <= r_ppButtonX + 20 - && mouseY >= r_ppButtonY && mouseY <= r_ppButtonY + 20) { - hover = true; - } - } - - if(play) { - dy = 20; - } - if(hover) { - dx = 20; - } - - mc.renderEngine.bindTexture(replay_gui); - - GlStateManager.resetColor(); - this.drawModalRectWithCustomSizedTexture(r_ppButtonX, r_ppButtonY, dx, dy, 20, 20, 64, 64); - - //Handling the click on the Replay starter - if(hover && Mouse.isButtonDown(0) && isClick() && FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { - if(ReplayHandler.isInPath()) { - ReplayHandler.interruptReplay(); - } else { - ReplayHandler.startPath(null); - } - } - - } - - private void addPlaceKeyframe() { - Entity cam = mc.getRenderViewEntity(); - if(cam == null) return; - ReplayHandler.addKeyframe(new PositionKeyframe(ReplayHandler.getRealTimelineCursor(), new Position(cam.posX, cam.posY, cam.posZ, cam.rotationPitch, cam.rotationYaw % 360, ReplayHandler.getCameraTilt()))); - } - - private void addTimeKeyframe() { - ReplayHandler.addKeyframe(new TimeKeyframe(ReplayHandler.getRealTimelineCursor(), ReplayMod.replaySender.currentTimeStamp())); - } - - private void zoomIn() { - if(!isClick()) return; - this.zoom_scale = Math.max(0.025f, zoom_scale - zoom_steps); - } - - private void zoomOut() { - if(!isClick()) return; - this.zoom_scale = Math.min(1f, zoom_scale + zoom_steps); - this.pos_left = Math.min(pos_left, 1f - zoom_scale); - } - - private boolean isClick() { - if(Mouse.isButtonDown(0)) { - boolean bef = mouseDwn; - mouseDwn = true; - return !bef; - } else { - mouseDwn = false; - return false; - } - } - - private enum MarkerType { - - ONE_S(1 * 1000, 100), - FIVE_S(5 * 1000, 1 * 1000), - QUARTER_M(15 * 1000, 3 * 1000), - HALF_M(30 * 1000, 5 * 1000), - ONE_M(60 * 1000, 10 * 1000), - FIVE_M(5 * 60 * 1000, 50 * 1000); - - int minimum; - int small_min; - int maximum = 10; - - MarkerType(int minimum, int small_min) { - this.minimum = minimum; - this.small_min = small_min; - } - - public static MarkerType getMarkerType(float scale, long totalLength) { - long visible = Math.round((double) totalLength * scale); - long seconds = visible; - - for(MarkerType mt : values()) { - if(seconds / mt.getDistance() <= 10) { - return mt; - } - } - - return FIVE_M; - } - - int getDistance() { - return minimum; - } - - int getSmallDistance() { - return small_min; - } - } - - public void playOrPause() { - boolean paused = !ReplayMod.replaySender.paused(); - if(paused) { - ReplayMod.replaySender.setReplaySpeed(0); - } else { - ReplayMod.replaySender.setReplaySpeed(speedSlider.getSliderValue()); - } - } -} diff --git a/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java b/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java index c6216342..48defecb 100755 --- a/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/events/KeyInputHandler.java @@ -89,7 +89,7 @@ public class KeyInputHandler { } if(kb.getKeyDescription().equals("key.chat") && kb.isPressed()) { - mc.displayGuiScreen(new GuiMouseInput()); + mc.displayGuiScreen(new GuiMouseInput(ReplayMod.overlay)); break; } diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiKeyframeRepository.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiKeyframeRepository.java index eb0f9cfa..2194f2d1 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/GuiKeyframeRepository.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiKeyframeRepository.java @@ -3,6 +3,7 @@ package eu.crushedpixel.replaymod.gui; import eu.crushedpixel.replaymod.ReplayMod; import eu.crushedpixel.replaymod.gui.elements.GuiEntryList; import eu.crushedpixel.replaymod.gui.elements.listeners.SelectionListener; +import eu.crushedpixel.replaymod.gui.overlay.GuiReplayOverlay; import eu.crushedpixel.replaymod.holders.Keyframe; import eu.crushedpixel.replaymod.holders.KeyframeSet; import eu.crushedpixel.replaymod.replay.ReplayHandler; @@ -18,7 +19,7 @@ import java.awt.*; import java.io.IOException; import java.util.ArrayList; -public class GuiKeyframeRepository extends GuiScreen { +public class GuiKeyframeRepository extends GuiScreen implements GuiReplayOverlay.NoOverlay { private boolean initialized = false; diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiMouseInput.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiMouseInput.java index d9bb2bda..a2e8cf09 100755 --- a/src/main/java/eu/crushedpixel/replaymod/gui/GuiMouseInput.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiMouseInput.java @@ -1,6 +1,7 @@ package eu.crushedpixel.replaymod.gui; import eu.crushedpixel.replaymod.ReplayMod; +import eu.crushedpixel.replaymod.gui.overlay.GuiReplayOverlay; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.settings.KeyBinding; @@ -9,6 +10,27 @@ import java.io.IOException; public class GuiMouseInput extends GuiScreen { + private final GuiReplayOverlay overlay; + + public GuiMouseInput(GuiReplayOverlay overlay) { + this.overlay = overlay; + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { + overlay.mouseClicked(mouseX, mouseY); + } + + @Override + protected void mouseReleased(int mouseX, int mouseY, int state) { + overlay.mouseReleased(mouseX, mouseY); + } + + @Override + protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { + overlay.mouseDrag(mouseX, mouseY); + } + @Override protected void keyTyped(char typedChar, int keyCode) throws IOException { for(KeyBinding kb : Minecraft.getMinecraft().gameSettings.keyBindings) diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiPlayerOverview.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiPlayerOverview.java index cd73c403..dbdbb5c6 100755 --- a/src/main/java/eu/crushedpixel/replaymod/gui/GuiPlayerOverview.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiPlayerOverview.java @@ -2,6 +2,7 @@ package eu.crushedpixel.replaymod.gui; import com.mojang.realmsclient.util.Pair; import eu.crushedpixel.replaymod.ReplayMod; +import eu.crushedpixel.replaymod.gui.overlay.GuiReplayOverlay; import eu.crushedpixel.replaymod.holders.PlayerVisibility; import eu.crushedpixel.replaymod.registry.PlayerHandler; import eu.crushedpixel.replaymod.replay.ReplayHandler; @@ -30,7 +31,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; -public class GuiPlayerOverview extends GuiScreen { +public class GuiPlayerOverview extends GuiScreen implements GuiReplayOverlay.NoOverlay { public static boolean defaultSave = false; @@ -289,7 +290,7 @@ public class GuiPlayerOverview extends GuiScreen { } //this is necessary to reset the GL parameters for further GUI rendering - drawRect(0, 0, 0, 0, Color.LIGHT_GRAY.getRGB()); + GlStateManager.enableBlend(); } private PlayerVisibility getVisibilityInstance() { diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiReplaySpeedSlider.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiReplaySpeedSlider.java index 13915232..06fcd1ae 100755 --- a/src/main/java/eu/crushedpixel/replaymod/gui/GuiReplaySpeedSlider.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiReplaySpeedSlider.java @@ -26,11 +26,6 @@ public class GuiReplaySpeedSlider extends GuiButton { this.valueStep = 1; this.displayString = displayKey + ": 1x"; this.displayKey = displayKey; - - Minecraft.getMinecraft().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 float convertScaleRet(float value) { @@ -151,7 +146,7 @@ public class GuiReplaySpeedSlider extends GuiButton { float min = 0 - valueMin; float max = valueMax + min; - return Math.round(value * (max) - min); + return Math.round(value * max - min); } public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) { diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiKeyframeTimeline.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiKeyframeTimeline.java new file mode 100644 index 00000000..0815f635 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiKeyframeTimeline.java @@ -0,0 +1,125 @@ +package eu.crushedpixel.replaymod.gui.elements; + +import eu.crushedpixel.replaymod.gui.GuiEditKeyframe; +import eu.crushedpixel.replaymod.holders.Keyframe; +import eu.crushedpixel.replaymod.holders.PositionKeyframe; +import eu.crushedpixel.replaymod.holders.TimeKeyframe; +import eu.crushedpixel.replaymod.replay.ReplayHandler; +import net.minecraft.client.Minecraft; + +public class GuiKeyframeTimeline extends GuiTimeline { + private static final int KEYFRAME_PLACE_X = 74; + private static final int KEYFRAME_PLACE_Y = 20; + private static final int KEYFRAME_TIME_X = 74; + private static final int KEYFRAME_TIME_Y = 25; + + private Keyframe clickedKeyFrame; + private long clickTime; + private boolean dragging; + + public GuiKeyframeTimeline(int positionX, int positionY, int width) { + super(positionX, positionY, width); + showMarkers = true; + } + + public void mouseClicked(Minecraft mc, int mouseX, int mouseY) { + long time = getTimeAt(mouseX, mouseY); + if (time == -1) { + return; + } + + int tolerance = (int) (2 * Math.round(zoom * timelineLength / width)); + + Keyframe closest; + if (mouseY >= positionY + BORDER_TOP + 5) { + closest = ReplayHandler.getClosestTimeKeyframeForRealTime((int) time, tolerance); + } else if (mouseY >= positionY + BORDER_TOP) { + closest = ReplayHandler.getClosestPlaceKeyframeForRealTime((int) time, tolerance); + } else { + closest = null; + } + + ReplayHandler.selectKeyframe(closest); //can be null, deselects keyframe + + // If we clicked on a key frame, then continue monitoring the mouse for movements + long currentTime = System.currentTimeMillis(); + if (closest != null) { + if (currentTime - clickTime < 500) { // if double clicked then open GUI instead + mc.displayGuiScreen(new GuiEditKeyframe(closest)); + this.clickedKeyFrame = null; + } else { + this.clickedKeyFrame = closest; + this.dragging = false; + } + } else { // If we didn't then just update the cursor + ReplayHandler.setRealTimelineCursor((int) time); + this.dragging = true; + } + this.clickTime = currentTime; + } + + public void mouseDrag(int mouseX, int mouseY) { + long time = getTimeAt(mouseX, mouseY); + if (time != -1) { + if (clickedKeyFrame != null) { + int tolerance = (int) (2 * Math.round(zoom * timelineLength / width)); + + if (dragging || Math.abs(clickedKeyFrame.getRealTimestamp() - time) > tolerance) { + clickedKeyFrame.setRealTimestamp((int) time); + ReplayHandler.sortKeyframes(); + dragging = true; + } + } else if (dragging) { + ReplayHandler.setRealTimelineCursor((int) time); + } + } + } + + public void mouseRelease(int mouseX, int mouseY) { + mouseDrag(mouseX, mouseY); + clickedKeyFrame = null; + dragging = false; + } + + @Override + public void draw(Minecraft mc, int mouseX, int mouseY) { + super.draw(mc, mouseX, mouseY); + + int bodyWidth = width - BORDER_LEFT - BORDER_RIGHT; + + long leftTime = Math.round(timeStart * timelineLength); + long rightTime = Math.round((timeStart + zoom) * timelineLength); + + double segmentLength = timelineLength * zoom; + + //Draw Keyframe logos + for(Keyframe kf : ReplayHandler.getKeyframes()) { + if (kf.getRealTimestamp() <= rightTime && kf.getRealTimestamp() >= leftTime) { + int textureX; + int textureY; + int y = positionY; + if (kf instanceof PositionKeyframe) { + textureX = KEYFRAME_PLACE_X; + textureY = KEYFRAME_PLACE_Y; + y += 0; + } else if (kf instanceof TimeKeyframe) { + textureX = KEYFRAME_TIME_X; + textureY = KEYFRAME_TIME_Y; + y += 5; + } else { + throw new UnsupportedOperationException("Unknown keyframe type: " + kf.getClass()); + } + + if (ReplayHandler.isSelected(kf)) { + textureX += 5; + } + + long positionInSegment = kf.getRealTimestamp() - leftTime; + double fractionOfSegment = positionInSegment / segmentLength; + int x = (int) (positionX + BORDER_LEFT + fractionOfSegment * bodyWidth); + + rect(x - 2, y + BORDER_TOP, textureX, textureY, 5, 5); + } + } + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiScrollbar.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiScrollbar.java new file mode 100644 index 00000000..d3b33afc --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiScrollbar.java @@ -0,0 +1,142 @@ +package eu.crushedpixel.replaymod.gui.elements; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.renderer.GlStateManager; + +import static eu.crushedpixel.replaymod.gui.overlay.GuiReplayOverlay.TEXTURE_SIZE; +import static eu.crushedpixel.replaymod.gui.overlay.GuiReplayOverlay.replay_gui; +import static org.lwjgl.opengl.GL11.GL_BLEND; +import static org.lwjgl.opengl.GL11.glEnable; + +public class GuiScrollbar extends Gui { + + protected static final int BORDER_LEFT = 1; + protected static final int BORDER_RIGHT = 2; + protected static final int BORDER_TOP = 1; + + protected static final int BACKGROUND_WIDTH = 64; + protected static final int BACKGROUND_HEIGHT = 9; + protected static final int BACKGROUND_BODY_WIDTH = BACKGROUND_WIDTH - BORDER_LEFT - BORDER_RIGHT; + protected static final int BACKGROUND_TEXTURE_X = 64; + protected static final int BACKGROUND_TEXTURE_Y = 97; + + protected static final int SLIDER_WIDTH = 62; + protected static final int SLIDER_HEIGHT = 7; + protected static final int SLIDER_TEXTURE_X = 64; + protected static final int SLIDER_TEXTURE_Y = 90; + protected static final int SLIDER_BORDER_LEFT = 1; + protected static final int SLIDER_BORDER_RIGHT = 2; + protected static final int SLIDER_BODY_WIDTH = SLIDER_WIDTH - SLIDER_BORDER_LEFT - SLIDER_BORDER_RIGHT; + + /** + * Current position of the left end of the slider. Must be between 0 and 1 - {@link #size} (inclusive). + */ + public double sliderPosition; + + /** + * Size of the slider. Should be between 0 (exclusive) and 1 (inclusive) + */ + public double size = 1; + + protected final int positionX; + protected final int positionY; + protected final int width; + + private int draggingStart; + private double draggingStartPosition; + + public GuiScrollbar(int positionX, int positionY, int width) { + this.positionX = positionX; + this.positionY = positionY; + this.width = width; + } + + private int getSliderOffsetX() { + return (int) Math.round((width - BORDER_LEFT - BORDER_RIGHT) * sliderPosition) + BORDER_LEFT; + } + + public boolean startDragging(int mouseX, int mouseY) { + int offsetX = getSliderOffsetX(); + int minX = positionX + offsetX; + int maxX = positionX + offsetX + (int) (width * size); + int minY = positionY + BORDER_TOP; + int maxY = minY + SLIDER_HEIGHT; + if (mouseX >= minX && mouseY >= minY && mouseX < maxX && mouseY < maxY) { + draggingStart = mouseX; + draggingStartPosition = sliderPosition; + return true; + } else { + return false; + } + } + + public void doDragging(int mouseX) { + if (draggingStart != -1) { + double delta = (double) (mouseX - draggingStart) / (width - BORDER_LEFT - BORDER_RIGHT); + sliderPosition = Math.max(0, Math.min(1 - size, draggingStartPosition + delta)); + } + } + + public void endDragging(int mouseX) { + doDragging(mouseX); + draggingStart = -1; + } + + public void draw(Minecraft mc) { + GlStateManager.resetColor(); + mc.renderEngine.bindTexture(replay_gui); + glEnable(GL_BLEND); + + // Background + { + // We have to increase the border size as there is one pixel row which is part of the border while drawing + // but isn't during position calculations + int BORDER_LEFT = GuiScrollbar.BORDER_LEFT + 1; + int BACKGROUND_BODY_WIDTH = GuiScrollbar.BACKGROUND_BODY_WIDTH - 1; + + int bodyLeft = positionX + BORDER_LEFT; + int bodyRight = positionX + width - BORDER_RIGHT; + + // Left border + rect(positionX, positionY, BACKGROUND_TEXTURE_X, BACKGROUND_TEXTURE_Y, BORDER_LEFT, BACKGROUND_HEIGHT); + // Body + for (int i = bodyLeft; i < bodyRight; i += BACKGROUND_BODY_WIDTH) { + rect(i, positionY, BACKGROUND_TEXTURE_X + BORDER_LEFT, BACKGROUND_TEXTURE_Y, + Math.min(BACKGROUND_BODY_WIDTH, bodyRight - i), BACKGROUND_HEIGHT); + } + // Right border + rect(bodyRight, positionY, BACKGROUND_TEXTURE_X + BACKGROUND_WIDTH - BORDER_RIGHT, + BACKGROUND_TEXTURE_Y, BORDER_RIGHT, BACKGROUND_HEIGHT); + } + + // The slider itself + { + int positionX = this.positionX + getSliderOffsetX(); + int positionY = this.positionY + BORDER_TOP; + + int backgroundBodyWidth = width - BORDER_LEFT - BORDER_RIGHT; + int bodyLeft = positionX + SLIDER_BORDER_LEFT; + int bodyRight = positionX + (int) Math.round(backgroundBodyWidth * size) - SLIDER_BORDER_RIGHT; + + // Left border + rect(positionX, positionY, SLIDER_TEXTURE_X, SLIDER_TEXTURE_Y, SLIDER_BORDER_LEFT, SLIDER_HEIGHT); + // Body + for (int i = bodyLeft; i < bodyRight; i += SLIDER_BODY_WIDTH) { + rect(i, positionY, SLIDER_TEXTURE_X + SLIDER_BORDER_LEFT, SLIDER_TEXTURE_Y, + Math.min(SLIDER_BODY_WIDTH, bodyRight - i), SLIDER_HEIGHT); + } + // Right border + rect(bodyRight, positionY, SLIDER_TEXTURE_X + SLIDER_WIDTH - SLIDER_BORDER_RIGHT, + SLIDER_TEXTURE_Y, SLIDER_BORDER_RIGHT, SLIDER_HEIGHT); + } + } + + protected void rect(int x, int y, int u, int v, int width, int height) { + GlStateManager.resetColor(); + Minecraft.getMinecraft().renderEngine.bindTexture(replay_gui); + glEnable(GL_BLEND); + + drawModalRectWithCustomSizedTexture(x, y, u, v, width, height, TEXTURE_SIZE, TEXTURE_SIZE); + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiTexturedButton.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiTexturedButton.java new file mode 100644 index 00000000..df31f555 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiTexturedButton.java @@ -0,0 +1,38 @@ +package eu.crushedpixel.replaymod.gui.elements; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.util.ResourceLocation; + +public class GuiTexturedButton extends GuiButton { + private final ResourceLocation texture; + private final int u, v; + private final int textureWidth, textureHeight; + public GuiTexturedButton(int buttonId, int x, int y, int width, int height, ResourceLocation texture, + int u, int v, int textureWidth, int textureHeight) { + super(buttonId, x, y, width, height, ""); + this.texture = texture; + this.u = u; + this.v = v; + this.textureWidth = textureWidth; + this.textureHeight = textureHeight; + } + + @Override + public void drawButton(Minecraft mc, int mouseX, int mouseY) { + if (visible) { + hovered = mouseX >= xPosition + && mouseY >= yPosition + && mouseX < xPosition + width + && mouseY < yPosition + height; + + mc.renderEngine.bindTexture(texture); + + GlStateManager.color(1, 1, 1); + int u = this.u + (hovered ? width : 0); + Gui.drawModalRectWithCustomSizedTexture(xPosition, yPosition, u, v, width, height, textureWidth, textureHeight); + } + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiTimeline.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiTimeline.java new file mode 100644 index 00000000..ecab3eea --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiTimeline.java @@ -0,0 +1,194 @@ +package eu.crushedpixel.replaymod.gui.elements; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; + +import static eu.crushedpixel.replaymod.gui.overlay.GuiReplayOverlay.TEXTURE_SIZE; +import static eu.crushedpixel.replaymod.gui.overlay.GuiReplayOverlay.replay_gui; +import static org.lwjgl.opengl.GL11.GL_BLEND; +import static org.lwjgl.opengl.GL11.glEnable; + +public class GuiTimeline extends Gui { + + protected static final int TEXTURE_WIDTH = 64; + protected static final int BORDER_TOP = 4; + protected static final int BORDER_BOTTOM = 3; + + protected static final int HEIGHT = 22; + protected static final int TEXTURE_X = 64; + protected static final int TEXTURE_Y = 106; + + protected static final int BORDER_LEFT = 4; + protected static final int BORDER_RIGHT = 4; + protected static final int BODY_WIDTH = TEXTURE_WIDTH - BORDER_LEFT - BORDER_RIGHT; + + /** + * Current position of the cursor. Should normally be between 0 and {@link #timelineLength}. + */ + public int cursorPosition; + + /** + * Total length of the timeline. + */ + public int timelineLength; + + /** + * The time at the left border of this timeline (fraction of the total length). + */ + public double timeStart; + + /** + * Zoom of this timeline. 1/10 allows the user to see 1/10 of the total length. + */ + public double zoom = 1; + + /** + * Whether to draw markers on the timeline in regular time intervals. + * This draws the time at big markers above the timeline. Therefore extra space in negative y direction + * should be kept empty if markers are desired. + */ + public boolean showMarkers; + + protected final int positionX; + protected final int positionY; + protected final int width; + + public GuiTimeline(int positionX, int positionY, int width) { + this.positionX = positionX; + this.positionY = positionY; + this.width = width; + } + + /** + * Returns the time which the mouse is at. + * @param mouseX X coordinate of the mouse + * @param mouseY Y coordinate of the mouse + * @return The time or -1 if the mouse isn't on the timeline + */ + public long getTimeAt(int mouseX, int mouseY) { + int left = positionX + BORDER_LEFT; + int right = positionX + width - BORDER_RIGHT; + int bodyWidth = width - BORDER_LEFT - BORDER_RIGHT; + if (mouseX >= left && mouseX <= right && mouseY >= positionY && mouseY <= positionY + HEIGHT) { + double segmentLength = timelineLength * zoom; + double segmentTime = segmentLength * (mouseX - left) / bodyWidth; + return Math.round(timeStart * timelineLength + segmentTime); + } else { + return -1; + } + } + + public void draw(Minecraft mc, int mouseX, int mouseY) { + int bodyLeft = positionX + BORDER_LEFT; + int bodyRight = positionX + width - BORDER_RIGHT; + int bodyWidth = width - BORDER_LEFT - BORDER_RIGHT; + + { + // We have to increase the border size as there is one pixel row which is part of the border while drawing + // but isn't during position calculations + int BORDER_LEFT = GuiTimeline.BORDER_LEFT + 1; + int BODY_WIDTH = GuiTimeline.BODY_WIDTH - 1; + + // Left border + rect(positionX, positionY, TEXTURE_X, TEXTURE_Y, BORDER_LEFT, HEIGHT); + // Body + for (int i = bodyLeft; i < bodyRight; i += BODY_WIDTH) { + rect(i, positionY, TEXTURE_X + BORDER_LEFT, TEXTURE_Y, Math.min(BODY_WIDTH, bodyRight - i), HEIGHT); + } + // Right border + rect(bodyRight, positionY, TEXTURE_X + BORDER_LEFT + BODY_WIDTH, TEXTURE_Y, BORDER_RIGHT, HEIGHT); + } + + long leftTime = Math.round(timeStart * timelineLength); + long rightTime = Math.round((timeStart + zoom) * timelineLength); + + // Draw markers + if (showMarkers) { + int markerY = positionY + HEIGHT - BORDER_BOTTOM; + MarkerType mt = MarkerType.getMarkerType(zoom, timelineLength); + + // Small markers + for (int s = 0; s <= timelineLength; s += mt.smallDistance) { + if (s <= rightTime && s >= leftTime) { + long positionInSegment = s - leftTime; + double fractionOfSegment = positionInSegment / (zoom * timelineLength); + int markerX = (int) (positionX + BORDER_LEFT + fractionOfSegment * bodyWidth); + + drawVerticalLine(markerX, markerY - 3, markerY, 0xffffffff); + } + } + + // Big markers + for (int s = 0; s <= timelineLength; s += mt.distance) { + if (s <= rightTime && s >= leftTime) { + long positionInSegment = s - leftTime; + double fractionOfSegment = positionInSegment / (zoom * timelineLength); + int markerX = (int) (positionX + BORDER_LEFT + fractionOfSegment * bodyWidth); + + drawVerticalLine(markerX, markerY - 7, markerY, 0xffc0c0c0); // Light gray + + // Write time above the timeline + long sec = Math.round(s / 1000.0); + String timestamp = String.format("%02d:%02ds", sec / 60, sec % 60); + drawCenteredString(mc.fontRendererObj, timestamp, markerX, positionY - 8, 0xffffffff); + } + } + } + + // Draw the cursor if it's on the current timeline segment + if (cursorPosition >= leftTime && cursorPosition <= rightTime) { + long positionInSegment = cursorPosition - leftTime; + double fractionOfSegment = positionInSegment / (zoom * timelineLength); + int cursorX = (int) (positionX + BORDER_LEFT + fractionOfSegment * bodyWidth); + + rect(cursorX - 2, positionY + BORDER_TOP, 84, 20, 5, 16); + } + + // Draw time under cursor if the mouse is on the timeline + long mouseTime = getTimeAt(mouseX, mouseY); + if (mouseTime != -1) { + long sec = mouseTime / 1000; + String timestamp = String.format("%02d:%02ds", sec / 60, sec % 60); + drawCenteredString(mc.fontRendererObj, timestamp, mouseX, mouseY + 5, 0xffffffff); + } + } + + protected void rect(int x, int y, int u, int v, int width, int height) { + Minecraft.getMinecraft().renderEngine.bindTexture(replay_gui); + glEnable(GL_BLEND); + + drawModalRectWithCustomSizedTexture(x, y, u, v, width, height, TEXTURE_SIZE, TEXTURE_SIZE); + } + + + private enum MarkerType { + + ONE_S(1000, 100), + FIVE_S(5 * 1000, 1000), + QUARTER_M(15 * 1000, 3 * 1000), + HALF_M(30 * 1000, 5 * 1000), + ONE_M(60 * 1000, 10 * 1000), + FIVE_M(5 * 60 * 1000, 50 * 1000); + + int distance; + int smallDistance; + int maximum = 10; + + MarkerType(int minimum, int smallDistance) { + this.distance = minimum; + this.smallDistance = smallDistance; + } + + public static MarkerType getMarkerType(double scale, long totalLength) { + long seconds = Math.round(totalLength * scale); + + for(MarkerType mt : values()) { + if(seconds / mt.distance <= 10) { + return mt; + } + } + + return FIVE_M; + } + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/overlay/GuiRecordingOverlay.java b/src/main/java/eu/crushedpixel/replaymod/gui/overlay/GuiRecordingOverlay.java new file mode 100644 index 00000000..fa2e98e5 --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/gui/overlay/GuiRecordingOverlay.java @@ -0,0 +1,37 @@ +package eu.crushedpixel.replaymod.gui.overlay; + +import eu.crushedpixel.replaymod.ReplayMod; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.resources.I18n; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +/** + * Renders overlay during recording. + */ +public class GuiRecordingOverlay { + private final Minecraft mc; + + public GuiRecordingOverlay(Minecraft mc) { + this.mc = mc; + } + + /** + * Render the recording icon and text in the top left corner of the screen. + * @param event Rendered post game overlay + */ + @SubscribeEvent + public void renderRecordingIndicator(RenderGameOverlayEvent.Post event) { + if(ReplayMod.replaySettings.showRecordingIndicator()) { + FontRenderer fontRenderer = mc.fontRendererObj; + fontRenderer.drawString(I18n.format("replaymod.gui.recording").toUpperCase(), 30, 18 - (fontRenderer.FONT_HEIGHT / 2), 0xffffffff); + mc.renderEngine.bindTexture(GuiReplayOverlay.replay_gui); + GlStateManager.resetColor(); + GlStateManager.enableAlpha(); + Gui.drawModalRectWithCustomSizedTexture(10, 10, 40, 21, 16, 16, 64, 64); + } + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/overlay/GuiReplayOverlay.java b/src/main/java/eu/crushedpixel/replaymod/gui/overlay/GuiReplayOverlay.java new file mode 100755 index 00000000..1b024cdd --- /dev/null +++ b/src/main/java/eu/crushedpixel/replaymod/gui/overlay/GuiReplayOverlay.java @@ -0,0 +1,411 @@ +package eu.crushedpixel.replaymod.gui.overlay; + +import eu.crushedpixel.replaymod.ReplayMod; +import eu.crushedpixel.replaymod.entities.CameraEntity; +import eu.crushedpixel.replaymod.gui.GuiMouseInput; +import eu.crushedpixel.replaymod.gui.GuiRenderSettings; +import eu.crushedpixel.replaymod.gui.GuiReplaySpeedSlider; +import eu.crushedpixel.replaymod.gui.elements.GuiKeyframeTimeline; +import eu.crushedpixel.replaymod.gui.elements.GuiScrollbar; +import eu.crushedpixel.replaymod.gui.elements.GuiTexturedButton; +import eu.crushedpixel.replaymod.gui.elements.GuiTimeline; +import eu.crushedpixel.replaymod.holders.Keyframe; +import eu.crushedpixel.replaymod.holders.Position; +import eu.crushedpixel.replaymod.holders.PositionKeyframe; +import eu.crushedpixel.replaymod.holders.TimeKeyframe; +import eu.crushedpixel.replaymod.registry.ReplayGuiRegistry; +import eu.crushedpixel.replaymod.replay.ReplayHandler; +import eu.crushedpixel.replaymod.utils.MouseUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiChat; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.inventory.GuiInventory; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.resources.I18n; +import net.minecraft.entity.Entity; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.event.RenderGameOverlayEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.client.FMLClientHandler; +import net.minecraftforge.fml.common.FMLCommonHandler; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.Display; +import org.lwjgl.util.Point; + +import java.io.IOException; + +import static net.minecraft.client.renderer.GlStateManager.*; +import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; +import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; + +public class GuiReplayOverlay extends Gui { + private static final Minecraft mc = Minecraft.getMinecraft(); + public static final ResourceLocation replay_gui = new ResourceLocation("replaymod", "replay_gui.png"); + public static final int TEXTURE_SIZE = 128; + private static final float ZOOM_STEPS = 0.05f; + + private static GuiTexturedButton texturedButton(int x, int y, int u, int v, int size) { + return new GuiTexturedButton(0, x, y, size, size, replay_gui, u, v, TEXTURE_SIZE, TEXTURE_SIZE); + } + + private final int displayWidth = mc.displayWidth; + private final int displayHeight = mc.displayHeight; + + private final Point screenDimensions = MouseUtils.getScaledDimensions(); + private final int WIDTH = screenDimensions.getX(); + private final int HEIGHT = screenDimensions.getY(); + + // Top row + private final int TOP_ROW = 10; + private final int BUTTON_PLAY_PAUSE_X = 10; + private final int SPEED_X = 35; + private final int SPEED_WIDTH = 100; + private final int TIMELINE_X = SPEED_X + SPEED_WIDTH + 5; + + // Bottom row + private final int BOTTOM_ROW = TOP_ROW + 33; + private final int BUTTON_PLAY_PATH_X = 10; + private final int BUTTON_EXPORT_X = BUTTON_PLAY_PATH_X + 25; + private final int BUTTON_PLACE_X = BUTTON_EXPORT_X + 25; + private final int BUTTON_TIME_X = BUTTON_PLACE_X + 25; + private final int TIMELINE_REAL_X = BUTTON_TIME_X + 25; + private final int TIMELINE_REAL_WIDTH = WIDTH - 14 - 11 - TIMELINE_REAL_X; + + private final GuiButton buttonPlay = texturedButton(BUTTON_PLAY_PAUSE_X, TOP_ROW, 0, 0, 20); + private final GuiButton buttonPause = texturedButton(BUTTON_PLAY_PAUSE_X, TOP_ROW, 0, 20, 20); + private final GuiButton buttonExport = texturedButton(BUTTON_EXPORT_X, BOTTOM_ROW, 40, 0, 20); + private final GuiButton buttonPlayPath = texturedButton(BUTTON_PLAY_PATH_X, BOTTOM_ROW, 0, 0, 20); + private final GuiButton buttonPlace = texturedButton(BUTTON_PLACE_X, BOTTOM_ROW, 0, 40, 20); + private final GuiButton buttonPlaceSelected = texturedButton(BUTTON_PLACE_X, BOTTOM_ROW, 0, 60, 20); + private final GuiButton buttonTime = texturedButton(BUTTON_TIME_X, BOTTOM_ROW, 0, 80, 20); + private final GuiButton buttonTimeSelected = texturedButton(BUTTON_TIME_X, BOTTOM_ROW, 0, 100, 20); + private final GuiButton buttonZoomIn = texturedButton(WIDTH - 14 - 9, BOTTOM_ROW, 40, 20, 9); + private final GuiButton buttonZoomOut = texturedButton(WIDTH - 14 - 9, BOTTOM_ROW + 11, 40, 30, 9); + + private final GuiTimeline timeline = new GuiTimeline(TIMELINE_X, TOP_ROW - 1, WIDTH - 14 - TIMELINE_X); + private final GuiKeyframeTimeline timelineReal = new GuiKeyframeTimeline(TIMELINE_REAL_X, BOTTOM_ROW - 1, TIMELINE_REAL_WIDTH); + { + timelineReal.timelineLength = 10 * 60 * 1000; + } + private final GuiScrollbar scrollbar = new GuiScrollbar(TIMELINE_REAL_X, BOTTOM_ROW + 22, TIMELINE_REAL_WIDTH); + + private GuiReplaySpeedSlider speedSlider = new GuiReplaySpeedSlider(1, SPEED_X, TOP_ROW, I18n.format("replaymod.gui.speed")); + + private float zoom_scale = 0.1f; //can see 1/10th of the timeline + private double pos_left = 0f; //left border of timeline is at 0% + + public boolean isVisible() { + return ReplayHandler.isInReplay(); + } + + /** + * Resets the UI. + * @param slider {@code true} if the speed-slider should be reset as well + */ + public void resetUI(boolean slider) { + if(FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { + mc.displayGuiScreen(null); + } + ReplayHandler.setRealTimelineCursor(0); + if(slider) + speedSlider = new GuiReplaySpeedSlider(1, SPEED_X, TOP_ROW, I18n.format("replaymod.gui.speed")); + } + + public void register() { + FMLCommonHandler.instance().bus().register(this); + MinecraftForge.EVENT_BUS.register(this); + } + + public void unregister() { + FMLCommonHandler.instance().bus().unregister(this); + MinecraftForge.EVENT_BUS.unregister(this); + } + + private void checkResize() { + if (displayWidth != mc.displayWidth || displayHeight != mc.displayHeight) { + GuiReplayOverlay other = new GuiReplayOverlay(); + other.zoom_scale = this.zoom_scale; + other.pos_left = this.pos_left; + other.speedSlider = this.speedSlider; + + this.unregister(); + other.register(); + ReplayMod.overlay = other; + } + } + + @SubscribeEvent + public void onRenderTabList(RenderGameOverlayEvent.Pre event) { //cancelling tab list rendering and rendering help instead + if (!isVisible()) return; + if (event.type == RenderGameOverlayEvent.ElementType.PLAYER_LIST) { + event.setCanceled(true); + } + } + + private void tick() { + if (mc.inGameHasFocus) { // TODO Check if this is necessary + Point scaled = MouseUtils.getScaledDimensions(); + Mouse.setCursorPosition(scaled.getX() / 2, scaled.getY() / 2); + } + + if (FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { + Entity player = ReplayHandler.getCameraEntity(); + if(player != null) { + player.setVelocity(0, 0, 0); + } + } + + checkResize(); + } + + public void mouseDrag(int mouseX, int mouseY) { + speedSlider.mousePressed(mc, mouseX, mouseY); + + scrollbar.doDragging(mouseX); + pos_left = scrollbar.sliderPosition; + + timelineReal.mouseDrag(mouseX, mouseY); + } + + public void mouseReleased(int mouseX, int mouseY) { + speedSlider.mouseReleased(mouseX, mouseY); + + scrollbar.endDragging(mouseX); + pos_left = scrollbar.sliderPosition; + + timelineReal.mouseRelease(mouseX, mouseY); + } + + public void mouseClicked(int mouseX, int mouseY) { + if (buttonPlayPath.mousePressed(mc, mouseX, mouseY)) { + if (ReplayHandler.isInPath()) { + ReplayHandler.interruptReplay(); + } else { + ReplayHandler.startPath(null); + } + } + + if (ReplayHandler.isInPath()) { + return; // Only allow clicking of cancel button during path replay + } + + if (ReplayMod.replaySender.paused()) { + if (buttonPlay.mousePressed(mc, mouseX, mouseY)) { + ReplayMod.replaySender.setReplaySpeed(speedSlider.getSliderValue()); + } + } else { + if (buttonPause.mousePressed(mc, mouseX, mouseY)) { + ReplayMod.replaySender.setReplaySpeed(0); + } + } + + speedSlider.mousePressed(mc, mouseX, mouseY); + scrollbar.startDragging(mouseX, mouseY); + timelineReal.mouseClicked(mc, mouseX, mouseY); + + if (buttonExport.mousePressed(mc, mouseX, mouseY)) { + mc.displayGuiScreen(new GuiRenderSettings()); + } + + Keyframe keyframe = ReplayHandler.getSelectedKeyframe(); + + if (buttonPlace.mousePressed(mc, mouseX, mouseY)) { + if (keyframe instanceof PositionKeyframe) { + ReplayHandler.removeKeyframe(keyframe); + } else { + Entity cam = mc.getRenderViewEntity(); + if(cam != null) { + Position position = new Position(cam.posX, cam.posY, cam.posZ, cam.rotationPitch, + cam.rotationYaw % 360, ReplayHandler.getCameraTilt()); + ReplayHandler.addKeyframe(new PositionKeyframe(ReplayHandler.getRealTimelineCursor(), position)); + } + } + } + + if (buttonTime.mousePressed(mc, mouseX, mouseY)) { + if (keyframe instanceof TimeKeyframe) { + ReplayHandler.removeKeyframe(keyframe); + } else { + ReplayHandler.addKeyframe(new TimeKeyframe(ReplayHandler.getRealTimelineCursor(), ReplayMod.replaySender.currentTimeStamp())); + } + } + + if (buttonZoomIn.mousePressed(mc, mouseX, mouseY)) { + zoom_scale = Math.max(0.025f, zoom_scale - ZOOM_STEPS); + } + + if (buttonZoomOut.mousePressed(mc, mouseX, mouseY)) { + zoom_scale = Math.min(1f, zoom_scale + ZOOM_STEPS); + pos_left = Math.min(pos_left, 1f - zoom_scale); + } + + long timelineTime = timeline.getTimeAt(mouseX, mouseY); + if (timelineTime != -1) { // Click on timeline + //When hurrying, no Timeline jumping etc. is possible + if(!ReplayMod.replaySender.isHurrying()) { + if(timelineTime < ReplayMod.replaySender.currentTimeStamp()) { + mc.displayGuiScreen(null); + } + + CameraEntity cam = ReplayHandler.getCameraEntity(); + if(cam != null) { + ReplayHandler.setLastPosition(new Position(cam.posX, cam.posY, cam.posZ, cam.rotationPitch, cam.rotationYaw)); + } else { + ReplayHandler.setLastPosition(null); + } + + long diff = timelineTime - ReplayMod.replaySender.getDesiredTimestamp(); + if(diff != 0) { + if (diff > 0 && diff < 5000) { // Small difference and no time travel + ReplayMod.replaySender.jumpToTime((int) timelineTime); + } else { // We either have to restart the replay or send a significant amount of packets + // Render our please-wait-screen + GuiScreen guiScreen = new GuiScreen() { + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + drawBackground(0); + drawCenteredString(fontRendererObj, I18n.format("replaymod.gui.pleasewait"), + width / 2, height / 2, 0xffffffff); + } + }; + + // Make sure that the replaysender changes into sync mode + ReplayMod.replaySender.setSyncModeAndWait(); + + // Perform the rendering using OpenGL + pushMatrix(); + clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + enableTexture2D(); + mc.getFramebuffer().bindFramebuffer(true); + mc.entityRenderer.setupOverlayRendering(); + + guiScreen.setWorldAndResolution(mc, WIDTH, HEIGHT); + guiScreen.drawScreen(0, 0, 0); + + mc.getFramebuffer().unbindFramebuffer(); + popMatrix(); + pushMatrix(); + mc.getFramebuffer().framebufferRender(mc.displayWidth, mc.displayHeight); + popMatrix(); + + Display.update(); + + // Send the packets + ReplayMod.replaySender.sendPacketsTill((int) timelineTime); + ReplayMod.replaySender.setAsyncMode(true); + ReplayMod.replaySender.setReplaySpeed(0); + + // Tick twice to process all packets and position interpolation + try { + mc.runTick(); + mc.runTick(); + } catch (IOException e) { + e.printStackTrace(); // This should never be thrown but whatever + } + + // No need to remove our please-wait-screen. It'll vanish with the next + // render pass as it's never been a real GuiScreen in the first place. + } + } + } + } + } + + @SubscribeEvent + public void onRenderGui(RenderGameOverlayEvent.Post event) throws IllegalArgumentException, IllegalAccessException { + if (!isVisible()) return; + + FMLClientHandler fml = FMLClientHandler.instance(); + + tick(); + + // Do not draw if GUI doesn't want us to + if(mc.currentScreen instanceof NoOverlay) { + return; + } + + // If we are not currently spectating someone + if (ReplayHandler.isCamera()) { + ReplayGuiRegistry.hide(); // hide all normal UI + } else { + ReplayGuiRegistry.show(); // otherwise show them + } + + // Replace chat and inventory GUI (opened by pressing the respective hotkey) with a dummy input GUI + if(fml.isGUIOpen(GuiChat.class) || fml.isGUIOpen(GuiInventory.class)) { + mc.displayGuiScreen(new GuiMouseInput(this)); + } + + GlStateManager.resetColor(); + + Keyframe keyframe = ReplayHandler.getSelectedKeyframe(); + + Point mousePoint = MouseUtils.getMousePos(); + int mouseX = mousePoint.getX(); + int mouseY = mousePoint.getY(); + + if (!(mc.currentScreen instanceof GuiMouseInput)) { + // We only react to the mouse if our gui screen is opened. + // Otherwise we just move the mouse away so it doesn't activate any buttons + mouseX = mouseY = -1000; + } + + // Draw speed slider + speedSlider.drawButton(mc, mouseX, mouseY); + + // Draw buttons + buttonExport.drawButton(mc, mouseX, mouseY); + + // Draw play/pause button + if (ReplayMod.replaySender.paused()) { + buttonPlay.drawButton(mc, mouseX, mouseY); + } else { + buttonPause.drawButton(mc, mouseX, mouseY); + } + + buttonPlayPath.drawButton(mc, mouseX, mouseY); + + // Keyframe buttons + if (keyframe instanceof PositionKeyframe) { + buttonPlaceSelected.drawButton(mc, mouseX, mouseY); + } else { + buttonPlace.drawButton(mc, mouseX, mouseY); + } + + if (keyframe instanceof TimeKeyframe) { + buttonTimeSelected.drawButton(mc, mouseX, mouseY); + } else { + buttonTime.drawButton(mc, mouseX, mouseY); + } + + buttonZoomIn.drawButton(mc, mouseX, mouseY); + buttonZoomOut.drawButton(mc, mouseX, mouseY); + + // Draw scrollbar for real timeline + scrollbar.size = zoom_scale; + scrollbar.sliderPosition = pos_left; + scrollbar.draw(mc); + + // Finally draw timelines so that no other GUI elements overlap the mouse-position strings + timeline.cursorPosition = ReplayMod.replaySender.currentTimeStamp(); + timeline.timelineLength = ReplayMod.replaySender.replayLength(); + timeline.draw(mc, mouseX, mouseY); + + timelineReal.cursorPosition = ReplayHandler.getRealTimelineCursor(); + timelineReal.zoom = zoom_scale; + timelineReal.timeStart = pos_left; + timelineReal.draw(mc, mouseX, mouseY); + + GlStateManager.enableBlend(); + } + + /** + * Dummy interface for GUI on which this replay overlay shall not be rendered. + */ + public static interface NoOverlay { + + } +} diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/replayviewer/GuiReplayViewer.java b/src/main/java/eu/crushedpixel/replaymod/gui/replayviewer/GuiReplayViewer.java index 2b70b8ee..96254a4c 100755 --- a/src/main/java/eu/crushedpixel/replaymod/gui/replayviewer/GuiReplayViewer.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/replayviewer/GuiReplayViewer.java @@ -24,6 +24,7 @@ import net.minecraft.util.Util; import org.apache.commons.io.FilenameUtils; import org.lwjgl.Sys; import org.lwjgl.input.Keyboard; +import org.lwjgl.util.Point; import javax.imageio.ImageIO; import java.awt.*; diff --git a/src/main/java/eu/crushedpixel/replaymod/recording/ConnectionEventHandler.java b/src/main/java/eu/crushedpixel/replaymod/recording/ConnectionEventHandler.java index dceaf60d..48c9d702 100755 --- a/src/main/java/eu/crushedpixel/replaymod/recording/ConnectionEventHandler.java +++ b/src/main/java/eu/crushedpixel/replaymod/recording/ConnectionEventHandler.java @@ -2,14 +2,17 @@ package eu.crushedpixel.replaymod.recording; import eu.crushedpixel.replaymod.ReplayMod; import eu.crushedpixel.replaymod.chat.ChatMessageHandler.ChatMessageType; +import eu.crushedpixel.replaymod.gui.overlay.GuiRecordingOverlay; import eu.crushedpixel.replaymod.utils.ReplayFile; import eu.crushedpixel.replaymod.utils.ReplayFileIO; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelPipeline; +import net.minecraft.client.Minecraft; import net.minecraft.network.NetworkManager; import net.minecraft.network.Packet; import net.minecraft.server.MinecraftServer; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.network.FMLNetworkEvent.ClientConnectedToServerEvent; import net.minecraftforge.fml.common.network.FMLNetworkEvent.ClientDisconnectionFromServerEvent; @@ -33,6 +36,7 @@ public class ConnectionEventHandler { private static final SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); private static PacketListener packetListener = null; private static boolean isRecording = false; + private final GuiRecordingOverlay guiOverlay = new GuiRecordingOverlay(Minecraft.getMinecraft()); private File currentFile; private String fileName; @@ -101,6 +105,8 @@ public class ConnectionEventHandler { ReplayMod.chatMessageHandler.addLocalizedChatMessage("replaymod.chat.recordingstarted", ChatMessageType.INFORMATION); isRecording = true; + MinecraftForge.EVENT_BUS.register(guiOverlay); + final PacketListener listener = insert; if(insert != null && event.isLocal) { @@ -139,6 +145,7 @@ public class ConnectionEventHandler { @SubscribeEvent public void onDisconnectedFromServerEvent(ClientDisconnectionFromServerEvent event) { isRecording = false; + MinecraftForge.EVENT_BUS.unregister(guiOverlay); packetListener = null; ReplayMod.chatMessageHandler.stop(); } diff --git a/src/main/java/eu/crushedpixel/replaymod/replay/ReplaySender.java b/src/main/java/eu/crushedpixel/replaymod/replay/ReplaySender.java index c537d5b5..f4b6ee7e 100755 --- a/src/main/java/eu/crushedpixel/replaymod/replay/ReplaySender.java +++ b/src/main/java/eu/crushedpixel/replaymod/replay/ReplaySender.java @@ -176,6 +176,20 @@ public class ReplaySender extends ChannelInboundHandlerAdapter { } } + /** + * Set whether this replay sender to operate in sync mode. + * When in sync mode, it will send packets when {@link #sendPacketsTill(int)} is called. + * This call will block until the async worker thread has stopped. + */ + public void setSyncModeAndWait() { + if (!this.asyncMode) return; + this.asyncMode = false; + this.terminate = true; + synchronized (this) { + // This will wait for the worker thread to leave the synchronized code part + } + } + /** * Return the timestamp of the last packet sent. * @return The timestamp in milliseconds since the start of the replay diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/MouseUtils.java b/src/main/java/eu/crushedpixel/replaymod/utils/MouseUtils.java index 00c1ddc8..2029bf81 100755 --- a/src/main/java/eu/crushedpixel/replaymod/utils/MouseUtils.java +++ b/src/main/java/eu/crushedpixel/replaymod/utils/MouseUtils.java @@ -3,8 +3,7 @@ package eu.crushedpixel.replaymod.utils; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.ScaledResolution; import org.lwjgl.input.Mouse; - -import java.awt.*; +import org.lwjgl.util.Point; public class MouseUtils { diff --git a/src/main/resources/assets/replaymod/extended_gui.png b/src/main/resources/assets/replaymod/extended_gui.png deleted file mode 100755 index 68bb39250a9c2ad226c7a438dc29e6f84ec6f80d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1972 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 D^@*UiiTRB4LBGz#`6qENoaL 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 diff --git a/src/main/resources/assets/replaymod/timeline_icons.png b/src/main/resources/assets/replaymod/timeline_icons.png deleted file mode 100755 index a3127b156d6196ef4551b9fb16f57e5bc4c83197..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2264 zcmV;}2q*W6P)>fV;@cnTA zss9JQ&%P5go$mmD9UVy{$hxusu(7d$t5>fA0G>a89x;D*{~iF~;}74*FF)D>0DSr7 z=aKRgMmXPTGs2M}UR`+aVXciG3L&C)430DvSBND>k8R7yu4P$_)`l@EGtZf^QA zR->Pb7cYkEyF0#>=Z8P}qp!tz{TXJA9la2^a^(t~bGUi)W(a}Z-Q9@!qu<_zF@)d! z`n&kmZ~hJd_~MJtahyp4z@tZx@bcx$F!{ptTMImo8lj*Y%vBb#cu(#|JYC)asUIs8ad!-mce-mYj^-! zJmAO~VV(wf=Yi0XQ|>@Os7}*>&|omm8Fe6ZtPSA3hjR{6%9%jO7($YWsXWJ#<_Uz3 zuLo2>sO|yNfKWXT{9gj0kRfLPp*Ei5NE1M)PSb$UG@j!~^Tq-1v;h{m#>M7+<>1X?r_q`wd z9=)!L!4lM8Fx4LkSB6$lp zfVDQVc%J7Njb=A7^E^j|A*7TcX##*TDCMzz%Nq_2l0-!E7H)tF{yOJCM9A|TS(fd& zU?Pg3b4At5GiLwt>Km;?-3t&AIOp)*BhPbeZ*Sw&sZ*_!9zT8@5}o(H6%IQ`kaB+V z7HWWc7AK+rjUPOCFmrO1JnJr8UOL&Z7nIMMMsw4#pWFXqVJRvx&rHM_~!VORdFjMNjMO#6ikvNpu>8Fs2gM&{d4C&o<4m#E^XFAO_MhoA?Ent&o7H@qGly%D+|Tuf{+9+twHe zo%}Y?RYkrTjy!su;tLlgC5*MDqcHR{!ijF2my}c`50Dv}i<~BZ<)|DS1e$POf=6xu zB^Wx2g9sRChpu8U1f+roBUIy2SGXDgfL@1z$^&3y4FC^<2Z6BK6BAK1=ApUCY4QVr zA%yF;$W?+tazF?Lc%Z8a^?IqqK)Z?Y$N&Jq84quQH>wW@0|x{7*w1oD5zjp3)cFBG z@c1J$rDVj{O;p%e3B@BLbrowJj}1`Xz3%JaR#dqj^+V~0M z5J&}sQWD14kav_+P`vOOr9>1e9Bg8Ii365Y`cwIl6kdPzZ>+ty9(ILVYvg)_G%3FJ z*4Dc5S%DrwBps0Opn-wxBzrDUGYg>&1QeiT zw3X&7E1rn&l4GTWv=*Esh}-HopUGxdwpkV0&{aOtNE?f{Rzu2U zU)E1l0%I+_CkQU!X!da!PZCh0#V(}`Tjwe#d>vX}UyqaEpg*YX=XL1pBrR{Lt~@LE z@=BzQg~iEc<>bl)fYC<7m)YHsgLO3f+@Pj>WvqJNpssu()z{ik2LmK&p%ghExfLbUqens_W zXtP`vE%#$@QG9c202|NoG+HaeMBNeZLK=ZRzqT=FtBG?YqahX4RD$wD@bliqm2Tmjmd zmU`mh8lS`np=$A2^!>AZ1X0TG_0oX+X2!nr5#pyDr}7l$`{5TE1&ib m=fGu2OIp&Bmb9c}K>q`LcaZ2K9*T7U0000