Combine input/inputhandler

This commit is contained in:
UnknownShadow200 2019-09-03 19:09:45 +10:00
parent 89f35a5293
commit 57d9b5f238
8 changed files with 594 additions and 612 deletions

View File

@ -215,7 +215,6 @@
<ClInclude Include="LWidgets.h" />
<ClInclude Include="Model.h" />
<ClInclude Include="Input.h" />
<ClInclude Include="InputHandler.h" />
<ClInclude Include="Menus.h" />
<ClInclude Include="Protocol.h" />
<ClInclude Include="Physics.h" />
@ -272,7 +271,6 @@
<ClCompile Include="Graphics.c" />
<ClCompile Include="Gui.c" />
<ClCompile Include="HeldBlockRenderer.c" />
<ClCompile Include="InputHandler.c" />
<ClCompile Include="Inventory.c" />
<ClCompile Include="Launcher.c" />
<ClCompile Include="LScreens.c" />

View File

@ -240,9 +240,6 @@
<ClInclude Include="HeldBlockRenderer.h">
<Filter>Header Files\Rendering</Filter>
</ClInclude>
<ClInclude Include="InputHandler.h">
<Filter>Header Files\Game</Filter>
</ClInclude>
<ClInclude Include="Physics.h">
<Filter>Header Files\Math</Filter>
</ClInclude>
@ -419,9 +416,6 @@
<ClCompile Include="HeldBlockRenderer.c">
<Filter>Source Files\Rendering</Filter>
</ClCompile>
<ClCompile Include="InputHandler.c">
<Filter>Source Files\Game</Filter>
</ClCompile>
<ClCompile Include="Physics.c">
<Filter>Source Files\Math</Filter>
</ClCompile>

View File

@ -20,7 +20,7 @@
#include "Particle.h"
#include "Http.h"
#include "Inventory.h"
#include "InputHandler.h"
#include "Input.h"
#include "Server.h"
#include "TexturePack.h"
#include "Screens.h"

View File

@ -7,7 +7,7 @@
#include "ExtMath.h"
#include "Screens.h"
#include "Camera.h"
#include "InputHandler.h"
#include "Input.h"
#include "Logger.h"
#include "Platform.h"
#include "Bitmap.h"

View File

