Renderer/src/main/java/electrosphere/controls/ControlHandler.java
austin 3a5aa301de
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good
move window pointer into rendering engine
2025-05-15 15:29:30 -04:00

575 lines
21 KiB
Java

package electrosphere.controls;
import static org.lwjgl.glfw.GLFW.GLFW_CURSOR;
import static org.lwjgl.glfw.GLFW.GLFW_CURSOR_DISABLED;
import static org.lwjgl.glfw.GLFW.GLFW_CURSOR_NORMAL;
import static org.lwjgl.glfw.GLFW.GLFW_MOUSE_BUTTON_1;
import static org.lwjgl.glfw.GLFW.GLFW_MOUSE_BUTTON_2;
import static org.lwjgl.glfw.GLFW.glfwGetCursorPos;
import static org.lwjgl.glfw.GLFW.glfwSetInputMode;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import org.joml.Vector2f;
import org.lwjgl.glfw.GLFW;
import electrosphere.client.entity.camera.CameraEntityUtils;
import electrosphere.client.ui.menu.WindowStrings;
import electrosphere.client.ui.menu.WindowUtils;
import electrosphere.controls.categories.ControlCategoryAlwaysOnDebug;
import electrosphere.controls.categories.ControlCategoryFreecam;
import electrosphere.controls.categories.ControlCategoryInGameDebug;
import electrosphere.controls.categories.ControlCategoryInventory;
import electrosphere.controls.categories.ControlCategoryMainGame;
import electrosphere.controls.categories.ControlCategoryMenuNav;
import electrosphere.controls.categories.ControlCategoryTyping;
import electrosphere.engine.Globals;
import electrosphere.entity.state.equip.ClientEquipState;
import electrosphere.renderer.ui.elements.Window;
import electrosphere.renderer.ui.events.MouseEvent;
import electrosphere.renderer.ui.events.ScrollEvent;
/**
* Main handler for controls
*/
public class ControlHandler {
/**
* The different buckets of inputs that the control handler be configured to scan for each frame
*/
public static enum ControlsState {
TITLE_PAGE,
TITLE_MENU,
MAIN_GAME,
IN_GAME_MAIN_MENU,
IN_GAME_FREE_CAMERA,
INVENTORY,
NO_INPUT,
}
//The bucket of inputs that the control handler is currently scanning for
ControlsState state = ControlsState.TITLE_MENU;
/**
* The state of the mouse
*/
MouseState mouseState = new MouseState();
/**
* The list of control states that have the mouse visible and enabled
*/
static ControlsState[] mouseEnabledStates = new ControlsState[]{
ControlsState.TITLE_PAGE,
ControlsState.TITLE_MENU,
ControlsState.IN_GAME_MAIN_MENU,
ControlsState.INVENTORY,
};
/**
* Controls whether the mouse is visible or not
*/
boolean mouseIsVisible = true;
/**
* If set to true, opengl will try to capture the screen next frame
*/
boolean shouldRecaptureScreen = false;
/**
* Controls whether the camera is first or third person
*/
boolean cameraIsThirdPerson = false;
/**
* The list of window strings that would block main game controls
*/
static String[] controlBlockingWindows = new String[]{
WindowStrings.LEVEL_EDTIOR_SIDE_PANEL,
WindowStrings.VOXEL_TYPE_SELECTION,
WindowStrings.SPAWN_TYPE_SELECTION,
WindowStrings.WINDOW_CHARACTER,
WindowStrings.WINDOW_INVENTORY_TARGET,
WindowStrings.WINDOW_DEBUG,
WindowStrings.WINDOW_MENU_INGAME_MAIN,
WindowStrings.WINDOW_MENU_INVENTORY,
WindowStrings.WINDOW_MENU_MAIN,
};
/**
* The map of control name (string) -> control (object)
*/
HashMap<String, Control> controls;
List<Control> mainGameControlList = new LinkedList<Control>();
List<Control> mainGameDebugControlList = new LinkedList<Control>();
List<Control> menuNavigationControlList = new LinkedList<Control>();
List<Control> typingControlList = new LinkedList<Control>();
List<Control> inventoryControlList = new LinkedList<Control>();
List<Control> alwaysOnDebugControlList = new LinkedList<Control>();
List<Control> freeCameraControlList = new LinkedList<Control>();
/**
* Constructor
*/
private ControlHandler(){
controls = new HashMap<String, Control>();
}
/**
* Generates an example controls map
* @return the example controls map object
*/
public static ControlHandler generateExampleControlsMap(){
ControlHandler handler = new ControlHandler();
ControlCategoryAlwaysOnDebug.mapControls(handler);
ControlCategoryMainGame.mapControls(handler);
ControlCategoryMenuNav.mapControls(handler);
ControlCategoryTyping.mapControls(handler);
ControlCategoryInventory.mapControls(handler);
ControlCategoryInGameDebug.mapControls(handler);
ControlCategoryFreecam.mapControls(handler);
/*
Save to file
*/
// Utilities.saveObjectToBakedJsonFile("/Config/keybinds.json", handler);
/*
return
*/
return handler;
}
/**
* Polls the currently set bucket of controls
*/
public void pollControls(){
switch(state){
case MAIN_GAME: {
this.runHandlers(mainGameControlList);
this.runHandlers(mainGameDebugControlList);
this.runHandlers(alwaysOnDebugControlList);
} break;
case TITLE_PAGE:
break;
case TITLE_MENU: {
this.runHandlers(menuNavigationControlList);
/*
Typing..
*/
this.runHandlers(typingControlList);
this.runHandlers(alwaysOnDebugControlList);
} break;
case IN_GAME_MAIN_MENU: {
this.runHandlers(menuNavigationControlList);
this.runHandlers(typingControlList);
this.runHandlers(alwaysOnDebugControlList);
// pollMenuNavigationControls();
} break;
case IN_GAME_FREE_CAMERA: {
this.runHandlers(freeCameraControlList);
this.runHandlers(mainGameDebugControlList);
this.runHandlers(alwaysOnDebugControlList);
} break;
case INVENTORY: {
this.runHandlers(inventoryControlList);
this.runHandlers(menuNavigationControlList);
this.runHandlers(alwaysOnDebugControlList);
} break;
case NO_INPUT: {
this.runHandlers(alwaysOnDebugControlList);
} break;
}
Globals.scrollCallback.clear();
}
/**
* Attaches callbacks to each of the control objects
*/
public void setCallbacks(){
ControlCategoryMainGame.setCallbacks(controls, mainGameControlList, inventoryControlList);
ControlCategoryInGameDebug.setCallbacks(controls, mainGameDebugControlList, alwaysOnDebugControlList);
ControlCategoryMenuNav.setCallbacks(controls, menuNavigationControlList, mainGameDebugControlList, inventoryControlList);
ControlCategoryTyping.setCallbacks(controls, typingControlList);
ControlCategoryInventory.setCallbacks(controls, inventoryControlList);
ControlCategoryAlwaysOnDebug.setCallbacks(controls, alwaysOnDebugControlList);
ControlCategoryFreecam.setCallbacks(controls, freeCameraControlList);
}
/**
* Gets the main controls list
* @return The main controls
*/
public List<Control> getMainControlsList(){
return mainGameControlList;
}
/**
* Checks a list of controls to see if the corresponding key/mouse event is firing this frame
* @param controls The list of controls to check
*/
private void runHandlers(List<Control> controls){
//Fills the buffer
this.getMousePositionInBuffer();
this.mouseState.setLastX((float)this.mouseState.getCurrentX());
this.mouseState.setLastY((float)this.mouseState.getCurrentY());
this.mouseState.setCurrentX(this.mouseState.getMouseBufferX()[0]);
this.mouseState.setCurrentY(this.mouseState.getMouseBufferY()[0]);
this.mouseState.setDeltaX(this.mouseState.getCurrentX() - this.mouseState.getLastX());
this.mouseState.setDeltaY(this.mouseState.getLastY() - this.mouseState.getCurrentY());
float xoffset = (float) (this.mouseState.getCurrentX() - this.mouseState.getLastX());
float yoffset = (float) (this.mouseState.getLastY() - this.mouseState.getCurrentY());
MouseEvent currentMouseEvent = new MouseEvent(
(int)this.mouseState.getCurrentX(),
(int)this.mouseState.getCurrentY(),
(int)this.mouseState.getLastX(),
(int)this.mouseState.getLastY(),
(int)xoffset,
(int)yoffset,
this.getButton1Raw(),
this.getButton2Raw()
);
boolean mouseMoveEvent = xoffset != 0 || yoffset != 0;
for(Control control : controls){
switch(control.getType()){
case KEY: {
if(Globals.controlCallback.getKey(control.getKeyValue())){
if(!control.isState()){
//on press
control.onPress(this.mouseState);
control.setPressFrame((float)Globals.engineState.timekeeper.getNumberOfRenderFramesElapsed());
} else {
//on repeat
if((float)Globals.engineState.timekeeper.getNumberOfRenderFramesElapsed() - control.getPressFrame() > control.getRepeatTimeout()){
control.onRepeat(this.mouseState);
}
}
control.setState(true);
} else {
if(control.isState()){
//on release
control.onRelease(this.mouseState);
//on click
if((float)Globals.engineState.timekeeper.getNumberOfRenderFramesElapsed() - control.getPressFrame() < control.getRepeatTimeout()){
control.onClick(this.mouseState);
}
} else {
}
control.setState(false);
}
} break;
case MOUSE_BUTTON: {
if(Globals.mouseCallback.getButton(control.getKeyValue())){
if(!control.isState()){
//on press
control.onPress(this.mouseState);
control.setPressFrame((float)Globals.engineState.timekeeper.getNumberOfRenderFramesElapsed());
} else {
//on repeat
control.onRepeat(this.mouseState);
}
control.setState(true);
} else {
if(control.isState()){
//on release
control.onRelease(this.mouseState);
if((float)Globals.engineState.timekeeper.getNumberOfRenderFramesElapsed() - control.getPressFrame() < control.getRepeatTimeout()){
control.onClick(this.mouseState);
}
} else {
}
control.setState(false);
}
} break;
case MOUSE_MOVEMENT: {
if(mouseMoveEvent){
control.onMove(this.mouseState,currentMouseEvent);
}
} break;
case MOUSE_SCROLL: {
double yScroll = Globals.scrollCallback.getOffsetY();
if(yScroll != 0){
ScrollEvent event = new ScrollEvent(this.mouseState.getCurrentX(),this.mouseState.getCurrentY(),yScroll);
control.onScroll(this.mouseState,event);
}
} break;
}
}
}
/**
* Checks if any menus are open that would intercept player input (main menu, inventory, debug, etc)
* @return true if such a menu is open, false otherwise
*/
private boolean hasControlBlockingMenuOpen(){
boolean rVal = false;
//check main ui framework windows
for(String windowString : controlBlockingWindows){
rVal = rVal || WindowUtils.windowIsOpen(windowString);
}
//check if any generated inventory windows are open
for(String windowId : Globals.elementService.getCurrentWindowIds()){
rVal = rVal || (WindowUtils.windowIsOpen(windowId) && WindowUtils.isInventoryWindow(windowId));
}
//check imgui windows
rVal = rVal || Globals.renderingEngine.getImGuiPipeline().shouldCaptureControls();
return rVal;
}
/**
* Checks if the only menus open are inventory menus
* @return true if only inventory menus AND a menu is open, false otherwise
*/
private boolean onlyInventoryMenusOpen(){
boolean foundInventory = false;
for(String windowId : Globals.elementService.getCurrentWindowIds()){
if(WindowUtils.isInventoryWindow(windowId) || WindowStrings.WINDOW_MENU_INVENTORY.equals(windowId) || WindowStrings.WINDOW_CHARACTER.equals(windowId) || WindowStrings.WINDOW_INVENTORY_TARGET.equals(windowId)){
foundInventory = true;
} else if(Globals.elementService.getWindow(windowId) instanceof Window == false || ((Window)Globals.elementService.getWindow(windowId)).visible) {
return false;
}
}
return foundInventory;
}
/**
* Hints to the engine that it should update the control state
* The provided control state will be overwritten if, for instance,
* there is a menu open that demands mouse input and you are trying
* to tell the engine to convert to immediate player control
* @param desiredState The desired control state
*/
public void hintUpdateControlState(ControlsState desiredState){
ControlsState properState = desiredState;
//correct for freecam or actual ingame control based on value of getTrackPlayerEntity
if(desiredState == ControlsState.IN_GAME_FREE_CAMERA && Globals.cameraHandler.getTrackPlayerEntity()){
properState = ControlsState.MAIN_GAME;
}
if(desiredState == ControlsState.MAIN_GAME && !Globals.cameraHandler.getTrackPlayerEntity()){
properState = ControlsState.IN_GAME_FREE_CAMERA;
}
//set to menu state if a menu is open, otherwise use the hinted control scheme
if(this.onlyInventoryMenusOpen()){
this.setHandlerState(ControlsState.INVENTORY);
} else if(this.hasControlBlockingMenuOpen()){
this.setHandlerState(ControlsState.IN_GAME_MAIN_MENU);
} else {
this.setHandlerState(properState);
}
//checks if the current handler state should have mouse enabled or not
if(Arrays.binarySearch(mouseEnabledStates,getHandlerState()) >= 0){
this.showMouse();
} else {
this.hideMouse();
}
}
/**
* Transfers the mouse position from the glfw buffer to variables stored inside the control handler
*/
private void getMousePositionInBuffer(){
//only if not headless, gather position
if(!Globals.HEADLESS){
glfwGetCursorPos(Globals.renderingEngine.getWindowPtr(), this.mouseState.getMouseBufferX(), this.mouseState.getMouseBufferY());
}
}
/**
* Checks if the mouse button 1 is currently pressed
* @return true if pressed, false otherwise
*/
private boolean getButton1Raw(){
if(Globals.HEADLESS){
return false;
} else {
return Globals.mouseCallback.getButton(GLFW_MOUSE_BUTTON_1);
}
}
/**
* Checks if the mouse button 2 is currently pressed
* @return true if pressed, false otherwise
*/
private boolean getButton2Raw(){
if(Globals.HEADLESS){
return false;
} else {
return Globals.mouseCallback.getButton(GLFW_MOUSE_BUTTON_2);
}
}
/**
* Gets a control
* @param controlName The name of the control
* @return The control if it exists, null otherwise
*/
public Control getControl(String controlName){
return controls.get(controlName);
}
/**
* Checks if the handler contains a control
* @param controlName The name of the control
* @return true if the control exists, false otherwise
*/
public boolean containsControl(String controlName){
return controls.containsKey(controlName);
}
/**
* Removes a control from the handler
* @param controlName The name of the control
*/
public void removeControl(String controlName){
controls.remove(controlName);
}
/**
* Adds a control to the handler
* @param controlName The name of the control
* @param c The control itself
*/
public void addControl(String controlName, Control c){
if(controls.containsKey(controlName)){
throw new Error("Control handler already contains control " + controlName + " " + c + " " + controls.get(controlName));
}
controls.put(controlName, c);
}
/**
* Sets the state of the controls handler
* @param state the state
*/
private void setHandlerState(ControlsState state){
this.state = state;
}
/**
* Gets the current state of the controls handler
* @return the state
*/
public ControlsState getHandlerState(){
return state;
}
/**
* Hides the mouse
*/
public void hideMouse(){
glfwSetInputMode(Globals.renderingEngine.getWindowPtr(), GLFW_CURSOR, GLFW_CURSOR_NORMAL);
glfwSetInputMode(Globals.renderingEngine.getWindowPtr(), GLFW_CURSOR, GLFW_CURSOR_DISABLED);
mouseIsVisible = false;
}
/**
* Shows the mouse
*/
public void showMouse(){
glfwSetInputMode(Globals.renderingEngine.getWindowPtr(), GLFW_CURSOR, GLFW_CURSOR_NORMAL);
mouseIsVisible = true;
}
/**
* Gets whether the mouse is visible or not
* @return true if visible, false otherwise
*/
public boolean isMouseVisible(){
return mouseIsVisible;
}
/**
* Gets the mouse position as a vector2f
* @return The vector containing the mouse position
*/
public Vector2f getMousePosition(){
double posX[] = new double[1];
double posY[] = new double[1];
GLFW.glfwGetCursorPos(Globals.renderingEngine.getWindowPtr(), posX, posY);
Vector2f rVal = new Vector2f((float)posX[0],(float)posY[0]);
return rVal;
}
/**
* Gets the mouse position as a vector2f
* @return The vector containing the mouse position
*/
public Vector2f getMousePositionNormalized(){
double posX[] = new double[1];
double posY[] = new double[1];
GLFW.glfwGetCursorPos(Globals.renderingEngine.getWindowPtr(), posX, posY);
int sizeX[] = new int[1];
int sizeY[] = new int[1];
GLFW.glfwGetWindowSize(Globals.renderingEngine.getWindowPtr(), sizeX, sizeY);
Vector2f rVal = new Vector2f((float)((2.0 * posX[0] / sizeX[0]) - 1.0),(float)(1.0 - (2.0 * posY[0] / sizeY[0])));
return rVal;
}
/**
* Sets whether the engine should try to recapture window focus next frame or not
* @param shouldRecapture true if should try to recapture next frame, false otherwise
*/
public void setRecapture(boolean shouldRecapture){
this.shouldRecaptureScreen = shouldRecapture;
}
/**
* Returns whether the engine should try to recapture window focus next frame or not
* @return true if it should try to recapture, false otherwise
*/
public boolean shouldRecapture(){
return this.shouldRecaptureScreen;
}
/**
* Checks if the camera is third person
* @return true if third person, false if first person
*/
public boolean cameraIsThirdPerson(){
return cameraIsThirdPerson;
}
/**
* Sets the 1st/3rd person status of the camera
* @param isThirdPerson True for 3rd person, false for 1st person
*/
public void setIsThirdPerson(boolean isThirdPerson){
this.cameraIsThirdPerson = isThirdPerson;
if(Globals.clientState.playerEntity != null){
CameraEntityUtils.initCamera();
ClientEquipState playerEquipState = ClientEquipState.getClientEquipState(Globals.clientState.playerEntity);
if(playerEquipState != null){
playerEquipState.evaluatePlayerAttachments();
}
}
}
}