Files
ReplayModCinematic/src/main/java/eu/crushedpixel/replaymod/gui/overlay/GuiReplayOverlay.java

634 lines
25 KiB
Java
Executable File

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.*;
import eu.crushedpixel.replaymod.gui.elements.timelines.GuiKeyframeTimeline;
import eu.crushedpixel.replaymod.gui.elements.timelines.GuiMarkerTimeline;
import eu.crushedpixel.replaymod.holders.AdvancedPosition;
import eu.crushedpixel.replaymod.holders.Keyframe;
import eu.crushedpixel.replaymod.holders.TimestampValue;
import eu.crushedpixel.replaymod.registry.KeybindRegistry;
import eu.crushedpixel.replaymod.registry.ReplayGuiRegistry;
import eu.crushedpixel.replaymod.replay.ReplayHandler;
import eu.crushedpixel.replaymod.utils.CameraPathValidator;
import eu.crushedpixel.replaymod.utils.MouseUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityOtherPlayerMP;
import net.minecraft.client.gui.Gui;
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.client.settings.KeyBinding;
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.Keyboard;
import org.lwjgl.opengl.Display;
import org.lwjgl.util.Point;
import java.awt.Color;
import java.io.IOException;
import java.util.List;
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;
public static final int KEYFRAME_TIMELINE_LENGTH = 30 * 60 * 1000;
public static GuiTexturedButton texturedButton(int x, int y, int u, int v, int size, Runnable action, String hoverText) {
return new GuiTexturedButton(0, x, y, size, size, replay_gui, u, v, TEXTURE_SIZE, TEXTURE_SIZE, action, I18n.format(hoverText));
}
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 GuiElement buttonPlayPause = new DelegatingElement() {
private final GuiElement buttonPlay = texturedButton(BUTTON_PLAY_PAUSE_X, TOP_ROW, 0, 0, 20, new Runnable() {
@Override
public void run() {
ReplayMod.replaySender.setReplaySpeed(speedSlider.getSliderValue());
}
}, "replaymod.gui.ingame.menu.unpause");
private final GuiElement buttonPause = texturedButton(BUTTON_PLAY_PAUSE_X, TOP_ROW, 0, 20, 20, new Runnable() {
@Override
public void run() {
ReplayMod.replaySender.setReplaySpeed(0);
}
}, "replaymod.gui.ingame.menu.pause");
@Override
public GuiElement delegate() {
return ReplayMod.replaySender.paused() ? buttonPlay : buttonPause;
}
};
private final GuiElement buttonExport = texturedButton(BUTTON_EXPORT_X, BOTTOM_ROW, 40, 0, 20, new Runnable() {
@Override
public void run() {
try {
CameraPathValidator.validateCameraPath(ReplayHandler.getPositionKeyframes(), ReplayHandler.getTimeKeyframes());
} catch(CameraPathValidator.InvalidCameraPathException e) {
e.printToChat();
return;
}
mc.displayGuiScreen(new GuiRenderSettings());
}
}, "replaymod.gui.ingame.menu.renderpath");
private final GuiElement buttonPlayPausePath = new DelegatingElement() {
private final GuiElement buttonPlay = texturedButton(BUTTON_PLAY_PATH_X, BOTTOM_ROW, 0, 0, 20, new Runnable() {
@Override
public void run() {
ReplayHandler.startPath(null);
}
}, "replaymod.gui.ingame.menu.playpath");
private final GuiElement buttonPause = texturedButton(BUTTON_PLAY_PATH_X, BOTTOM_ROW, 0, 20, 20, new Runnable() {
@Override
public void run() {
ReplayHandler.interruptReplay();
}
}, "replaymod.gui.ingame.menu.pausepath");
@Override
public GuiElement delegate() {
return ReplayHandler.isInPath() ? buttonPause : buttonPlay;
}
};
private final GuiElement buttonPlace = new DelegatingElement() {
private final GuiElement buttonNotSelected = texturedButton(BUTTON_PLACE_X, BOTTOM_ROW, 0, 40, 20, new Runnable() {
@Override
public void run() {
Entity cam = mc.getRenderViewEntity();
if (cam != null) {
AdvancedPosition position = new AdvancedPosition(cam.posX, cam.posY, cam.posZ, cam.rotationPitch,
cam.rotationYaw % 360, ReplayHandler.getCameraTilt(), null);
if (ReplayHandler.isCamera())
ReplayHandler.addKeyframe(new Keyframe<AdvancedPosition>(ReplayHandler.getRealTimelineCursor(), position));
else
ReplayHandler.addKeyframe(new Keyframe<AdvancedPosition>(ReplayHandler.getRealTimelineCursor(), new AdvancedPosition(cam.getEntityId(), true)));
}
}
}, "replaymod.gui.ingame.menu.addposkeyframe");
private final GuiElement buttonSelected = texturedButton(BUTTON_PLACE_X, BOTTOM_ROW, 0, 60, 20, new Runnable() {
@Override
public void run() {
ReplayHandler.removeKeyframe(ReplayHandler.getSelectedKeyframe());
}
}, "replaymod.gui.ingame.menu.removeposkeyframe");
private final GuiElement buttonSpectatorNotSelected = texturedButton(BUTTON_PLACE_X, BOTTOM_ROW, 40, 40, 20, new Runnable() {
@Override
public void run() {
Entity cam = mc.getRenderViewEntity();
if (cam != null) {
AdvancedPosition position = new AdvancedPosition(cam.posX, cam.posY, cam.posZ, cam.rotationPitch,
cam.rotationYaw % 360, ReplayHandler.getCameraTilt(), null);
if (ReplayHandler.isCamera())
ReplayHandler.addKeyframe(new Keyframe<AdvancedPosition>(ReplayHandler.getRealTimelineCursor(), position));
else
ReplayHandler.addKeyframe(new Keyframe<AdvancedPosition>(ReplayHandler.getRealTimelineCursor(), new AdvancedPosition(cam.getEntityId(), true)));
}
}
}, "replaymod.gui.ingame.menu.addspeckeyframe");
private final GuiElement buttonSpectatorSelected = texturedButton(BUTTON_PLACE_X, BOTTOM_ROW, 40, 60, 20, new Runnable() {
@Override
public void run() {
ReplayHandler.removeKeyframe(ReplayHandler.getSelectedKeyframe());
}
}, "replaymod.gui.ingame.menu.removespeckeyframe");
@Override
public GuiElement delegate() {
boolean selected = ReplayHandler.getSelectedKeyframe() != null && ReplayHandler.getSelectedKeyframe().getValue() instanceof AdvancedPosition;
boolean camera;
if(selected) {
camera = ((AdvancedPosition)ReplayHandler.getSelectedKeyframe().getValue()).getSpectatedEntityID() == null;
} else {
camera = ReplayHandler.isCamera();
}
if(camera) {
return selected ? buttonSelected : buttonNotSelected;
} else {
return selected ? buttonSpectatorSelected : buttonSpectatorNotSelected;
}
}
};
private final GuiElement buttonTime = new DelegatingElement() {
private final GuiElement buttonNotSelected = texturedButton(BUTTON_TIME_X, BOTTOM_ROW, 0, 80, 20, new Runnable() {
@Override
public void run() {
ReplayHandler.addKeyframe(new Keyframe<TimestampValue>(ReplayHandler.getRealTimelineCursor(), new TimestampValue(ReplayMod.replaySender.currentTimeStamp())));
}
}, "replaymod.gui.ingame.menu.addtimekeyframe");
private final GuiElement buttonSelected = texturedButton(BUTTON_TIME_X, BOTTOM_ROW, 0, 100, 20, new Runnable() {
@Override
public void run() {
ReplayHandler.removeKeyframe(ReplayHandler.getSelectedKeyframe());
}
}, "replaymod.gui.ingame.menu.removetimekeyframe");
@Override
public GuiElement delegate() {
return ReplayHandler.getSelectedKeyframe() != null && ReplayHandler.getSelectedKeyframe().getValue() instanceof TimestampValue ? buttonSelected : buttonNotSelected;
}
};
private final GuiElement buttonZoomIn = texturedButton(WIDTH - 14 - 9, BOTTOM_ROW, 40, 20, 9, new Runnable() {
@Override
public void run() {
timelineReal.zoomIn();
}
}, "replaymod.gui.ingame.menu.zoomin");
private final GuiElement buttonZoomOut = texturedButton(WIDTH - 14 - 9, BOTTOM_ROW + 11, 40, 30, 9, new Runnable() {
@Override
public void run() {
timelineReal.zoomOut();
}
}, "replaymod.gui.ingame.menu.zoomout");
private final GuiMarkerTimeline timeline = new GuiMarkerTimeline(TIMELINE_X, TOP_ROW - 1, WIDTH - 14 - TIMELINE_X, 22, false);
private final GuiKeyframeTimeline timelineReal = new GuiKeyframeTimeline(TIMELINE_REAL_X, BOTTOM_ROW - 1, TIMELINE_REAL_WIDTH, 22, true, true, true);
{
timelineReal.timelineLength = KEYFRAME_TIMELINE_LENGTH;
}
private final GuiScrollbar scrollbar = new GuiScrollbar(TIMELINE_REAL_X, BOTTOM_ROW + 22, TIMELINE_REAL_WIDTH) {
@Override
public void dragged() {
timelineReal.timeStart = scrollbar.sliderPosition;
}
};
private final GuiReplaySpeedSlider speedSlider = new GuiReplaySpeedSlider(SPEED_X, TOP_ROW, I18n.format("replaymod.gui.speed"));
public double getSpeedSliderValue() {
return speedSlider.getSliderValue();
}
private boolean toolbarOpen = false;
private final DelegatingElement toolbar = new DelegatingElement() {
private void toggleOpen() {
toolbarOpen = !toolbarOpen;
}
private GuiElement buttonOpenToolbar = new GuiArrowButton(-1, 10, HEIGHT-10-20, "", GuiArrowButton.Direction.UP) {
@Override
public void performAction() {
toggleOpen();
}
};
private GuiElement buttonCloseToolbar = new GuiArrowButton(-1, 10, HEIGHT-10-20, "", GuiArrowButton.Direction.DOWN) {
@Override
public void performAction() {
toggleOpen();
}
};
private ComposedElement openElements = new ComposedElement(buttonCloseToolbar);
private int maxButtonY = -1;
{
int buttonX = 10;
int maxWidth = 0;
int i = 0;
for(final KeyBinding kb : KeybindRegistry.getReplayModKeyBindings()) {
int buttonY = HEIGHT-55-(i*25);
if(buttonY < 80) {
buttonX += 25 + maxWidth + 5;
maxWidth = 0;
i = 0;
buttonY = HEIGHT-55-(i*25);
}
if(buttonY < maxButtonY || maxButtonY == -1) maxButtonY = buttonY;
GuiElement button = new GuiAdvancedButton(buttonX, buttonY, 20, 20, Keyboard.getKeyName(kb.getKeyCode()), new Runnable() {
@Override
public void run() {
ReplayMod.keyInputHandler.handleCustomKeybindings(kb, false, kb.getKeyCode());
}
}, null);
String stringText = I18n.format(kb.getKeyDescription());
int stringWidth = mc.fontRendererObj.getStringWidth(stringText);
if(stringWidth > maxWidth) {
maxWidth = stringWidth;
}
GuiString string = new GuiString(buttonX+25, buttonY+6, Color.WHITE, stringText);
openElements.addPart(button);
openElements.addPart(string);
i++;
}
}
@Override
public GuiElement delegate() {
if(toolbarOpen) {
return openElements;
} else {
return buttonOpenToolbar;
}
}
@Override
public void draw(Minecraft mc, int mouseX, int mouseY) {
if(toolbarOpen) {
drawGradientRect(0, maxButtonY - 10, WIDTH, HEIGHT, -1072689136, -804253680);
}
super.draw(mc, mouseX, mouseY);
}
};
public void closeToolbar() {
toolbarOpen = false;
}
private final GuiElement content = new ComposedElement(buttonPlayPause, buttonExport, buttonPlace, buttonTime,
buttonPlayPausePath, buttonZoomIn, buttonZoomOut, timeline, timelineReal, scrollbar, speedSlider, toolbar);
public boolean isVisible() {
return ReplayHandler.isInReplay();
}
/**
* Resets the UI.
* @param resetElements Whether the timeline and Speed Slider should be reset as well
*/
public void resetUI(boolean resetElements) {
if(FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) {
mc.displayGuiScreen(null);
}
if (resetElements) {
timelineReal.zoom = 0.033f;
timelineReal.timeStart = 0;
ReplayHandler.setRealTimelineCursor(0);
speedSlider.reset();
}
}
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.timelineReal.zoom = this.timelineReal.zoom;
other.timelineReal.timeStart = this.timelineReal.timeStart;
other.speedSlider.copyValueFrom(this.speedSlider);
this.unregister();
other.register();
ReplayMod.overlay = other;
if (mc.currentScreen instanceof GuiMouseInput) {
mc.displayGuiScreen(new GuiMouseInput(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 (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, int button) {
content.mouseDrag(mc, mouseX, mouseY, button);
}
public void mouseReleased(int mouseX, int mouseY, int button) {
content.mouseRelease(mc, mouseX, mouseY, button);
}
public void mouseClicked(int mouseX, int mouseY, int button) {
if (ReplayHandler.isInPath()) { // Only allow clicking of cancel button during path replay
buttonPlayPausePath.mouseClick(mc, mouseX, mouseY, button);
} else {
content.mouseClick(mc, mouseX, mouseY, button);
}
}
public void performJump(long timelineTime) {
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 AdvancedPosition(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);
mc.getNetHandler().getNetworkManager().processReceivedPackets();
@SuppressWarnings("unchecked")
List<Entity> entities = (List<Entity>) mc.theWorld.loadedEntityList;
for (Entity entity : entities) {
if (entity instanceof EntityOtherPlayerMP) {
EntityOtherPlayerMP e = (EntityOtherPlayerMP) entity;
e.setPosition(e.otherPlayerMPX, e.otherPlayerMPY, e.otherPlayerMPZ);
e.rotationYaw = (float) e.otherPlayerMPYaw;
e.rotationPitch = (float) e.otherPlayerMPPitch;
}
entity.lastTickPosX = entity.prevPosX = entity.posX;
entity.lastTickPosY = entity.prevPosY = entity.posY;
entity.lastTickPosZ = entity.prevPosZ = entity.posZ;
entity.prevRotationYaw = entity.rotationYaw;
entity.prevRotationPitch = entity.rotationPitch;
}
try {
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 (event.type != RenderGameOverlayEvent.ElementType.ALL) return;
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();
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;
}
// Setup scrollbar and timelines
scrollbar.size = timelineReal.zoom;
scrollbar.sliderPosition = timelineReal.timeStart;
timeline.cursorPosition = ReplayMod.replaySender.currentTimeStamp();
timeline.timelineLength = ReplayMod.replaySender.replayLength();
timelineReal.cursorPosition = ReplayHandler.getRealTimelineCursor();
// Draw all elements
content.draw(mc, mouseX, mouseY);
content.drawOverlay(mc, mouseX, mouseY);
GlStateManager.enableBlend();
}
public void togglePlayPause() {
if (ReplayMod.replaySender.paused()) {
ReplayMod.replaySender.setReplaySpeed(speedSlider.getSliderValue());
} else {
ReplayMod.replaySender.setReplaySpeed(0);
}
}
/**
* Render the Ambient Lighting and Path Preview indicators in the lower right corner.
* @param event Rendered post game overlay
*/
@SubscribeEvent
public void renderIndicators(RenderGameOverlayEvent.Post event) {
if (event.type != RenderGameOverlayEvent.ElementType.ALL) return;
if(!ReplayHandler.isInReplay() || mc.currentScreen instanceof NoOverlay) return;
int xPos = WIDTH-10;
if(ReplayMod.replaySettings.isLightingEnabled()) {
int width = 19;
mc.renderEngine.bindTexture(replay_gui);
GlStateManager.color(1, 1, 1, 1);
GlStateManager.enableAlpha();
GlStateManager.disableLighting();
Gui.drawModalRectWithCustomSizedTexture(xPos-width, HEIGHT - 10 - 13,
90, 20, 19, 13, TEXTURE_SIZE, TEXTURE_SIZE);
xPos -= width + 5;
}
if(ReplayMod.replaySettings.showPathPreview()) {
int width = 20;
mc.renderEngine.bindTexture(replay_gui);
GlStateManager.color(1, 1, 1, 1);
GlStateManager.enableAlpha();
GlStateManager.disableLighting();
Gui.drawModalRectWithCustomSizedTexture(xPos - width, HEIGHT - 10 - 13,
100, 0, 20, 13, TEXTURE_SIZE, TEXTURE_SIZE);
//noinspection UnusedAssignment
xPos -= width + 5;
}
//can be continued
}
/**
* Dummy interface for GUI on which this replay overlay shall not be rendered.
*/
public interface NoOverlay {
}
}