@ -6,6 +6,37 @@
#include "Logger.h"
#include "Platform.h"
#include "Chat.h"
#include "Utils.h"
#include "Server.h"
#include "HeldBlockRenderer.h"
#include "Game.h"
#include "Platform.h"
#include "ExtMath.h"
#include "Camera.h"
#include "Inventory.h"
#include "World.h"
#include "Event.h"
#include "Window.h"
#include "Entity.h"
#include "Chat.h"
#include "Funcs.h"
#include "Screens.h"
#include "Block.h"
#include "Menus.h"
#include "Gui.h"
#include "Protocol.h"
#include "AxisLinesRenderer.h"
static bool input_buttonsDown[3];
static int input_pickingId = -1;
static const short normDists[10] = { 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096 };
static const short classicDists[4] = { 8, 32, 128, 512 };
static TimeMS input_lastClick;
static float input_fovIndex = -1.0f;
#ifdef CC_BUILD_WEB
static bool suppressEscape;
#endif
enum MouseButton_ { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
/*########################################################################################################################*
@ -247,7 +278,8 @@ void KeyBind_Set(KeyBind binding, Key key) {
KeyBind_Save();
}
void KeyBind_Init(void) {
/* Initialises and loads key bindings from options */
static void KeyBind_Init(void) {
int i;
for (i = 0; i < KEYBIND_COUNT; i++) {
KeyBinds[i] = KeyBind_Defaults[i];
@ -375,7 +407,8 @@ int Hotkeys_FindPartial(Key key) {
return -1;
}
void Hotkeys_Init(void) {
/* Initialises and loads hotkeys from options. */
static void Hotkeys_Init(void) {
static const String prefix = String_FromConst("hotkey-");
String strKey, strMods, strMore, strText;
String entry, key, value;
@ -423,3 +456,552 @@ void Hotkeys_UserAddedHotkey(Key trigger, cc_uint8 modifiers, bool moreInput, co
String_Format2(&value, "%t&%s", &moreInput, text);
Options_SetString(&key, &value);
}
/*########################################################################################################################*
*-----------------------------------------------------Mouse helpers-------------------------------------------------------*
*#########################################################################################################################*/
static void MouseStateUpdate(int button, bool pressed) {
struct Entity* p;
/* defer getting the targeted entity, as it's a costly operation */
if (input_pickingId == -1) {
p = &LocalPlayer_Instance.Base;
input_pickingId = Entities_GetCloset(p);
}
input_buttonsDown[button] = pressed;
CPE_SendPlayerClick(button, pressed, (EntityID)input_pickingId, &Game_SelectedPos);
}
static void MouseStateChanged(int button, bool pressed) {
if (!Server.SupportsPlayerClick) return;
if (pressed) {
/* Can send multiple Pressed events */
MouseStateUpdate(button, true);
} else {
if (!input_buttonsDown[button]) return;
MouseStateUpdate(button, false);
}
}
static void MouseStatePress(int button) {
input_lastClick = DateTime_CurrentUTC_MS();
input_pickingId = -1;
MouseStateChanged(button, true);
}
static void MouseStateRelease(int button) {
input_pickingId = -1;
MouseStateChanged(button, false);
}
void InputHandler_OnScreensChanged(void) {
input_lastClick = DateTime_CurrentUTC_MS();
input_pickingId = -1;
MouseStateChanged(MOUSE_LEFT, false);
MouseStateChanged(MOUSE_RIGHT, false);
MouseStateChanged(MOUSE_MIDDLE, false);
}
static bool TouchesSolid(BlockID b) { return Blocks.Collide[b] == COLLIDE_SOLID; }
static bool PushbackPlace(struct AABB* blockBB) {
struct Entity* p = &LocalPlayer_Instance.Base;
struct HacksComp* hacks = &LocalPlayer_Instance.Hacks;
Face closestFace;
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;
}
LocationUpdate_MakePos(&update, pos, false);
p->VTABLE->SetLocation(p, &update, false);
return true;
}
static bool IntersectsOthers(Vec3 pos, BlockID block) {
struct AABB blockBB, entityBB;
struct Entity* entity;
int id;
Vec3_Add(&blockBB.Min, &pos, &Blocks.MinBB[block]);
Vec3_Add(&blockBB.Max, &pos, &Blocks.MaxBB[block]);
for (id = 0; id < ENTITIES_SELF_ID; id++) {
entity = Entities.List[id];
if (!entity) continue;
Entity_GetBounds(entity, &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 bool CheckIsFree(BlockID block) {
struct Entity* p = &LocalPlayer_Instance.Base;
struct HacksComp* hacks = &LocalPlayer_Instance.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 = LocalPlayer_Instance.Interp.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;
LocationUpdate_MakePos(&update, nextPos, false);
p->VTABLE->SetLocation(p, &update, false);
return true;
}
static 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.BlockPos;
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);
}
static 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;
if (!CheckIsFree(block)) return;
Game_ChangeBlock(pos.X, pos.Y, pos.Z, block);
Event_RaiseBlock(&UserEvents.BlockChanged, pos, old, block);
}
static void InputHandler_PickBlock(void) {
IVec3 pos;
BlockID cur;
pos = Game_SelectedPos.BlockPos;
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_PickBlocks(void) {
bool left, middle, right;
TimeMS now = DateTime_CurrentUTC_MS();
int delta = (int)(now - input_lastClick);
if (delta < 250) return; /* 4 times per second */
input_lastClick = now;
if (Gui_GetInputGrab()) return;
left = KeyBind_IsPressed(KEYBIND_DELETE_BLOCK);
middle = KeyBind_IsPressed(KEYBIND_PICK_BLOCK);
right = KeyBind_IsPressed(KEYBIND_PLACE_BLOCK);
if (Server.SupportsPlayerClick) {
input_pickingId = -1;
MouseStateChanged(MOUSE_LEFT, left);
MouseStateChanged(MOUSE_RIGHT, right);
MouseStateChanged(MOUSE_MIDDLE, middle);
}
if (left) {
InputHandler_DeleteBlock();
} else if (right) {
InputHandler_PlaceBlock();
} else if (middle) {
InputHandler_PickBlock();
}
}
/*########################################################################################################################*
*------------------------------------------------------Key helpers--------------------------------------------------------*
*#########################################################################################################################*/
static bool InputHandler_IsShutdown(Key key) {
if (key == KEY_F4 && Key_IsAltPressed()) return true;
/* On OSX, Cmd+Q should also terminate the process */
#ifdef CC_BUILD_OSX
return key == 'Q' && Key_IsWinPressed();
#else
return false;
#endif
}
static void InputHandler_Toggle(Key key, bool* target, const char* enableMsg, const char* disableMsg) {
*target = !(*target);
if (*target) {
Chat_Add2("%c. &ePress &a%c &eto disable.", enableMsg, Input_Names[key]);
} else {
Chat_Add2("%c. &ePress &a%c &eto re-enable.", disableMsg, Input_Names[key]);
}
}
static void InputHandler_CycleDistanceForwards(const short* viewDists, int count) {
int i, dist;
for (i = 0; i < count; i++) {
dist = viewDists[i];
if (dist > Game_UserViewDistance) {
Game_UserSetViewDistance(dist); return;
}
}
Game_UserSetViewDistance(viewDists[0]);
}
static void InputHandler_CycleDistanceBackwards(const short* viewDists, int count) {
int i, dist;
for (i = count - 1; i >= 0; i--) {
dist = viewDists[i];
if (dist < Game_UserViewDistance) {
Game_UserSetViewDistance(dist); return;
}
}
Game_UserSetViewDistance(viewDists[count - 1]);
}
bool InputHandler_SetFOV(int fov) {
struct HacksComp* h = &LocalPlayer_Instance.Hacks;
if (!h->Enabled || !h->CanUseThirdPersonCamera) return false;
Game_ZoomFov = fov;
Game_SetFov(fov);
return true;
}
static bool InputHandler_DoFovZoom(float deltaPrecise) {
struct HacksComp* h;
if (!KeyBind_IsPressed(KEYBIND_ZOOM_SCROLL)) return false;
h = &LocalPlayer_Instance.Hacks;
if (!h->Enabled || !h->CanUseThirdPersonCamera) return false;
if (input_fovIndex == -1.0f) input_fovIndex = (float)Game_ZoomFov;
input_fovIndex -= deltaPrecise * 5.0f;
Math_Clamp(input_fovIndex, 1.0f, Game_DefaultFov);
return InputHandler_SetFOV((int)input_fovIndex);
}
static void InputHandler_CheckZoomFov(void* obj) {
struct HacksComp* h = &LocalPlayer_Instance.Hacks;
if (!h->Enabled || !h->CanUseThirdPersonCamera) Game_SetFov(Game_DefaultFov);
}
static bool HandleBlockKey(Key key) {
if (Gui_GetInputGrab()) return false;
if (key == KeyBinds[KEYBIND_DELETE_BLOCK]) {
MouseStatePress(MOUSE_LEFT);
InputHandler_DeleteBlock();
} else if (key == KeyBinds[KEYBIND_PLACE_BLOCK]) {
MouseStatePress(MOUSE_RIGHT);
InputHandler_PlaceBlock();
} else if (key == KeyBinds[KEYBIND_PICK_BLOCK]) {
MouseStatePress(MOUSE_MIDDLE);
InputHandler_PickBlock();
} else {
return false;
}
return true;
}
static bool HandleNonClassicKey(Key key) {
if (key == KeyBinds[KEYBIND_HIDE_GUI]) {
Game_HideGui = !Game_HideGui;
} else if (key == KeyBinds[KEYBIND_SMOOTH_CAMERA]) {
InputHandler_Toggle(key, &Camera.Smooth,
" &eSmooth camera is &aenabled",
" &eSmooth camera is &cdisabled");
} else if (key == KeyBinds[KEYBIND_AXIS_LINES]) {
InputHandler_Toggle(key, &AxisLinesRenderer_Enabled,
" &eAxis lines (&4X&e, &2Y&e, &1Z&e) now show",
" &eAxis lines no longer show");
} else if (key == KeyBinds[KEYBIND_AUTOROTATE]) {
InputHandler_Toggle(key, &AutoRotate_Enabled,
" &eAuto rotate is &aenabled",
" &eAuto rotate is &cdisabled");
} else if (key == KeyBinds[KEYBIND_THIRD_PERSON]) {
Camera_CycleActive();
} else if (key == KeyBinds[KEYBIND_DROP_BLOCK]) {
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);
}
} else if (key == KeyBinds[KEYBIND_IDOVERLAY]) {
TexIdsOverlay_Show();
} else if (key == KeyBinds[KEYBIND_BREAK_LIQUIDS]) {
InputHandler_Toggle(key, &Game_BreakableLiquids,
" &eBreakable liquids is &aenabled",
" &eBreakable liquids is &cdisabled");
} else {
return false;
}
return true;
}
static bool HandleCoreKey(Key key) {
if (key == KeyBinds[KEYBIND_HIDE_FPS]) {
Gui_ShowFPS = !Gui_ShowFPS;
} else if (key == KeyBinds[KEYBIND_FULLSCREEN]) {
int state = Window_GetWindowState();
if (state == WINDOW_STATE_FULLSCREEN) {
Window_ExitFullscreen();
} else if (state != WINDOW_STATE_MINIMISED) {
Window_EnterFullscreen();
}
} else if (key == KeyBinds[KEYBIND_FOG]) {
const short* viewDists = Gui_ClassicMenu ? classicDists : normDists;
int count = Gui_ClassicMenu ? Array_Elems(classicDists) : Array_Elems(normDists);
if (Key_IsShiftPressed()) {
InputHandler_CycleDistanceBackwards(viewDists, count);
} else {
InputHandler_CycleDistanceForwards(viewDists, count);
}
} else if (key == KEY_F5 && Game_ClassicMode) {
int weather = Env.Weather == WEATHER_SUNNY ? WEATHER_RAINY : WEATHER_SUNNY;
Env_SetWeather(weather);
} else {
if (Game_ClassicMode) return false;
return HandleNonClassicKey(key);
}
return true;
}
static void HandleHotkeyDown(Key key) {
struct HotkeyData* hkey;
String text;
int i = Hotkeys_FindPartial(key);
if (i == -1) return;
hkey = &HotkeysList[i];
text = StringsBuffer_UNSAFE_Get(&HotkeysText, hkey->TextIndex);
if (!hkey->StaysOpen) {
Chat_Send(&text, false);
} else if (!Gui_GetInputGrab()) {
HUDScreen_OpenInput(&text);
}
}
/*########################################################################################################################*
*-----------------------------------------------------Base handlers-------------------------------------------------------*
*#########################################################################################################################*/
static void HandleMouseWheel(void* obj, float delta) {
struct Screen* s;
int i;
struct Widget* widget;
bool hotbar;
for (i = 0; i < Gui_ScreensCount; i++) {
s = Gui_Screens[i];
if (s->VTABLE->HandlesMouseScroll(s, delta)) return;
}
hotbar = Key_IsAltPressed() || Key_IsControlPressed() || Key_IsShiftPressed();
if (!hotbar && Camera.Active->Zoom(delta)) return;
if (InputHandler_DoFovZoom(delta) || !Inventory.CanChangeSelected) return;
widget = HUDScreen_GetHotbar();
Elem_HandlesMouseScroll(widget, delta);
}
static void HandlePointerMove(void* obj, int idx, int xDelta, int yDelta) {
struct Screen* s;
int i, x = Pointers[idx].x, y = Pointers[idx].y;
for (i = 0; i < Gui_ScreensCount; i++) {
s = Gui_Screens[i];
if (s->VTABLE->HandlesPointerMove(s, 1 << idx, x, y)) return;
}
}
static void HandlePointerDown(void* obj, int idx) {
struct Screen* s;
int i, x = Pointers[idx].x, y = Pointers[idx].y;
for (i = 0; i < Gui_ScreensCount; i++) {
s = Gui_Screens[i];
if (s->VTABLE->HandlesPointerDown(s, 1 << idx, x, y)) return;
}
}
static void HandlePointerUp(void* obj, int idx) {
struct Screen* s;
int i, x = Pointers[idx].x, y = Pointers[idx].y;
for (i = 0; i < Gui_ScreensCount; i++) {
s = Gui_Screens[i];
if (s->VTABLE->HandlesPointerUp(s, 1 << idx, x, y)) return;
}
}
static void HandleInputDown(void* obj, int key, bool was) {
struct Screen* s;
int i;
#ifndef CC_BUILD_WEB
if (key == KEY_ESCAPE && (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_Close(); return;
} else if (key == KeyBinds[KEYBIND_SCREENSHOT] && !was) {
Game_ScreenshotRequested = true; return;
}
for (i = 0; i < Gui_ScreensCount; i++) {
s = Gui_Screens[i];
if (s->VTABLE->HandlesKeyDown(s, key)) return;
}
if ((key == KEY_ESCAPE || key == KEY_PAUSE) && !Gui_GetInputGrab()) {
#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
PauseScreen_Show(); return;
}
/* These should not be triggered multiple times when holding down */
if (was) return;
if (HandleBlockKey(key)) {
} else if (HandleCoreKey(key)) {
} else if (LocalPlayer_HandlesKey(key)) {
} else { HandleHotkeyDown(key); }
}
static void HandleInputUp(void* obj, int key) {
struct Screen* s;
int i;
if (key == KeyBinds[KEYBIND_ZOOM_SCROLL]) Game_SetFov(Game_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 == KEY_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];
if (s->VTABLE->HandlesKeyUp(s, key)) return;
}
if (Gui_GetInputGrab()) return;
if (key == KeyBinds[KEYBIND_DELETE_BLOCK]) MouseStateRelease(MOUSE_LEFT);
if (key == KeyBinds[KEYBIND_PLACE_BLOCK]) MouseStateRelease(MOUSE_RIGHT);
if (key == KeyBinds[KEYBIND_PICK_BLOCK]) MouseStateRelease(MOUSE_MIDDLE);
}
static void HandleKeyPress(void* obj, int keyChar) {
struct Screen* s;
int i;
for (i = 0; i < Gui_ScreensCount; i++) {
s = Gui_Screens[i];
if (s->VTABLE->HandlesKeyPress(s, keyChar)) return;
}
}
void InputHandler_Init(void) {
Event_RegisterMove(&PointerEvents.Moved, NULL, HandlePointerMove);
Event_RegisterInt(&PointerEvents.Down, NULL, HandlePointerDown);
Event_RegisterInt(&PointerEvents.Up, NULL, HandlePointerUp);
Event_RegisterInt(&InputEvents.Down, NULL, HandleInputDown);
Event_RegisterInt(&InputEvents.Up, NULL, HandleInputUp);
Event_RegisterInt(&InputEvents.Press, NULL, HandleKeyPress);
Event_RegisterFloat(&InputEvents.Wheel, NULL, HandleMouseWheel);
Event_RegisterVoid(&UserEvents.HackPermissionsChanged, NULL, InputHandler_CheckZoomFov);
KeyBind_Init();
Hotkeys_Init();
}

View File

@ -1,8 +1,9 @@
#ifndef CC_INPUT_H
#define CC_INPUT_H
#include "String.h"
/* Manages the keyboard, and raises events when keys are pressed etc.
Copyright 2014-2019 ClassiCube | Licensed under BSD-3 | Based on OpenTK code
/* Manages keyboard, mouse, and touch state.
Raises events when keys are pressed etc, and implements base handlers for them.
Copyright 2014-2019 ClassiCube | Licensed under BSD-3
*/
enum Key_ {
@ -127,9 +128,6 @@ extern const cc_uint8 KeyBind_Defaults[KEYBIND_COUNT];
bool KeyBind_IsPressed(KeyBind binding);
/* Set the key that the given key binding is bound to. (also updates options list) */
void KeyBind_Set(KeyBind binding, Key key);
/* Initalises and loads key bindings from options. */
void KeyBind_Init(void);
extern const cc_uint8 Hotkeys_LWJGL[256];
struct HotkeyData {
@ -153,10 +151,13 @@ bool Hotkeys_Remove(Key trigger, cc_uint8 modifiers);
/* Returns the first hotkey which is bound to the given key and has its modifiers pressed. */
/* NOTE: The hotkeys list is sorted, so hotkeys with most modifiers are checked first. */
int Hotkeys_FindPartial(Key key);
/* Initalises and loads hotkeys from options. */
void Hotkeys_Init(void);
/* Called when user has removed a hotkey. (removes it from options) */
void Hotkeys_UserRemovedHotkey(Key trigger, cc_uint8 modifiers);
/* Called when user has added a hotkey. (Adds it to options) */
void Hotkeys_UserAddedHotkey(Key trigger, cc_uint8 modifiers, bool moreInput, const String* text);
bool InputHandler_SetFOV(int fov);
void InputHandler_PickBlocks(void);
void InputHandler_Init(void);
void InputHandler_OnScreensChanged(void);
#endif

View File

@ -1,580 +0,0 @@
#include "InputHandler.h"
#include "Utils.h"
#include "Server.h"
#include "HeldBlockRenderer.h"
#include "Game.h"
#include "Platform.h"
#include "ExtMath.h"
#include "Camera.h"
#include "Inventory.h"
#include "World.h"
#include "Event.h"
#include "Window.h"
#include "Entity.h"
#include "Chat.h"
#include "Funcs.h"
#include "Screens.h"
#include "Block.h"
#include "Menus.h"
#include "Gui.h"
#include "Protocol.h"
#include "AxisLinesRenderer.h"
static bool input_buttonsDown[3];
static int input_pickingId = -1;
static const short normDists[10] = { 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096 };
static const short classicDists[4] = { 8, 32, 128, 512 };
static TimeMS input_lastClick;
static float input_fovIndex = -1.0f;
#ifdef CC_BUILD_WEB
static bool suppressEscape;
#endif
enum MouseButton_ { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
/*########################################################################################################################*
*-----------------------------------------------------Mouse helpers-------------------------------------------------------*
*#########################################################################################################################*/
static void MouseStateUpdate(int button, bool pressed) {
struct Entity* p;
/* defer getting the targeted entity, as it's a costly operation */
if (input_pickingId == -1) {
p = &LocalPlayer_Instance.Base;
input_pickingId = Entities_GetCloset(p);
}
input_buttonsDown[button] = pressed;
CPE_SendPlayerClick(button, pressed, (EntityID)input_pickingId, &Game_SelectedPos);
}
static void MouseStateChanged(int button, bool pressed) {
if (!Server.SupportsPlayerClick) return;
if (pressed) {
/* Can send multiple Pressed events */
MouseStateUpdate(button, true);
} else {
if (!input_buttonsDown[button]) return;
MouseStateUpdate(button, false);
}
}
static void MouseStatePress(int button) {
input_lastClick = DateTime_CurrentUTC_MS();
input_pickingId = -1;
MouseStateChanged(button, true);
}
static void MouseStateRelease(int button) {
input_pickingId = -1;
MouseStateChanged(button, false);
}
void InputHandler_OnScreensChanged(void) {
input_lastClick = DateTime_CurrentUTC_MS();
input_pickingId = -1;
MouseStateChanged(MOUSE_LEFT, false);
MouseStateChanged(MOUSE_RIGHT, false);
MouseStateChanged(MOUSE_MIDDLE, false);
}
static bool TouchesSolid(BlockID b) { return Blocks.Collide[b] == COLLIDE_SOLID; }
static bool PushbackPlace(struct AABB* blockBB) {
struct Entity* p = &LocalPlayer_Instance.Base;
struct HacksComp* hacks = &LocalPlayer_Instance.Hacks;
Face closestFace;
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;
}
LocationUpdate_MakePos(&update, pos, false);
p->VTABLE->SetLocation(p, &update, false);
return true;
}
static bool IntersectsOthers(Vec3 pos, BlockID block) {
struct AABB blockBB, entityBB;
struct Entity* entity;
int id;
Vec3_Add(&blockBB.Min, &pos, &Blocks.MinBB[block]);
Vec3_Add(&blockBB.Max, &pos, &Blocks.MaxBB[block]);
for (id = 0; id < ENTITIES_SELF_ID; id++) {
entity = Entities.List[id];
if (!entity) continue;
Entity_GetBounds(entity, &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 bool CheckIsFree(BlockID block) {
struct Entity* p = &LocalPlayer_Instance.Base;
struct HacksComp* hacks = &LocalPlayer_Instance.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 = LocalPlayer_Instance.Interp.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;
LocationUpdate_MakePos(&update, nextPos, false);
p->VTABLE->SetLocation(p, &update, false);
return true;
}
static 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.BlockPos;
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);
}
static 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;
if (!CheckIsFree(block)) return;
Game_ChangeBlock(pos.X, pos.Y, pos.Z, block);
Event_RaiseBlock(&UserEvents.BlockChanged, pos, old, block);
}
static void InputHandler_PickBlock(void) {
IVec3 pos;
BlockID cur;
pos = Game_SelectedPos.BlockPos;
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_PickBlocks(void) {
bool left, middle, right;
TimeMS now = DateTime_CurrentUTC_MS();
int delta = (int)(now - input_lastClick);
if (delta < 250) return; /* 4 times per second */
input_lastClick = now;
if (Gui_GetInputGrab()) return;
left = KeyBind_IsPressed(KEYBIND_DELETE_BLOCK);
middle = KeyBind_IsPressed(KEYBIND_PICK_BLOCK);
right = KeyBind_IsPressed(KEYBIND_PLACE_BLOCK);
if (Server.SupportsPlayerClick) {
input_pickingId = -1;
MouseStateChanged(MOUSE_LEFT, left);
MouseStateChanged(MOUSE_RIGHT, right);
MouseStateChanged(MOUSE_MIDDLE, middle);
}
if (left) {
InputHandler_DeleteBlock();
} else if (right) {
InputHandler_PlaceBlock();
} else if (middle) {
InputHandler_PickBlock();
}
}
/*########################################################################################################################*
*------------------------------------------------------Key helpers--------------------------------------------------------*
*#########################################################################################################################*/
static bool InputHandler_IsShutdown(Key key) {
if (key == KEY_F4 && Key_IsAltPressed()) return true;
/* On OSX, Cmd+Q should also terminate the process */
#ifdef CC_BUILD_OSX
return key == 'Q' && Key_IsWinPressed();
#else
return false;
#endif
}
static void InputHandler_Toggle(Key key, bool* target, const char* enableMsg, const char* disableMsg) {
*target = !(*target);
if (*target) {
Chat_Add2("%c. &ePress &a%c &eto disable.", enableMsg, Input_Names[key]);
} else {
Chat_Add2("%c. &ePress &a%c &eto re-enable.", disableMsg, Input_Names[key]);
}
}
static void InputHandler_CycleDistanceForwards(const short* viewDists, int count) {
int i, dist;
for (i = 0; i < count; i++) {
dist = viewDists[i];
if (dist > Game_UserViewDistance) {
Game_UserSetViewDistance(dist); return;
}
}
Game_UserSetViewDistance(viewDists[0]);
}
static void InputHandler_CycleDistanceBackwards(const short* viewDists, int count) {
int i, dist;
for (i = count - 1; i >= 0; i--) {
dist = viewDists[i];
if (dist < Game_UserViewDistance) {
Game_UserSetViewDistance(dist); return;
}
}
Game_UserSetViewDistance(viewDists[count - 1]);
}
bool InputHandler_SetFOV(int fov) {
struct HacksComp* h = &LocalPlayer_Instance.Hacks;
if (!h->Enabled || !h->CanUseThirdPersonCamera) return false;
Game_ZoomFov = fov;
Game_SetFov(fov);
return true;
}
static bool InputHandler_DoFovZoom(float deltaPrecise) {
struct HacksComp* h;
if (!KeyBind_IsPressed(KEYBIND_ZOOM_SCROLL)) return false;
h = &LocalPlayer_Instance.Hacks;
if (!h->Enabled || !h->CanUseThirdPersonCamera) return false;
if (input_fovIndex == -1.0f) input_fovIndex = (float)Game_ZoomFov;
input_fovIndex -= deltaPrecise * 5.0f;
Math_Clamp(input_fovIndex, 1.0f, Game_DefaultFov);
return InputHandler_SetFOV((int)input_fovIndex);
}
static void InputHandler_CheckZoomFov(void* obj) {
struct HacksComp* h = &LocalPlayer_Instance.Hacks;
if (!h->Enabled || !h->CanUseThirdPersonCamera) Game_SetFov(Game_DefaultFov);
}
static bool HandleBlockKey(Key key) {
if (Gui_GetInputGrab()) return false;
if (key == KeyBinds[KEYBIND_DELETE_BLOCK]) {
MouseStatePress(MOUSE_LEFT);
InputHandler_DeleteBlock();
} else if (key == KeyBinds[KEYBIND_PLACE_BLOCK]) {
MouseStatePress(MOUSE_RIGHT);
InputHandler_PlaceBlock();
} else if (key == KeyBinds[KEYBIND_PICK_BLOCK]) {
MouseStatePress(MOUSE_MIDDLE);
InputHandler_PickBlock();
} else {
return false;
}
return true;
}
static bool HandleNonClassicKey(Key key) {
if (key == KeyBinds[KEYBIND_HIDE_GUI]) {
Game_HideGui = !Game_HideGui;
} else if (key == KeyBinds[KEYBIND_SMOOTH_CAMERA]) {
InputHandler_Toggle(key, &Camera.Smooth,
" &eSmooth camera is &aenabled",
" &eSmooth camera is &cdisabled");
} else if (key == KeyBinds[KEYBIND_AXIS_LINES]) {
InputHandler_Toggle(key, &AxisLinesRenderer_Enabled,
" &eAxis lines (&4X&e, &2Y&e, &1Z&e) now show",
" &eAxis lines no longer show");
} else if (key == KeyBinds[KEYBIND_AUTOROTATE]) {
InputHandler_Toggle(key, &AutoRotate_Enabled,
" &eAuto rotate is &aenabled",
" &eAuto rotate is &cdisabled");
} else if (key == KeyBinds[KEYBIND_THIRD_PERSON]) {
Camera_CycleActive();
} else if (key == KeyBinds[KEYBIND_DROP_BLOCK]) {
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);
}
} else if (key == KeyBinds[KEYBIND_IDOVERLAY]) {
TexIdsOverlay_Show();
} else if (key == KeyBinds[KEYBIND_BREAK_LIQUIDS]) {
InputHandler_Toggle(key, &Game_BreakableLiquids,
" &eBreakable liquids is &aenabled",
" &eBreakable liquids is &cdisabled");
} else {
return false;
}
return true;
}
static bool HandleCoreKey(Key key) {
if (key == KeyBinds[KEYBIND_HIDE_FPS]) {
Gui_ShowFPS = !Gui_ShowFPS;
} else if (key == KeyBinds[KEYBIND_FULLSCREEN]) {
int state = Window_GetWindowState();
if (state == WINDOW_STATE_FULLSCREEN) {
Window_ExitFullscreen();
} else if (state != WINDOW_STATE_MINIMISED) {
Window_EnterFullscreen();
}
} else if (key == KeyBinds[KEYBIND_FOG]) {
const short* viewDists = Gui_ClassicMenu ? classicDists : normDists;
int count = Gui_ClassicMenu ? Array_Elems(classicDists) : Array_Elems(normDists);
if (Key_IsShiftPressed()) {
InputHandler_CycleDistanceBackwards(viewDists, count);
} else {
InputHandler_CycleDistanceForwards(viewDists, count);
}
} else if (key == KEY_F5 && Game_ClassicMode) {
int weather = Env.Weather == WEATHER_SUNNY ? WEATHER_RAINY : WEATHER_SUNNY;
Env_SetWeather(weather);
} else {
if (Game_ClassicMode) return false;
return HandleNonClassicKey(key);
}
return true;
}
static void HandleHotkeyDown(Key key) {
struct HotkeyData* hkey;
String text;
int i = Hotkeys_FindPartial(key);
if (i == -1) return;
hkey = &HotkeysList[i];
text = StringsBuffer_UNSAFE_Get(&HotkeysText, hkey->TextIndex);
if (!hkey->StaysOpen) {
Chat_Send(&text, false);
} else if (!Gui_GetInputGrab()) {
HUDScreen_OpenInput(&text);
}
}
/*########################################################################################################################*
*-----------------------------------------------------Base handlers-------------------------------------------------------*
*#########################################################################################################################*/
static void HandleMouseWheel(void* obj, float delta) {
struct Screen* s;
int i;
struct Widget* widget;
bool hotbar;
for (i = 0; i < Gui_ScreensCount; i++) {
s = Gui_Screens[i];
if (s->VTABLE->HandlesMouseScroll(s, delta)) return;
}
hotbar = Key_IsAltPressed() || Key_IsControlPressed() || Key_IsShiftPressed();
if (!hotbar && Camera.Active->Zoom(delta)) return;
if (InputHandler_DoFovZoom(delta) || !Inventory.CanChangeSelected) return;
widget = HUDScreen_GetHotbar();
Elem_HandlesMouseScroll(widget, delta);
}
static void HandlePointerMove(void* obj, int idx, int xDelta, int yDelta) {
struct Screen* s;
int i, x = Pointers[idx].x, y = Pointers[idx].y;
for (i = 0; i < Gui_ScreensCount; i++) {
s = Gui_Screens[i];
if (s->VTABLE->HandlesPointerMove(s, 1 << idx, x, y)) return;
}
}
static void HandlePointerDown(void* obj, int idx) {
struct Screen* s;
int i, x = Pointers[idx].x, y = Pointers[idx].y;
for (i = 0; i < Gui_ScreensCount; i++) {
s = Gui_Screens[i];
if (s->VTABLE->HandlesPointerDown(s, 1 << idx, x, y)) return;
}
}
static void HandlePointerUp(void* obj, int idx) {
struct Screen* s;
int i, x = Pointers[idx].x, y = Pointers[idx].y;
for (i = 0; i < Gui_ScreensCount; i++) {
s = Gui_Screens[i];
if (s->VTABLE->HandlesPointerUp(s, 1 << idx, x, y)) return;
}
}
static void HandleInputDown(void* obj, int key, bool was) {
struct Screen* s;
int i;
#ifndef CC_BUILD_WEB
if (key == KEY_ESCAPE && (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_Close(); return;
} else if (key == KeyBinds[KEYBIND_SCREENSHOT] && !was) {
Game_ScreenshotRequested = true; return;
}
for (i = 0; i < Gui_ScreensCount; i++) {
s = Gui_Screens[i];
if (s->VTABLE->HandlesKeyDown(s, key)) return;
}
if ((key == KEY_ESCAPE || key == KEY_PAUSE) && !Gui_GetInputGrab()) {
#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
PauseScreen_Show(); return;
}
/* These should not be triggered multiple times when holding down */
if (was) return;
if (HandleBlockKey(key)) {
} else if (HandleCoreKey(key)) {
} else if (LocalPlayer_HandlesKey(key)) {
} else { HandleHotkeyDown(key); }
}
static void HandleInputUp(void* obj, int key) {
struct Screen* s;
int i;
if (key == KeyBinds[KEYBIND_ZOOM_SCROLL]) Game_SetFov(Game_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 == KEY_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];
if (s->VTABLE->HandlesKeyUp(s, key)) return;
}
if (Gui_GetInputGrab()) return;
if (key == KeyBinds[KEYBIND_DELETE_BLOCK]) MouseStateRelease(MOUSE_LEFT);
if (key == KeyBinds[KEYBIND_PLACE_BLOCK]) MouseStateRelease(MOUSE_RIGHT);
if (key == KeyBinds[KEYBIND_PICK_BLOCK]) MouseStateRelease(MOUSE_MIDDLE);
}
static void HandleKeyPress(void* obj, int keyChar) {
struct Screen* s;
int i;
for (i = 0; i < Gui_ScreensCount; i++) {
s = Gui_Screens[i];
if (s->VTABLE->HandlesKeyPress(s, keyChar)) return;
}
}
void InputHandler_Init(void) {
Event_RegisterMove(&PointerEvents.Moved, NULL, HandlePointerMove);
Event_RegisterInt(&PointerEvents.Down, NULL, HandlePointerDown);
Event_RegisterInt(&PointerEvents.Up, NULL, HandlePointerUp);
Event_RegisterInt(&InputEvents.Down, NULL, HandleInputDown);
Event_RegisterInt(&InputEvents.Up, NULL, HandleInputUp);
Event_RegisterInt(&InputEvents.Press, NULL, HandleKeyPress);
Event_RegisterFloat(&InputEvents.Wheel, NULL, HandleMouseWheel);
Event_RegisterVoid(&UserEvents.HackPermissionsChanged, NULL, InputHandler_CheckZoomFov);
KeyBind_Init();
Hotkeys_Init();
}

View File

@ -1,13 +0,0 @@
#ifndef CC_INPUTHANDLER_H
#define CC_INPUTHANDLER_H
#include "Input.h"
/* Implements base handlers for mouse and keyboard input.
Copyright 2014-2019 ClassiCube | Licensed under BSD-3
*/
struct Screen;
bool InputHandler_SetFOV(int fov);
void InputHandler_PickBlocks(void);
void InputHandler_Init(void);
void InputHandler_OnScreensChanged(void);
#endif