diff options
Diffstat (limited to 'src/Input.c')
-rw-r--r-- | src/Input.c | 1467 |
1 files changed, 1467 insertions, 0 deletions
diff --git a/src/Input.c b/src/Input.c new file mode 100644 index 0000000..0f6bc80 --- /dev/null +++ b/src/Input.c @@ -0,0 +1,1467 @@ +#include "Input.h" +#include "String.h" +#include "Event.h" +#include "Funcs.h" +#include "Options.h" +#include "Logger.h" +#include "Platform.h" +#include "Chat.h" +#include "Utils.h" +#include "Server.h" +#include "HeldBlockRenderer.h" +#include "Game.h" +#include "ExtMath.h" +#include "Camera.h" +#include "Inventory.h" +#include "World.h" +#include "Event.h" +#include "Window.h" +#include "Entity.h" +#include "Screens.h" +#include "Block.h" +#include "Menus.h" +#include "Gui.h" +#include "Protocol.h" +#include "AxisLinesRenderer.h" +#include "Picking.h" + +struct _InputState Input; +static cc_bool input_buttonsDown[3]; +static int input_pickingId = -1; +static double input_lastClick; +static float input_fovIndex = -1.0f; +#ifdef CC_BUILD_WEB +static cc_bool suppressEscape; +#endif +enum MouseButton_ { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE }; +/* Raises PointerEvents.Up or PointerEvents.Down */ +static void Pointer_SetPressed(int idx, cc_bool pressed); + + +/*########################################################################################################################* +*------------------------------------------------------Touch support------------------------------------------------------* +*#########################################################################################################################*/ +#ifdef CC_BUILD_TOUCH +static struct TouchPointer { + long id; + cc_uint8 type; + int begX, begY; + double start; +} touches[INPUT_MAX_POINTERS]; + +int Pointers_Count; +int Input_TapMode = INPUT_MODE_PLACE; +int Input_HoldMode = INPUT_MODE_DELETE; +cc_bool Input_TouchMode; + +static void MouseStatePress(int button); +static void MouseStateRelease(int button); + +static cc_bool AnyBlockTouches(void) { + int i; + for (i = 0; i < Pointers_Count; i++) { + if (!(touches[i].type & TOUCH_TYPE_BLOCKS)) continue; + + /* Touch might be an 'all' type - remove 'gui' type */ + touches[i].type &= TOUCH_TYPE_BLOCKS | TOUCH_TYPE_CAMERA; + return true; + } + return false; +} + +static void ClearTouches(void) { + int i; + for (i = 0; i < INPUT_MAX_POINTERS; i++) touches[i].type = 0; + Pointers_Count = Input_TouchMode ? 0 : 1; +} + +void Input_SetTouchMode(cc_bool enabled) { + Input_TouchMode = enabled; + ClearTouches(); +} + +static cc_bool MovedFromBeg(int i, int x, int y) { + return Math_AbsI(x - touches[i].begX) > Display_ScaleX(5) || + Math_AbsI(y - touches[i].begY) > Display_ScaleY(5); +} + +static cc_bool TryUpdateTouch(long id, int x, int y) { + int i; + for (i = 0; i < Pointers_Count; i++) { + if (touches[i].id != id || !touches[i].type) continue; + + if (Input.RawMode && (touches[i].type & TOUCH_TYPE_CAMERA)) { + /* If the pointer hasn't been locked to gui or block yet, moving a bit */ + /* should cause the pointer to get locked to camera movement. */ + if (touches[i].type == TOUCH_TYPE_ALL && MovedFromBeg(i, x, y)) { + /* Allow a little bit of leeway because though, because devices */ + /* might still report a few pixels of movement depending on how */ + /* user is holding the finger down on the touch surface */ + if (touches[i].type == TOUCH_TYPE_ALL) touches[i].type = TOUCH_TYPE_CAMERA; + } + Event_RaiseRawMove(&PointerEvents.RawMoved, x - Pointers[i].x, y - Pointers[i].y); + } + Pointer_SetPosition(i, x, y); + return true; + } + return false; +} + +void Input_AddTouch(long id, int x, int y) { + int i; + /* Check if already existing pointer with same ID */ + if (TryUpdateTouch(id, x, y)) return; + + for (i = 0; i < INPUT_MAX_POINTERS; i++) { + if (touches[i].type) continue; + + touches[i].id = id; + touches[i].type = TOUCH_TYPE_ALL; + touches[i].begX = x; + touches[i].begY = y; + + touches[i].start = Game.Time; + /* Also set last click time, otherwise quickly tapping */ + /* sometimes triggers a 'delete' in InputHandler_Tick, */ + /* and then another 'delete' in CheckBlockTap. */ + input_lastClick = Game.Time; + + if (i == Pointers_Count) Pointers_Count++; + Pointer_SetPosition(i, x, y); + Pointer_SetPressed(i, true); + return; + } +} +void Input_UpdateTouch(long id, int x, int y) { TryUpdateTouch(id, x, y); } + +/* Quickly tapping should trigger a block place/delete */ +static void CheckBlockTap(int i) { + int btn, pressed; + if (Game.Time > touches[i].start + 0.25) return; + if (touches[i].type != TOUCH_TYPE_ALL) return; + + if (Input_TapMode == INPUT_MODE_PLACE) { + btn = MOUSE_RIGHT; + } else if (Input_TapMode == INPUT_MODE_DELETE) { + btn = MOUSE_LEFT; + } else { return; } + + pressed = input_buttonsDown[btn]; + MouseStatePress(btn); + + if (btn == MOUSE_LEFT) { + InputHandler_DeleteBlock(); + } else { + InputHandler_PlaceBlock(); + } + if (!pressed) MouseStateRelease(btn); +} + +void Input_RemoveTouch(long id, int x, int y) { + int i; + for (i = 0; i < Pointers_Count; i++) { + if (touches[i].id != id || !touches[i].type) continue; + + Pointer_SetPosition(i, x, y); + Pointer_SetPressed(i, false); + + /* found the touch, remove it */ + Pointer_SetPosition(i, -100000, -100000); + touches[i].type = 0; + + if ((i + 1) == Pointers_Count) Pointers_Count--; + return; + } +} +#else +static void ClearTouches(void) { } +#endif + + +/*########################################################################################################################* +*-----------------------------------------------------------Key-----------------------------------------------------------* +*#########################################################################################################################*/ +#define Key_Function_Names \ +"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10",\ +"F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "F19", "F20",\ +"F21", "F22", "F23", "F24" +#define Key_Ascii_Names \ +"A", "B", "C", "D", "E", "F", "G", "H", "I", "J",\ +"K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",\ +"U", "V", "W", "X", "Y", "Z" +#define Pad_Names \ +"PAD_A", "PAD_B", "PAD_X", "PAD_Y", "PAD_L", "PAD_R", \ +"PAD_Z", "PAD_C", "PAD_D", \ +"PAD_LEFT", "PAD_RIGHT", "PAD_UP", "PAD_DOWN", \ +"PAD_START", "PAD_SELECT", "PAD_ZL", "PAD_ZR", \ +"PAD_LSTICK", "PAD_RSTICK", \ +"PAD_CLEFT", "PAD_CRIGHT", "PAD_CUP", "PAD_CDOWN" + +/* Names for each input button when stored to disc */ +static const char* const storageNames[INPUT_COUNT] = { + "None", + Key_Function_Names, + "Tilde", "Minus", "Plus", "BracketLeft", "BracketRight", "Slash", + "Semicolon", "Quote", "Comma", "Period", "BackSlash", + "ShiftLeft", "ShiftRight", "ControlLeft", "ControlRight", + "AltLeft", "AltRight", "WinLeft", "WinRight", + "Up", "Down", "Left", "Right", + "Number0", "Number1", "Number2", "Number3", "Number4", + "Number5", "Number6", "Number7", "Number8", "Number9", + "Insert", "Delete", "Home", "End", "PageUp", "PageDown", + "Menu", + Key_Ascii_Names, + "Enter", "Escape", "Space", "BackSpace", "Tab", "CapsLock", + "ScrollLock", "PrintScreen", "Pause", "NumLock", + "Keypad0", "Keypad1", "Keypad2", "Keypad3", "Keypad4", + "Keypad5", "Keypad6", "Keypad7", "Keypad8", "Keypad9", + "KeypadDivide", "KeypadMultiply", "KeypadSubtract", + "KeypadAdd", "KeypadDecimal", "KeypadEnter", + + "XButton1", "XButton2", "LeftMouse", "RightMouse", "MiddleMouse", + "WheelUp", "WheelDown", "WheelLeft", "WheelRight", + "XButton3", "XButton4", "XButton5", "XButton6", + + "VolumeMute", "VolumeUp", "VolumeDown", "Sleep", + "MediaNext", "MediaPrev", "MediaPlay", "MediaStop", + "BrowserPrev", "BrowserNext", "BrowserRefresh", "BrowserStop", "BrowserSsearch", "BrowserFavorites", "BrowserHome", + "LaunchMail", "LaunchMedia", "LaunchApp1", "LaunchCalc", + + Pad_Names +}; + +const char* const Input_DisplayNames[INPUT_COUNT] = { + "NONE", + Key_Function_Names, + "GRAVE", "MINUS", "PLUS", "LBRACKET", "RBRACKET", "SLASH", + "SEMICOLON", "APOSTROPHE", "COMMA", "PERIOD", "BACKSLASH", + "LSHIFT", "RSHIFT", "LCONTROL", "RCONTROL", + "LALT", "RALT", "LWIN", "RWIN", + "UP", "DOWN", "LEFT", "RIGHT", + "0", "1", "2", "3", "4", + "5", "6", "7", "8", "9", + "INSERT", "DELETE", "HOME", "END", "PRIOR", "DOWN", + "MENU", + Key_Ascii_Names, + "RETURN", "ESCAPE", "SPACE", "BACK", "TAB", "CAPITAL", + "SCROLL", "PRINT", "PAUSE", "NUMLOCK", + "NUMPAD0", "NUMPAD1", "NUMPAD2", "NUMPAD3", "NUMPAD4", + "NUMPAD5", "NUMPAD6", "NUMPAD7", "NUMPAD8", "NUMPAD9", + "DIVIDE", "MULTIPLY", "SUBTRACT", + "ADD", "DECIMAL", "NUMPADENTER", + + "XBUTTON1", "XBUTTON2", "LMOUSE", "RMOUSE", "MMOUSE", + "WHEELUP", "WHEELDOWN", "WHEELLEFT", "WHEELRIGHT", + "XBUTTON3", "XBUTTON4", "XBUTTON5", "XBUTTON6", + + "VOLUMEMUTE", "VOLUMEUP", "VOLUMEDOWN", "SLEEP", + "MEDIANEXT", "MEDIAPREV", "MEDIAPLAY", "MEDIASTOP", + "BROWSERPREV", "BROWSERNEXT", "BROWSERREFRESH", "BROWSERSTOP", "BROWSERSEARCH", "BROWSERFAVORITES", "BROWSERHOME", + "LAUNCHMAIL", "LAUNCHMEDIA", "LAUNCHAPP1", "LAUNCHCALC", + + Pad_Names +}; + +void Input_SetPressed(int key) { + cc_bool wasPressed = Input.Pressed[key]; + Input.Pressed[key] = true; + Event_RaiseInput(&InputEvents.Down, key, wasPressed); + + if (key == 'C' && Input_IsActionPressed()) Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_COPY, 0); + if (key == 'V' && Input_IsActionPressed()) Event_RaiseInput(&InputEvents.Down, INPUT_CLIPBOARD_PASTE, 0); + + /* don't allow multiple left mouse down events */ + if (key != CCMOUSE_L || wasPressed) return; + Pointer_SetPressed(0, true); +} + +void Input_SetReleased(int key) { + if (!Input.Pressed[key]) return; + Input.Pressed[key] = false; + + Event_RaiseInt(&InputEvents.Up, key); + if (key == CCMOUSE_L) Pointer_SetPressed(0, false); +} + +void Input_Set(int key, int pressed) { + if (pressed) { + Input_SetPressed(key); + } else { + Input_SetReleased(key); + } +} + +void Input_SetNonRepeatable(int key, int pressed) { + if (pressed) { + if (Input.Pressed[key]) return; + Input_SetPressed(key); + } else { + Input_SetReleased(key); + } +} + +void Input_Clear(void) { + int i; + for (i = 0; i < INPUT_COUNT; i++) + { + if (Input.Pressed[i]) Input_SetReleased(i); + } + /* TODO: Properly release instead of just clearing */ + ClearTouches(); +} + +int Input_CalcDelta(int key, int horDelta, int verDelta) { + if (Input_IsLeftButton(key) || key == CCKEY_KP4) return -horDelta; + if (Input_IsRightButton(key) || key == CCKEY_KP6) return +horDelta; + if (Input_IsUpButton(key) || key == CCKEY_KP8) return -verDelta; + if (Input_IsDownButton(key) || key == CCKEY_KP2) return +verDelta; + + return 0; +} + + +/*########################################################################################################################* +*----------------------------------------------------------Mouse----------------------------------------------------------* +*#########################################################################################################################*/ +struct Pointer Pointers[INPUT_MAX_POINTERS]; + +void Pointer_SetPressed(int idx, cc_bool pressed) { + if (pressed) { + Event_RaiseInt(&PointerEvents.Down, idx); + } else { + Event_RaiseInt(&PointerEvents.Up, idx); + } +} + +static float scrollingVAcc; +void Mouse_ScrollVWheel(float delta) { + int steps = Utils_AccumulateWheelDelta(&scrollingVAcc, delta); + Event_RaiseFloat(&InputEvents.Wheel, delta); + + if (steps > 0) { + for (; steps != 0; steps--) + Input_SetPressed(CCWHEEL_UP); + Input_SetReleased(CCWHEEL_UP); + } else if (steps < 0) { + for (; steps != 0; steps++) + Input_SetPressed(CCWHEEL_DOWN); + Input_SetReleased(CCWHEEL_DOWN); + } +} + +static float scrollingHAcc; +void Mouse_ScrollHWheel(float delta) { + int steps = Utils_AccumulateWheelDelta(&scrollingHAcc, delta); + + if (steps > 0) { + for (; steps != 0; steps--) + Input_SetPressed(CCWHEEL_RIGHT); + Input_SetReleased(CCWHEEL_RIGHT); + } else if (steps < 0) { + for (; steps != 0; steps++) + Input_SetPressed(CCWHEEL_LEFT); + Input_SetReleased(CCWHEEL_LEFT); + } +} + +void Pointer_SetPosition(int idx, int x, int y) { + if (x == Pointers[idx].x && y == Pointers[idx].y) return; + /* TODO: reset to -1, -1 when pointer is removed */ + Pointers[idx].x = x; Pointers[idx].y = y; + +#ifdef CC_BUILD_TOUCH + if (Input_TouchMode && !(touches[idx].type & TOUCH_TYPE_GUI)) return; +#endif + Event_RaiseInt(&PointerEvents.Moved, idx); +} + + +/*########################################################################################################################* +*---------------------------------------------------------Keybinds--------------------------------------------------------* +*#########################################################################################################################*/ +BindMapping PadBind_Mappings[BIND_COUNT]; +BindMapping KeyBind_Mappings[BIND_COUNT]; +BindTriggered Bind_OnTriggered[BIND_COUNT]; +BindReleased Bind_OnReleased[BIND_COUNT]; + +const BindMapping PadBind_Defaults[BIND_COUNT] = { + { CCPAD_UP, 0 }, { CCPAD_DOWN, 0 }, /* BIND_FORWARD, BIND_BACK */ + { CCPAD_LEFT, 0 }, { CCPAD_RIGHT, 0 }, /* BIND_LEFT, BIND_RIGHT */ + { CCPAD_A, 0 }, { 0, 0 }, /* BIND_JUMP, BIND_RESPAWN */ + { CCPAD_START, 0 }, { CCPAD_Y, 0 }, /* BIND_SET_SPAWN, BIND_CHAT */ + { CCPAD_X, 0 }, { 0, 0 }, /* BIND_INVENTORY, BIND_FOG */ + { CCPAD_START, 0 }, { 0, 0 }, /* BIND_SEND_CHAT, BIND_TABLIST */ + { CCPAD_B, CCPAD_L},{ CCPAD_B, CCPAD_X},/* BIND_SPEED, BIND_NOCLIP */ + { CCPAD_B, CCPAD_R }, /* BIND_FLY */ + {CCPAD_B,CCPAD_UP},{CCPAD_B,CCPAD_DOWN},/* BIND_FLY_UP, BIND_FLY_DOWN */ + { 0, 0 }, { 0, 0 }, /* BIND_EXT_INPUT, BIND_HIDE_FPS */ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, /* BIND_SCREENSHOT, BIND_FULLSCREEN, BIND_THIRD_PERSON, BIND_HIDE_GUI */ + { 0, 0 }, { 0, 0 }, { 0, 0 }, /* BIND_AXIS_LINES, BIND_ZOOM_SCROLL, BIND_HALF_SPEED */ + { CCPAD_L, 0 }, { 0, 0 },{ CCPAD_R, 0 },/* BIND_DELETE_BLOCK, BIND_PICK_BLOCK, BIND_PLACE_BLOCK */ + { 0, 0 }, { 0, 0 }, { 0, 0 }, /* BIND_AUTOROTATE, BIND_HOTBAR_SWITCH, BIND_SMOOTH_CAMERA */ + { 0, 0 }, { 0, 0 }, { 0, 0 }, /* BIND_DROP_BLOCK, BIND_IDOVERLAY, BIND_BREAK_LIQUIDS */ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, /* BIND_LOOK_UP, BIND_LOOK_DOWN, BIND_LOOK_RIGHT, BIND_LOOK_LEFT */ + { 0, 0 }, { 0, 0 }, { 0, 0 }, /* BIND_HOTBAR_1, BIND_HOTBAR_2, BIND_HOTBAR_3 */ + { 0, 0 }, { 0, 0 }, { 0, 0 }, /* BIND_HOTBAR_4, BIND_HOTBAR_5, BIND_HOTBAR_6 */ + { 0, 0 }, { 0, 0 }, { 0, 0 }, /* BIND_HOTBAR_7, BIND_HOTBAR_8, BIND_HOTBAR_9 */ + { CCPAD_ZL, 0 }, { CCPAD_ZR, 0 } /* BIND_HOTBAR_LEFT, BIND_HOTBAR_RIGHT */ +}; + +const BindMapping KeyBind_Defaults[BIND_COUNT] = { + { 'W', 0 }, { 'S', 0 }, { 'A', 0 }, { 'D', 0 }, /* BIND_FORWARD - BIND_RIGHT */ + { CCKEY_SPACE, 0 }, { 'R', 0 }, /* BIND_JUMP, BIND_RESPAWN */ + { CCKEY_ENTER, 0 }, { 'T', 0 }, /* BIND_SET_SPAWN, BIND_CHAT */ + { 'B', 0 }, { 'F', 0 }, /* BIND_INVENTORY, BIND_FOG */ + { CCKEY_ENTER, 0 }, { CCKEY_TAB, 0 }, /* BIND_SEND_CHAT, BIND_TABLIST */ + { CCKEY_LSHIFT, 0 }, { 'X', 0}, { 'Z', 0 }, /* BIND_SPEED, BIND_NOCLIP, BIND_FLY */ + { 'Q', 0 }, { 'E', 0 }, /* BIND_FLY_UP, BIND_FLY_DOWN */ + { CCKEY_LALT, 0 }, { CCKEY_F3, 0 }, /* BIND_EXT_INPUT, BIND_HIDE_FPS */ + { CCKEY_F12, 0 }, { CCKEY_F11, 0 }, /* BIND_SCREENSHOT, BIND_FULLSCREEN */ + { CCKEY_F5, 0 }, { CCKEY_F1, 0 }, /* BIND_THIRD_PERSON, BIND_HIDE_GUI */ + { CCKEY_F7, 0 }, { 'C', 0 }, { CCKEY_LCTRL, 0 },/* BIND_AXIS_LINES, BIND_ZOOM_SCROLL, BIND_HALF_SPEED */ + { CCMOUSE_L, 0},{ CCMOUSE_M, 0},{ CCMOUSE_R, 0},/* BIND_DELETE_BLOCK, BIND_PICK_BLOCK, BIND_PLACE_BLOCK */ + { CCKEY_F6, 0 }, { CCKEY_LALT, 0 }, /* BIND_AUTOROTATE, BIND_HOTBAR_SWITCH */ + { CCKEY_F8, 0 }, { 'G', 0 }, /* BIND_SMOOTH_CAMERA, BIND_DROP_BLOCK */ + { CCKEY_F10, 0 }, { 0, 0 }, /* BIND_IDOVERLAY, BIND_BREAK_LIQUIDS */ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, /* BIND_LOOK_UP, BIND_LOOK_DOWN, BIND_LOOK_RIGHT, BIND_LOOK_LEFT */ + { '1', 0 }, { '2', 0 }, { '3', 0 }, /* BIND_HOTBAR_1, BIND_HOTBAR_2, BIND_HOTBAR_3 */ + { '4', 0 }, { '5', 0 }, { '6', 0 }, /* BIND_HOTBAR_4, BIND_HOTBAR_5, BIND_HOTBAR_6 */ + { '7', 0 }, { '8', 0 }, { '9', 0 }, /* BIND_HOTBAR_7, BIND_HOTBAR_8, BIND_HOTBAR_9 */ + { 0, 0 }, { 0, 0 } /* BIND_HOTBAR_LEFT, BIND_HOTBAR_RIGHT */ +}; + +static const char* const bindNames[BIND_COUNT] = { + "Forward", "Back", "Left", "Right", + "Jump", "Respawn", "SetSpawn", "Chat", "Inventory", + "ToggleFog", "SendChat", "PlayerList", + "Speed", "NoClip", "Fly", "FlyUp", "FlyDown", + "ExtInput", "HideFPS", "Screenshot", "Fullscreen", + "ThirdPerson", "HideGUI", "AxisLines", "ZoomScrolling", + "HalfSpeed", "DeleteBlock", "PickBlock", "PlaceBlock", + "AutoRotate", "HotbarSwitching", "SmoothCamera", + "DropBlock", "IDOverlay", "BreakableLiquids", + "LookUp", "LookDown", "LookRight", "LookLeft", + "Hotbar1", "Hotbar2", "Hotbar3", + "Hotbar4", "Hotbar5", "Horbar6", + "Hotbar7", "Hotbar8", "Hotbar9", + "HotbarLeft", "HotbarRight" +}; + + +#define BindMapping2_Claims(mapping, btn) (Input.Pressed[(mapping)->button1] && (mapping)->button2 == btn) +static cc_bool Mappings_DoesClaim(InputBind binding, int btn, BindMapping* mappings) { + BindMapping* bind = &mappings[binding]; + int i; + if (bind->button2) return BindMapping2_Claims(bind, btn); + + /* Two button mapping takes priority over one button mapping */ + for (i = 0; i < BIND_COUNT; i++) + { + if (mappings[i].button2 && BindMapping2_Claims(&mappings[i], btn)) return false; + } + return bind->button1 == btn; +} + +static cc_bool Mappings_IsPressed(InputBind binding, BindMapping* mappings) { + BindMapping* bind = &mappings[binding]; + int btn = bind->button1; + int i; + + if (!Input.Pressed[btn]) return false; + if (bind->button2) return Input.Pressed[bind->button2]; + + /* Two button mappings to the button takes priority one button mapping */ + for (i = 0; i < BIND_COUNT; i++) + { + bind = &mappings[i]; + if (!bind->button2) continue; + if (!(bind->button1 == btn || bind->button2 == btn)) continue; + + if (Input.Pressed[bind->button1] && Input.Pressed[bind->button2]) return false; + } + return true; +} + + +cc_bool InputBind_Claims(InputBind binding, int btn) { + return Mappings_DoesClaim(binding, btn, KeyBind_Mappings) || + Mappings_DoesClaim(binding, btn, PadBind_Mappings); +} + +cc_bool InputBind_IsPressed(InputBind binding) { + return Mappings_IsPressed(binding, KeyBind_Mappings) || + Mappings_IsPressed(binding, PadBind_Mappings); +} + +static void KeyBind_Load(const char* prefix, BindMapping* keybinds, const BindMapping* defaults) { + cc_string name; char nameBuffer[STRING_SIZE + 1]; + BindMapping mapping; + cc_string str, part1, part2; + int i; + + String_InitArray_NT(name, nameBuffer); + for (i = 0; i < BIND_COUNT; i++) + { + name.length = 0; + String_Format1(&name, prefix, bindNames[i]); + name.buffer[name.length] = '\0'; + + if (!Options_UNSAFE_Get(name.buffer, &str)) { + keybinds[i] = defaults[i]; + continue; + } + + String_UNSAFE_Separate(&str, ',', &part1, &part2); + mapping.button1 = Utils_ParseEnum(&part1, defaults[i].button1, storageNames, INPUT_COUNT); + mapping.button2 = Utils_ParseEnum(&part2, defaults[i].button2, storageNames, INPUT_COUNT); + + if (mapping.button1 == CCKEY_ESCAPE) mapping = defaults[i]; + keybinds[i] = mapping; + } +} + +static void InputBind_Set(InputBind binding, int btn, BindMapping* binds, const char* fmt) { + cc_string name; char nameBuffer[STRING_SIZE]; + cc_string value; + String_InitArray(name, nameBuffer); + + String_Format1(&name, fmt, bindNames[binding]); + value = String_FromReadonly(storageNames[btn]); + Options_SetString(&name, &value); + + BindMapping_Set(&binds[binding], btn, 0); +} + +void KeyBind_Set(InputBind binding, int btn) { + InputBind_Set(binding, btn, KeyBind_Mappings, "key-%c"); +} + +void PadBind_Set(InputBind binding, int btn) { + InputBind_Set(binding, btn, PadBind_Mappings, "pad-%c"); +} + +static void InputBind_ResetOption(InputBind binding, const char* fmt) { + cc_string name; char nameBuffer[STRING_SIZE]; + String_InitArray(name, nameBuffer); + + String_Format1(&name, fmt, bindNames[binding]); + Options_SetString(&name, &String_Empty); +} + +void KeyBind_Reset(InputBind binding) { + InputBind_ResetOption(binding, "key-%c"); + KeyBind_Mappings[binding] = KeyBind_Defaults[binding]; +} + +void PadBind_Reset(InputBind binding) { + InputBind_ResetOption(binding, "pad-%c"); + PadBind_Mappings[binding] = PadBind_Defaults[binding]; +} + +/* Initialises and loads input bindings from options */ +static void KeyBind_Init(void) { + KeyBind_Load("key-%c", KeyBind_Mappings, KeyBind_Defaults); + KeyBind_Load("pad-%c", PadBind_Mappings, PadBind_Defaults); +} + + +/*########################################################################################################################* +*---------------------------------------------------------Gamepad---------------------------------------------------------* +*#########################################################################################################################*/ +#define GAMEPAD_BEG_BTN CCPAD_A +#define GAMEPAD_BTN_COUNT (INPUT_COUNT - GAMEPAD_BEG_BTN) + +int Gamepad_AxisBehaviour[2] = { AXIS_BEHAVIOUR_MOVEMENT, AXIS_BEHAVIOUR_CAMERA }; +int Gamepad_AxisSensitivity[2] = { AXIS_SENSI_NORMAL, AXIS_SENSI_NORMAL }; +static const float axis_sensiFactor[] = { 0.25f, 0.5f, 1.0f, 2.0f, 4.0f }; + +struct GamepadState { + float axisX[2], axisY[2]; + cc_bool pressed[GAMEPAD_BTN_COUNT]; + float holdtime[GAMEPAD_BTN_COUNT]; +}; +static struct GamepadState gamepads[INPUT_MAX_GAMEPADS]; + +static void Gamepad_Update(struct GamepadState* pad, float delta) { + int btn; + for (btn = 0; btn < GAMEPAD_BTN_COUNT; btn++) + { + if (!pad->pressed[btn]) continue; + pad->holdtime[btn] += delta; + if (pad->holdtime[btn] < 1.0f) continue; + + /* Held for over a second, trigger a fake press */ + pad->holdtime[btn] = 0; + Input_SetPressed(btn + GAMEPAD_BEG_BTN); + } +} + + +void Gamepad_SetButton(int port, int btn, int pressed) { + struct GamepadState* pad = &gamepads[port]; + int i; + btn -= GAMEPAD_BEG_BTN; + + /* Reset hold tracking time */ + if (pressed && !pad->pressed[btn]) pad->holdtime[btn] = 0; + pad->pressed[btn] = pressed != 0;; + + /* Set pressed if button pressed on any gamepad, to avoid constant flip flopping */ + /* between pressed and non-pressed when multiple controllers are plugged in */ + for (i = 0; i < INPUT_MAX_GAMEPADS; i++) + pressed |= gamepads[i].pressed[btn]; + + Input_SetNonRepeatable(btn + GAMEPAD_BEG_BTN, pressed); +} + +void Gamepad_SetAxis(int port, int axis, float x, float y, float delta) { + gamepads[port].axisX[axis] = x; + gamepads[port].axisY[axis] = y; + if (x == 0 && y == 0) return; + + int sensi = Gamepad_AxisSensitivity[axis]; + float scale = delta * 60.0f * axis_sensiFactor[sensi]; + Event_RaisePadAxis(&ControllerEvents.AxisUpdate, port, axis, x * scale, y * scale); +} + +void Gamepad_Tick(float delta) { + int port; + Window_ProcessGamepads(delta); + + for (port = 0; port < INPUT_MAX_GAMEPADS; port++) + { + Gamepad_Update(&gamepads[port], delta); + } +} + +static void PlayerInputPad(int port, int axis, struct LocalPlayer* p, float* xMoving, float* zMoving) { + float x, y, angle; + if (Gamepad_AxisBehaviour[axis] != AXIS_BEHAVIOUR_MOVEMENT) return; + + x = gamepads[port].axisX[axis]; + y = gamepads[port].axisY[axis]; + + if (x != 0 || y != 0) { + angle = Math_Atan2f(x, y); + *xMoving = Math_CosF(angle); + *zMoving = Math_SinF(angle); + } +} + +static void PlayerInputGamepad(struct LocalPlayer* p, float* xMoving, float* zMoving) { + int port; + for (port = 0; port < INPUT_MAX_GAMEPADS; port++) + { + /* In splitscreen mode, tie a controller to a specific player*/ + if (Game_NumLocalPlayers > 1 && p->index != port) continue; + + PlayerInputPad(port, PAD_AXIS_LEFT, p, xMoving, zMoving); + PlayerInputPad(port, PAD_AXIS_RIGHT, p, xMoving, zMoving); + } +} +static struct LocalPlayerInput gamepadInput = { PlayerInputGamepad }; + + +/*########################################################################################################################* +*---------------------------------------------------------Hotkeys---------------------------------------------------------* +*#########################################################################################################################*/ +const cc_uint8 Hotkeys_LWJGL[256] = { + 0, CCKEY_ESCAPE, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', CCKEY_MINUS, CCKEY_EQUALS, CCKEY_BACKSPACE, CCKEY_TAB, + 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', CCKEY_LBRACKET, CCKEY_RBRACKET, CCKEY_ENTER, CCKEY_LCTRL, 'A', 'S', + 'D', 'F', 'G', 'H', 'J', 'K', 'L', CCKEY_SEMICOLON, CCKEY_QUOTE, CCKEY_TILDE, CCKEY_LSHIFT, CCKEY_BACKSLASH, 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', CCKEY_COMMA, CCKEY_PERIOD, CCKEY_SLASH, CCKEY_RSHIFT, 0, CCKEY_LALT, CCKEY_SPACE, CCKEY_CAPSLOCK, CCKEY_F1, CCKEY_F2, CCKEY_F3, CCKEY_F4, CCKEY_F5, + CCKEY_F6, CCKEY_F7, CCKEY_F8, CCKEY_F9, CCKEY_F10, CCKEY_NUMLOCK, CCKEY_SCROLLLOCK, CCKEY_KP7, CCKEY_KP8, CCKEY_KP9, CCKEY_KP_MINUS, CCKEY_KP4, CCKEY_KP5, CCKEY_KP6, CCKEY_KP_PLUS, CCKEY_KP1, + CCKEY_KP2, CCKEY_KP3, CCKEY_KP0, CCKEY_KP_DECIMAL, 0, 0, 0, CCKEY_F11, CCKEY_F12, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, CCKEY_F13, CCKEY_F14, CCKEY_F15, CCKEY_F16, CCKEY_F17, CCKEY_F18, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CCKEY_KP_PLUS, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, CCKEY_KP_ENTER, CCKEY_RCTRL, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, CCKEY_KP_DIVIDE, 0, 0, CCKEY_RALT, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, CCKEY_PAUSE, 0, CCKEY_HOME, CCKEY_UP, CCKEY_PAGEUP, 0, CCKEY_LEFT, 0, CCKEY_RIGHT, 0, CCKEY_END, + CCKEY_DOWN, CCKEY_PAGEDOWN, CCKEY_INSERT, CCKEY_DELETE, 0, 0, 0, 0, 0, 0, 0, CCKEY_LWIN, CCKEY_RWIN, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +struct HotkeyData HotkeysList[HOTKEYS_MAX_COUNT]; +struct StringsBuffer HotkeysText; + +static void Hotkeys_QuickSort(int left, int right) { + struct HotkeyData* keys = HotkeysList; struct HotkeyData key; + + while (left < right) { + int i = left, j = right; + cc_uint8 pivot = keys[(i + j) >> 1].mods; + + /* partition the list */ + while (i <= j) { + while (pivot < keys[i].mods) i++; + while (pivot > keys[j].mods) j--; + QuickSort_Swap_Maybe(); + } + /* recurse into the smaller subset */ + QuickSort_Recurse(Hotkeys_QuickSort) + } +} + +static void Hotkeys_AddNewHotkey(int trigger, cc_uint8 modifiers, const cc_string* text, cc_uint8 flags) { + struct HotkeyData hKey; + hKey.trigger = trigger; + hKey.mods = modifiers; + hKey.textIndex = HotkeysText.count; + hKey.flags = flags; + + if (HotkeysText.count == HOTKEYS_MAX_COUNT) { + Chat_AddRaw("&cCannot define more than 256 hotkeys"); + return; + } + + HotkeysList[HotkeysText.count] = hKey; + StringsBuffer_Add(&HotkeysText, text); + /* sort so that hotkeys with largest modifiers are first */ + Hotkeys_QuickSort(0, HotkeysText.count - 1); +} + +static void Hotkeys_RemoveText(int index) { + struct HotkeyData* hKey = HotkeysList; + int i; + + for (i = 0; i < HotkeysText.count; i++, hKey++) { + if (hKey->textIndex >= index) hKey->textIndex--; + } + StringsBuffer_Remove(&HotkeysText, index); +} + + +void Hotkeys_Add(int trigger, cc_uint8 modifiers, const cc_string* text, cc_uint8 flags) { + struct HotkeyData* hk = HotkeysList; + int i; + + for (i = 0; i < HotkeysText.count; i++, hk++) { + if (hk->trigger != trigger || hk->mods != modifiers) continue; + Hotkeys_RemoveText(hk->textIndex); + + hk->flags = flags; + hk->textIndex = HotkeysText.count; + StringsBuffer_Add(&HotkeysText, text); + return; + } + Hotkeys_AddNewHotkey(trigger, modifiers, text, flags); +} + +cc_bool Hotkeys_Remove(int trigger, cc_uint8 modifiers) { + struct HotkeyData* hk = HotkeysList; + int i, j; + + for (i = 0; i < HotkeysText.count; i++, hk++) { + if (hk->trigger != trigger || hk->mods != modifiers) continue; + Hotkeys_RemoveText(hk->textIndex); + + for (j = i; j < HotkeysText.count; j++) { + HotkeysList[j] = HotkeysList[j + 1]; + } + return true; + } + return false; +} + +int Hotkeys_FindPartial(int key) { + struct HotkeyData hk; + int i, modifiers = 0; + + if (Input_IsCtrlPressed()) modifiers |= HOTKEY_MOD_CTRL; + if (Input_IsShiftPressed()) modifiers |= HOTKEY_MOD_SHIFT; + if (Input_IsAltPressed()) modifiers |= HOTKEY_MOD_ALT; + + for (i = 0; i < HotkeysText.count; i++) { + hk = HotkeysList[i]; + /* e.g. if holding Ctrl and Shift, a hotkey with only Ctrl modifiers matches */ + if ((hk.mods & modifiers) == hk.mods && hk.trigger == key) return i; + } + return -1; +} + +static const cc_string prefix = String_FromConst("hotkey-"); +static void StoredHotkey_Parse(cc_string* key, cc_string* value) { + cc_string strKey, strMods, strMore, strText; + int trigger; + cc_uint8 modifiers; + cc_bool more; + + /* Format is: key&modifiers = more-input&text */ + key->length -= prefix.length; key->buffer += prefix.length; + + if (!String_UNSAFE_Separate(key, '&', &strKey, &strMods)) return; + if (!String_UNSAFE_Separate(value, '&', &strMore, &strText)) return; + + trigger = Utils_ParseEnum(&strKey, INPUT_NONE, storageNames, INPUT_COUNT); + if (trigger == INPUT_NONE) return; + if (!Convert_ParseUInt8(&strMods, &modifiers)) return; + if (!Convert_ParseBool(&strMore, &more)) return; + + Hotkeys_Add(trigger, modifiers, &strText, more); +} + +static void StoredHotkeys_LoadAll(void) { + cc_string entry, key, value; + int i; + + for (i = 0; i < Options.count; i++) { + StringsBuffer_UNSAFE_GetRaw(&Options, i, &entry); + String_UNSAFE_Separate(&entry, '=', &key, &value); + + if (!String_CaselessStarts(&key, &prefix)) continue; + StoredHotkey_Parse(&key, &value); + } +} + +void StoredHotkeys_Load(int trigger, cc_uint8 modifiers) { + cc_string key, value; char keyBuffer[STRING_SIZE]; + String_InitArray(key, keyBuffer); + + String_Format2(&key, "hotkey-%c&%b", storageNames[trigger], &modifiers); + key.buffer[key.length] = '\0'; /* TODO: Avoid this null terminator */ + + Options_UNSAFE_Get(key.buffer, &value); + StoredHotkey_Parse(&key, &value); +} + +void StoredHotkeys_Remove(int trigger, cc_uint8 modifiers) { + cc_string key; char keyBuffer[STRING_SIZE]; + String_InitArray(key, keyBuffer); + + String_Format2(&key, "hotkey-%c&%b", storageNames[trigger], &modifiers); + Options_SetString(&key, NULL); +} + +void StoredHotkeys_Add(int trigger, cc_uint8 modifiers, cc_bool moreInput, const cc_string* text) { + cc_string key; char keyBuffer[STRING_SIZE]; + cc_string value; char valueBuffer[STRING_SIZE * 2]; + String_InitArray(key, keyBuffer); + String_InitArray(value, valueBuffer); + + String_Format2(&key, "hotkey-%c&%b", storageNames[trigger], &modifiers); + String_Format2(&value, "%t&%s", &moreInput, text); + Options_SetString(&key, &value); +} + + +/*########################################################################################################################* +*-----------------------------------------------------Mouse helpers-------------------------------------------------------* +*#########################################################################################################################*/ +static void MouseStateUpdate(int button, cc_bool pressed) { + struct Entity* p; + input_buttonsDown[button] = pressed; + if (!Server.SupportsPlayerClick) return; + + /* defer getting the targeted entity, as it's a costly operation */ + if (input_pickingId == -1) { + p = &Entities.CurPlayer->Base; + input_pickingId = Entities_GetClosest(p); + + if (input_pickingId == -1) + input_pickingId = ENTITIES_SELF_ID; + } + + + CPE_SendPlayerClick(button, pressed, (EntityID)input_pickingId, &Game_SelectedPos); +} + +static void MouseStatePress(int button) { + input_lastClick = Game.Time; + input_pickingId = -1; + MouseStateUpdate(button, true); +} + +static void MouseStateRelease(int button) { + input_pickingId = -1; + if (!input_buttonsDown[button]) return; + MouseStateUpdate(button, false); +} + +void InputHandler_OnScreensChanged(void) { + input_lastClick = Game.Time; + input_pickingId = -1; + if (!Gui.InputGrab) return; + + /* If input is grabbed, then the mouse isn't used for picking blocks in world anymore. */ + /* So release all mouse buttons, since game stops sending PlayerClick during grabbed input */ + MouseStateRelease(MOUSE_LEFT); + MouseStateRelease(MOUSE_RIGHT); + MouseStateRelease(MOUSE_MIDDLE); +} + +static cc_bool TouchesSolid(BlockID b) { return Blocks.Collide[b] == COLLIDE_SOLID; } +static cc_bool PushbackPlace(struct AABB* blockBB) { + struct Entity* p = &Entities.CurPlayer->Base; + struct HacksComp* hacks = &Entities.CurPlayer->Hacks; + Face closestFace; + cc_bool insideMap; + + Vec3 pos = p->Position; + struct AABB playerBB; + struct LocationUpdate update; + + /* Offset position by the closest face */ + closestFace = Game_SelectedPos.closest; + if (closestFace == FACE_XMAX) { + pos.x = blockBB->Max.x + 0.5f; + } else if (closestFace == FACE_ZMAX) { + pos.z = blockBB->Max.z + 0.5f; + } else if (closestFace == FACE_XMIN) { + pos.x = blockBB->Min.x - 0.5f; + } else if (closestFace == FACE_ZMIN) { + pos.z = blockBB->Min.z - 0.5f; + } else if (closestFace == FACE_YMAX) { + pos.y = blockBB->Min.y + 1 + ENTITY_ADJUSTMENT; + } else if (closestFace == FACE_YMIN) { + pos.y = blockBB->Min.y - p->Size.y - ENTITY_ADJUSTMENT; + } + + /* Exclude exact map boundaries, otherwise player can get stuck outside map */ + /* Being vertically above the map is acceptable though */ + insideMap = + pos.x > 0.0f && pos.y >= 0.0f && pos.z > 0.0f && + pos.x < World.Width && pos.z < World.Length; + if (!insideMap) return false; + + AABB_Make(&playerBB, &pos, &p->Size); + if (!hacks->Noclip && Entity_TouchesAny(&playerBB, TouchesSolid)) { + /* Don't put player inside another block */ + return false; + } + + update.flags = LU_HAS_POS | LU_POS_ABSOLUTE_INSTANT; + update.pos = pos; + p->VTABLE->SetLocation(p, &update); + return true; +} + +static cc_bool IntersectsOthers(Vec3 pos, BlockID block) { + struct AABB blockBB, entityBB; + struct Entity* e; + int id; + + Vec3_Add(&blockBB.Min, &pos, &Blocks.MinBB[block]); + Vec3_Add(&blockBB.Max, &pos, &Blocks.MaxBB[block]); + + for (id = 0; id < ENTITIES_MAX_COUNT; id++) + { + e = Entities.List[id]; + if (!e || e == &Entities.CurPlayer->Base) continue; + + Entity_GetBounds(e, &entityBB); + entityBB.Min.y += 1.0f / 32.0f; /* when player is exactly standing on top of ground */ + if (AABB_Intersects(&entityBB, &blockBB)) return true; + } + return false; +} + +static cc_bool CheckIsFree(BlockID block) { + struct Entity* p = &Entities.CurPlayer->Base; + struct HacksComp* hacks = &Entities.CurPlayer->Hacks; + + Vec3 pos, nextPos; + struct AABB blockBB, playerBB; + struct LocationUpdate update; + + /* Non solid blocks (e.g. water/flowers) can always be placed on players */ + if (Blocks.Collide[block] != COLLIDE_SOLID) return true; + + IVec3_ToVec3(&pos, &Game_SelectedPos.translatedPos); + if (IntersectsOthers(pos, block)) return false; + + nextPos = p->next.pos; + Vec3_Add(&blockBB.Min, &pos, &Blocks.MinBB[block]); + Vec3_Add(&blockBB.Max, &pos, &Blocks.MaxBB[block]); + + /* NOTE: Need to also test against next position here, otherwise player can */ + /* fall through the block at feet as collision is performed against nextPos */ + Entity_GetBounds(p, &playerBB); + playerBB.Min.y = min(nextPos.y, playerBB.Min.y); + + if (hacks->Noclip || !AABB_Intersects(&playerBB, &blockBB)) return true; + if (hacks->CanPushbackBlocks && hacks->PushbackPlacing && hacks->Enabled) { + return PushbackPlace(&blockBB); + } + + playerBB.Min.y += 0.25f + ENTITY_ADJUSTMENT; + if (AABB_Intersects(&playerBB, &blockBB)) return false; + + /* Push player upwards when they are jumping and trying to place a block underneath them */ + nextPos.y = pos.y + Blocks.MaxBB[block].y + ENTITY_ADJUSTMENT; + + update.flags = LU_HAS_POS | LU_POS_ABSOLUTE_INSTANT; + update.pos = nextPos; + p->VTABLE->SetLocation(p, &update); + return true; +} + +void InputHandler_DeleteBlock(void) { + IVec3 pos; + BlockID old; + /* always play delete animations, even if we aren't deleting a block */ + HeldBlockRenderer_ClickAnim(true); + + pos = Game_SelectedPos.pos; + if (!Game_SelectedPos.valid || !World_Contains(pos.x, pos.y, pos.z)) return; + + old = World_GetBlock(pos.x, pos.y, pos.z); + if (Blocks.Draw[old] == DRAW_GAS || !Blocks.CanDelete[old]) return; + + Game_ChangeBlock(pos.x, pos.y, pos.z, BLOCK_AIR); + Event_RaiseBlock(&UserEvents.BlockChanged, pos, old, BLOCK_AIR); +} + +void InputHandler_PlaceBlock(void) { + IVec3 pos; + BlockID old, block; + pos = Game_SelectedPos.translatedPos; + if (!Game_SelectedPos.valid || !World_Contains(pos.x, pos.y, pos.z)) return; + + old = World_GetBlock(pos.x, pos.y, pos.z); + block = Inventory_SelectedBlock; + if (AutoRotate_Enabled) block = AutoRotate_RotateBlock(block); + + if (Game_CanPick(old) || !Blocks.CanPlace[block]) return; + /* air-ish blocks can only replace over other air-ish blocks */ + if (Blocks.Draw[block] == DRAW_GAS && Blocks.Draw[old] != DRAW_GAS) return; + + /* undeletable gas blocks can't be replaced with other blocks */ + if (Blocks.Collide[old] == COLLIDE_NONE && !Blocks.CanDelete[old]) return; + + if (!CheckIsFree(block)) return; + + Game_ChangeBlock(pos.x, pos.y, pos.z, block); + Event_RaiseBlock(&UserEvents.BlockChanged, pos, old, block); +} + +void InputHandler_PickBlock(void) { + IVec3 pos; + BlockID cur; + pos = Game_SelectedPos.pos; + if (!World_Contains(pos.x, pos.y, pos.z)) return; + + cur = World_GetBlock(pos.x, pos.y, pos.z); + if (Blocks.Draw[cur] == DRAW_GAS) return; + if (!(Blocks.CanPlace[cur] || Blocks.CanDelete[cur])) return; + Inventory_PickBlock(cur); +} + +void InputHandler_Tick(void) { + cc_bool left, middle, right; + double now, delta; + + if (Gui.InputGrab) return; + now = Game.Time; + delta = now - input_lastClick; + + if (delta < 0.2495) return; /* 4 times per second */ + /* NOTE: 0.2495 is used instead of 0.25 to produce delta time */ + /* values slightly closer to the old code which measured */ + /* elapsed time using DateTime_CurrentUTC_MS() instead */ + input_lastClick = now; + + left = input_buttonsDown[MOUSE_LEFT]; + middle = input_buttonsDown[MOUSE_MIDDLE]; + right = input_buttonsDown[MOUSE_RIGHT]; + +#ifdef CC_BUILD_TOUCH + if (Input_TouchMode) { + left = (Input_HoldMode == INPUT_MODE_DELETE) && AnyBlockTouches(); + right = (Input_HoldMode == INPUT_MODE_PLACE) && AnyBlockTouches(); + middle = false; + } +#endif + + if (Server.SupportsPlayerClick) { + input_pickingId = -1; + if (left) MouseStateUpdate(MOUSE_LEFT, true); + if (right) MouseStateUpdate(MOUSE_RIGHT, true); + if (middle) MouseStateUpdate(MOUSE_MIDDLE, true); + } + + if (left) { + InputHandler_DeleteBlock(); + } else if (right) { + InputHandler_PlaceBlock(); + } else if (middle) { + InputHandler_PickBlock(); + } +} + + +/*########################################################################################################################* +*-----------------------------------------------------Input helpers-------------------------------------------------------* +*#########################################################################################################################*/ +static cc_bool InputHandler_IsShutdown(int key) { + if (key == CCKEY_F4 && Input_IsAltPressed()) return true; + + /* On macOS, Cmd+Q should also end the process */ +#ifdef CC_BUILD_DARWIN + return key == 'Q' && Input_IsWinPressed(); +#else + return false; +#endif +} + +static void InputHandler_Toggle(int key, cc_bool* target, const char* enableMsg, const char* disableMsg) { + *target = !(*target); + if (*target) { + Chat_Add2("%c. &ePress &a%c &eto disable.", enableMsg, Input_DisplayNames[key]); + } else { + Chat_Add2("%c. &ePress &a%c &eto re-enable.", disableMsg, Input_DisplayNames[key]); + } +} + +cc_bool InputHandler_SetFOV(int fov) { + struct HacksComp* h = &Entities.CurPlayer->Hacks; + if (!h->Enabled || !h->CanUseThirdPerson) return false; + + Camera.ZoomFov = fov; + Camera_SetFov(fov); + return true; +} + +cc_bool Input_HandleMouseWheel(float delta) { + struct HacksComp* h; + cc_bool hotbar; + + hotbar = Input_IsAltPressed() || Input_IsCtrlPressed() || Input_IsShiftPressed(); + if (!hotbar && Camera.Active->Zoom(delta)) return true; + if (!InputBind_IsPressed(BIND_ZOOM_SCROLL)) return false; + + h = &Entities.CurPlayer->Hacks; + if (!h->Enabled || !h->CanUseThirdPerson) return false; + + if (input_fovIndex == -1.0f) input_fovIndex = (float)Camera.ZoomFov; + input_fovIndex -= delta * 5.0f; + + Math_Clamp(input_fovIndex, 1.0f, Camera.DefaultFov); + return InputHandler_SetFOV((int)input_fovIndex); +} + +static void InputHandler_CheckZoomFov(void* obj) { + struct HacksComp* h = &Entities.CurPlayer->Hacks; + if (!h->Enabled || !h->CanUseThirdPerson) Camera_SetFov(Camera.DefaultFov); +} + + +static cc_bool BindTriggered_DeleteBlock(int key) { + MouseStatePress(MOUSE_LEFT); + InputHandler_DeleteBlock(); + return true; +} + +static cc_bool BindTriggered_PlaceBlock(int key) { + MouseStatePress(MOUSE_RIGHT); + InputHandler_PlaceBlock(); + return true; +} + +static cc_bool BindTriggered_PickBlock(int key) { + MouseStatePress(MOUSE_MIDDLE); + InputHandler_PickBlock(); + return true; +} + +static void BindReleased_DeleteBlock(int key) { + MouseStateRelease(MOUSE_LEFT); +} + +static void BindReleased_PlaceBlock(int key) { + MouseStateRelease(MOUSE_RIGHT); +} + +static void BindReleased_PickBlock(int key) { + MouseStateRelease(MOUSE_MIDDLE); +} + + +static cc_bool BindTriggered_HideFPS(int key) { + Gui.ShowFPS = !Gui.ShowFPS; + return true; +} + +static cc_bool BindTriggered_Fullscreen(int key) { + Game_ToggleFullscreen(); + return true; +} + +static cc_bool BindTriggered_Fog(int key) { + Game_CycleViewDistance(); + return true; +} + + +static cc_bool BindTriggered_HideGUI(int key) { + Game_HideGui = !Game_HideGui; + return true; +} + +static cc_bool BindTriggered_SmoothCamera(int key) { + InputHandler_Toggle(key, &Camera.Smooth, + " &eSmooth camera is &aenabled", + " &eSmooth camera is &cdisabled"); + return true; +} + +static cc_bool BindTriggered_AxisLines(int key) { + InputHandler_Toggle(key, &AxisLinesRenderer_Enabled, + " &eAxis lines (&4X&e, &2Y&e, &1Z&e) now show", + " &eAxis lines no longer show"); + return true; +} + +static cc_bool BindTriggered_AutoRotate(int key) { + InputHandler_Toggle(key, &AutoRotate_Enabled, + " &eAuto rotate is &aenabled", + " &eAuto rotate is &cdisabled"); + return true; +} + +static cc_bool BindTriggered_ThirdPerson(int key) { + Camera_CycleActive(); + return true; +} + +static cc_bool BindTriggered_DropBlock(int key) { + if (Inventory_CheckChangeSelected() && Inventory_SelectedBlock != BLOCK_AIR) { + /* Don't assign SelectedIndex directly, because we don't want held block + switching positions if they already have air in their inventory hotbar. */ + Inventory_Set(Inventory.SelectedIndex, BLOCK_AIR); + Event_RaiseVoid(&UserEvents.HeldBlockChanged); + } + return true; +} + +static cc_bool BindTriggered_IDOverlay(int key) { + TexIdsOverlay_Show(); + return true; +} + +static cc_bool BindTriggered_BreakLiquids(int key) { + InputHandler_Toggle(key, &Game_BreakableLiquids, + " &eBreakable liquids is &aenabled", + " &eBreakable liquids is &cdisabled"); + return true; +} + +static void HandleHotkeyDown(int key) { + struct HotkeyData* hkey; + cc_string text; + int i = Hotkeys_FindPartial(key); + + if (i == -1) return; + hkey = &HotkeysList[i]; + text = StringsBuffer_UNSAFE_Get(&HotkeysText, hkey->textIndex); + + if (!(hkey->flags & HOTKEY_FLAG_STAYS_OPEN)) { + Chat_Send(&text, false); + } else if (!Gui.InputGrab) { + ChatScreen_OpenInput(&text); + } +} + +static void HookInputBinds(void) { + Bind_OnTriggered[BIND_HIDE_FPS] = BindTriggered_HideFPS; + Bind_OnTriggered[BIND_FULLSCREEN] = BindTriggered_Fullscreen; + Bind_OnTriggered[BIND_FOG] = BindTriggered_Fog; + + Bind_OnTriggered[BIND_DELETE_BLOCK] = BindTriggered_DeleteBlock; + Bind_OnTriggered[BIND_PLACE_BLOCK] = BindTriggered_PlaceBlock; + Bind_OnTriggered[BIND_PICK_BLOCK] = BindTriggered_PickBlock; + + Bind_OnReleased[BIND_DELETE_BLOCK] = BindReleased_DeleteBlock; + Bind_OnReleased[BIND_PLACE_BLOCK] = BindReleased_PlaceBlock; + Bind_OnReleased[BIND_PICK_BLOCK] = BindReleased_PickBlock; + + if (Game_ClassicMode) return; + Bind_OnTriggered[BIND_HIDE_GUI] = BindTriggered_HideGUI; + Bind_OnTriggered[BIND_SMOOTH_CAMERA] = BindTriggered_SmoothCamera; + Bind_OnTriggered[BIND_AXIS_LINES] = BindTriggered_AxisLines; + Bind_OnTriggered[BIND_AUTOROTATE] = BindTriggered_AutoRotate; + Bind_OnTriggered[BIND_THIRD_PERSON] = BindTriggered_ThirdPerson; + Bind_OnTriggered[BIND_DROP_BLOCK] = BindTriggered_DropBlock; + Bind_OnTriggered[BIND_IDOVERLAY] = BindTriggered_IDOverlay; + Bind_OnTriggered[BIND_BREAK_LIQUIDS] = BindTriggered_BreakLiquids; +} + + +/*########################################################################################################################* +*-----------------------------------------------------Base handlers-------------------------------------------------------* +*#########################################################################################################################*/ +static void OnPointerDown(void* obj, int idx) { + struct Screen* s; + int i, x, y, mask; +#ifdef CC_BUILD_TOUCH + if (Input_TouchMode && !(touches[idx].type & TOUCH_TYPE_GUI)) return; +#endif + x = Pointers[idx].x; y = Pointers[idx].y; + + for (i = 0; i < Gui.ScreensCount; i++) { + s = Gui_Screens[i]; + s->dirty = true; + mask = s->VTABLE->HandlesPointerDown(s, 1 << idx, x, y); + +#ifdef CC_BUILD_TOUCH + if (mask) { + /* Using &= mask instead of = mask is to handle one specific case */ + /* - when clicking 'Quit game' in android version, it will call */ + /* Game_Free, which will in turn call InputComponent.Free. */ + /* That resets the type of all touches to 0 - however, since it is */ + /* called DURING HandlesPointerDown, using = mask here would undo */ + /* the resetting of type to 0 for one of the touches states, */ + /* causing problems later with Input_AddTouch as it will assume that */ + /* the aforementioned touches state is wrongly still in use */ + touches[idx].type &= mask; return; + } +#else + if (mask) return; +#endif + } +} + +static void OnPointerUp(void* obj, int idx) { + struct Screen* s; + int i, x, y; +#ifdef CC_BUILD_TOUCH + CheckBlockTap(idx); + if (Input_TouchMode && !(touches[idx].type & TOUCH_TYPE_GUI)) return; +#endif + x = Pointers[idx].x; y = Pointers[idx].y; + + for (i = 0; i < Gui.ScreensCount; i++) { + s = Gui_Screens[i]; + s->dirty = true; + s->VTABLE->OnPointerUp(s, 1 << idx, x, y); + } +} + +static void OnInputDown(void* obj, int key, cc_bool was) { + struct Screen* s; + cc_bool triggered; + int i; + if (Window_Main.SoftKeyboardFocus) return; + +#ifndef CC_BUILD_WEB + if (Input_IsEscapeButton(key) && (s = Gui_GetClosable())) { + /* Don't want holding down escape to go in and out of pause menu */ + if (!was) Gui_Remove(s); + return; + } +#endif + + if (InputHandler_IsShutdown(key)) { + /* TODO: Do we need a separate exit function in Game class? */ + Window_RequestClose(); return; + } else if (InputBind_Claims(BIND_SCREENSHOT, key) && !was) { + Game_ScreenshotRequested = true; return; + } + + for (i = 0; i < Gui.ScreensCount; i++) { + s = Gui_Screens[i]; + s->dirty = true; + if (s->VTABLE->HandlesInputDown(s, key)) return; + } + if (Gui.InputGrab) return; + + if (Input_IsPauseButton(key)) { +#ifdef CC_BUILD_WEB + /* Can't do this in KeyUp, because pressing escape without having */ + /* explicitly disabled mouse lock means a KeyUp event isn't sent. */ + /* But switching to pause screen disables mouse lock, causing a KeyUp */ + /* event to be sent, triggering the active->closable case which immediately */ + /* closes the pause screen. Hence why the next KeyUp must be supressed. */ + suppressEscape = true; +#endif + Gui_ShowPauseMenu(); return; + } + + /* These should not be triggered multiple times when holding down */ + if (was) return; + triggered = false; + + for (i = 0; i < BIND_COUNT; i++) + { + if (!Bind_OnTriggered[i]) continue; + if (!InputBind_Claims(i, key)) continue; + + triggered |= Bind_OnTriggered[i](key); + } + + if (triggered) { + } else if (key == CCKEY_F5 && Game_ClassicMode) { + int weather = Env.Weather == WEATHER_SUNNY ? WEATHER_RAINY : WEATHER_SUNNY; + Env_SetWeather(weather); + } else { HandleHotkeyDown(key); } +} + +static void OnInputUp(void* obj, int key) { + struct Screen* s; + int i; + + if (InputBind_Claims(BIND_ZOOM_SCROLL, key)) Camera_SetFov(Camera.DefaultFov); +#ifdef CC_BUILD_WEB + /* When closing menus (which reacquires mouse focus) in key down, */ + /* this still leaves the cursor visible. But if this is instead */ + /* done in key up, the cursor disappears as expected. */ + if (key == CCKEY_ESCAPE && (s = Gui_GetClosable())) { + if (suppressEscape) { suppressEscape = false; return; } + Gui_Remove(s); return; + } +#endif + + for (i = 0; i < Gui.ScreensCount; i++) { + s = Gui_Screens[i]; + s->dirty = true; + s->VTABLE->OnInputUp(s, key); + } + + for (i = 0; i < BIND_COUNT; i++) + { + if (!Bind_OnReleased[i]) continue; + if (!InputBind_Claims(i, key)) continue; + + Bind_OnReleased[i](key); + } +} + +static void OnFocusChanged(void* obj) { if (!Window_Main.Focused) Input_Clear(); } + +static void PlayerInputNormal(struct LocalPlayer* p, float* xMoving, float* zMoving) { + if (InputBind_IsPressed(BIND_FORWARD)) *zMoving -= 1; + if (InputBind_IsPressed(BIND_BACK)) *zMoving += 1; + if (InputBind_IsPressed(BIND_LEFT)) *xMoving -= 1; + if (InputBind_IsPressed(BIND_RIGHT)) *xMoving += 1; +} +static struct LocalPlayerInput normalInput = { PlayerInputNormal }; + +static void OnInit(void) { + LocalPlayerInput_Add(&normalInput); + LocalPlayerInput_Add(&gamepadInput); + HookInputBinds(); + + Event_Register_(&PointerEvents.Down, NULL, OnPointerDown); + Event_Register_(&PointerEvents.Up, NULL, OnPointerUp); + Event_Register_(&InputEvents.Down, NULL, OnInputDown); + Event_Register_(&InputEvents.Up, NULL, OnInputUp); + + Event_Register_(&WindowEvents.FocusChanged, NULL, OnFocusChanged); + Event_Register_(&UserEvents.HackPermsChanged, NULL, InputHandler_CheckZoomFov); + KeyBind_Init(); + StoredHotkeys_LoadAll(); + /* Fix issue with Android where if you double click in server list to join, a touch */ + /* pointer is stuck down when the game loads (so you instantly start deleting blocks) */ + ClearTouches(); +} + +static void OnFree(void) { + ClearTouches(); + HotkeysText.count = 0; +} + +struct IGameComponent Input_Component = { + OnInit, /* Init */ + OnFree, /* Free */ +}; |