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 68bb3925..00000000 Binary files a/src/main/resources/assets/replaymod/extended_gui.png and /dev/null differ diff --git a/src/main/resources/assets/replaymod/replay_gui.png b/src/main/resources/assets/replaymod/replay_gui.png index 051f365f..d690c3ce 100755 Binary files a/src/main/resources/assets/replaymod/replay_gui.png and b/src/main/resources/assets/replaymod/replay_gui.png differ 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 a3127b15..00000000 Binary files a/src/main/resources/assets/replaymod/timeline_icons.png and /dev/null differ