From 22ef4c8be17528d7fa23c7a0557d49aeda6f2e3a Mon Sep 17 00:00:00 2001 From: grorp Date: Tue, 1 Oct 2024 17:21:42 +0200 Subject: [PATCH] Expose analog joystick input to the Lua API (#14348) --- doc/lua_api.md | 14 ++++-- src/client/client.cpp | 17 +++++-- src/client/game.cpp | 5 ++- src/client/inputhandler.cpp | 67 +++++----------------------- src/client/inputhandler.h | 16 +++---- src/client/localplayer.h | 2 + src/gui/touchcontrols.h | 4 +- src/network/networkprotocol.h | 2 + src/network/serverpackethandler.cpp | 14 +++++- src/player.cpp | 41 +++++++++++++++++ src/player.h | 8 +++- src/script/lua_api/l_localplayer.cpp | 13 +++--- src/script/lua_api/l_object.cpp | 7 +++ 13 files changed, 127 insertions(+), 83 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index 9c888ff13..4bf7d31c1 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -8249,12 +8249,18 @@ child will follow movement and rotation of that bone. bgcolor[], any non-style elements (eg: label) may result in weird behavior. * Only affects formspecs shown after this is called. * `get_formspec_prepend()`: returns a formspec string. -* `get_player_control()`: returns table with player pressed keys - * The table consists of fields with the following boolean values - representing the pressed keys: `up`, `down`, `left`, `right`, `jump`, - `aux1`, `sneak`, `dig`, `place`, `LMB`, `RMB`, and `zoom`. +* `get_player_control()`: returns table with player input + * The table contains the following boolean fields representing the pressed + keys: `up`, `down`, `left`, `right`, `jump`, `aux1`, `sneak`, `dig`, + `place`, `LMB`, `RMB` and `zoom`. * The fields `LMB` and `RMB` are equal to `dig` and `place` respectively, and exist only to preserve backwards compatibility. + * The table also contains the fields `movement_x` and `movement_y`. + * They represent the movement of the player. Values are numbers in the + range [-1.0,+1.0]. + * They take both keyboard and joystick input into account. + * You should prefer them over `up`, `down`, `left` and `right` to + support different input methods correctly. * Returns an empty table `{}` if the object is not a player. * `get_player_control_bits()`: returns integer with bit packed player pressed keys. diff --git a/src/client/client.cpp b/src/client/client.cpp index 1a2f51db9..a7b714069 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -1034,7 +1034,7 @@ void Client::Send(NetworkPacket* pkt) m_con->Send(PEER_ID_SERVER, scf.channel, pkt, scf.reliable); } -// Will fill up 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 bytes +// Will fill up 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 + 4 + 4 bytes void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket *pkt, bool camera_inverted) { v3f pf = myplayer->getPosition() * 100; @@ -1046,6 +1046,8 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket * u8 fov = std::fmin(255.0f, clientMap->getCameraFov() * 80.0f); u8 wanted_range = std::fmin(255.0f, std::ceil(clientMap->getWantedRange() * (1.0f / MAP_BLOCKSIZE))); + f32 movement_speed = myplayer->control.movement_speed; + f32 movement_dir = myplayer->control.movement_direction; v3s32 position(pf.X, pf.Y, pf.Z); v3s32 speed(sf.X, sf.Y, sf.Z); @@ -1060,10 +1062,13 @@ void writePlayerPos(LocalPlayer *myplayer, ClientMap *clientMap, NetworkPacket * [12+12+4+4+4] u8 fov*80 [12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE) [12+12+4+4+4+1+1] u8 camera_inverted (bool) + [12+12+4+4+4+1+1+1] f32 movement_speed + [12+12+4+4+4+1+1+1+4] f32 movement_direction */ *pkt << position << speed << pitch << yaw << keyPressed; *pkt << fov << wanted_range; *pkt << camera_inverted; + *pkt << movement_speed << movement_dir; } void Client::interact(InteractAction action, const PointedThing& pointed) @@ -1397,6 +1402,8 @@ void Client::sendPlayerPos() u32 keyPressed = player->control.getKeysPressed(); bool camera_inverted = m_camera->getCameraMode() == CAMERA_MODE_THIRD_FRONT; + f32 movement_speed = player->control.movement_speed; + f32 movement_dir = player->control.movement_direction; if ( player->last_position == player->getPosition() && @@ -1406,7 +1413,9 @@ void Client::sendPlayerPos() player->last_keyPressed == keyPressed && player->last_camera_fov == camera_fov && player->last_camera_inverted == camera_inverted && - player->last_wanted_range == wanted_range) + player->last_wanted_range == wanted_range && + player->last_movement_speed == movement_speed && + player->last_movement_dir == movement_dir) return; player->last_position = player->getPosition(); @@ -1417,8 +1426,10 @@ void Client::sendPlayerPos() player->last_camera_fov = camera_fov; player->last_camera_inverted = camera_inverted; player->last_wanted_range = wanted_range; + player->last_movement_speed = movement_speed; + player->last_movement_dir = movement_dir; - NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1); + NetworkPacket pkt(TOSERVER_PLAYERPOS, 12 + 12 + 4 + 4 + 4 + 1 + 1 + 1 + 4 + 4); writePlayerPos(player, &map, &pkt, camera_inverted); diff --git a/src/client/game.cpp b/src/client/game.cpp index 6cbf54f5a..9dd3a0e83 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -2752,9 +2752,10 @@ void Game::updatePlayerControl(const CameraOrientation &cam) isKeyDown(KeyType::PLACE), cam.camera_pitch, cam.camera_yaw, - input->getMovementSpeed(), - input->getMovementDirection() + input->getJoystickSpeed(), + input->getJoystickDirection() ); + control.setMovementFromKeys(); // autoforward if set: move at maximum speed if (player->getPlayerSettings().continuous_forward && diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp index 39c212d2f..168ef1193 100644 --- a/src/client/inputhandler.cpp +++ b/src/client/inputhandler.cpp @@ -220,48 +220,19 @@ bool MyEventReceiver::OnEvent(const SEvent &event) /* * RealInputHandler */ -float RealInputHandler::getMovementSpeed() +float RealInputHandler::getJoystickSpeed() { - bool f = m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD]), - b = m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD]), - l = m_receiver->IsKeyDown(keycache.key[KeyType::LEFT]), - r = m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT]); - if (f || b || l || r) - { - // if contradictory keys pressed, stay still - if (f && b && l && r) - return 0.0f; - else if (f && b && !l && !r) - return 0.0f; - else if (!f && !b && l && r) - return 0.0f; - return 1.0f; // If there is a keyboard event, assume maximum speed - } - if (g_touchcontrols && g_touchcontrols->getMovementSpeed()) - return g_touchcontrols->getMovementSpeed(); + if (g_touchcontrols && g_touchcontrols->getJoystickSpeed()) + return g_touchcontrols->getJoystickSpeed(); return joystick.getMovementSpeed(); } -float RealInputHandler::getMovementDirection() +float RealInputHandler::getJoystickDirection() { - float x = 0, z = 0; - - /* Check keyboard for input */ - if (m_receiver->IsKeyDown(keycache.key[KeyType::FORWARD])) - z += 1; - if (m_receiver->IsKeyDown(keycache.key[KeyType::BACKWARD])) - z -= 1; - if (m_receiver->IsKeyDown(keycache.key[KeyType::RIGHT])) - x += 1; - if (m_receiver->IsKeyDown(keycache.key[KeyType::LEFT])) - x -= 1; - - if (x != 0 || z != 0) /* If there is a keyboard event, it takes priority */ - return std::atan2(x, z); - // `getMovementDirection() == 0` means forward, so we cannot use - // `getMovementDirection()` as a condition. - else if (g_touchcontrols && g_touchcontrols->getMovementSpeed()) - return g_touchcontrols->getMovementDirection(); + // `getJoystickDirection() == 0` means forward, so we cannot use + // `getJoystickDirection()` as a condition. + if (g_touchcontrols && g_touchcontrols->getJoystickSpeed()) + return g_touchcontrols->getJoystickDirection(); return joystick.getMovementDirection(); } @@ -320,25 +291,11 @@ void RandomInputHandler::step(float dtime) counterMovement -= dtime; if (counterMovement < 0.0) { counterMovement = 0.1 * Rand(1, 40); - movementSpeed = Rand(0,100)*0.01; - movementDirection = Rand(-100, 100)*0.01 * M_PI; + joystickSpeed = Rand(0,100)*0.01; + joystickDirection = Rand(-100, 100)*0.01 * M_PI; } } else { - bool f = keydown[keycache.key[KeyType::FORWARD]], - l = keydown[keycache.key[KeyType::LEFT]]; - if (f || l) { - movementSpeed = 1.0f; - if (f && !l) - movementDirection = 0.0; - else if (!f && l) - movementDirection = -M_PI_2; - else if (f && l) - movementDirection = -M_PI_4; - else - movementDirection = 0.0; - } else { - movementSpeed = 0.0; - movementDirection = 0.0; - } + joystickSpeed = 0.0f; + joystickDirection = 0.0f; } } diff --git a/src/client/inputhandler.h b/src/client/inputhandler.h index daf01c488..8efefce5b 100644 --- a/src/client/inputhandler.h +++ b/src/client/inputhandler.h @@ -247,8 +247,8 @@ public: virtual bool wasKeyReleased(GameKeyType k) = 0; virtual bool cancelPressed() = 0; - virtual float getMovementSpeed() = 0; - virtual float getMovementDirection() = 0; + virtual float getJoystickSpeed() = 0; + virtual float getJoystickDirection() = 0; virtual void clearWasKeyPressed() {} virtual void clearWasKeyReleased() {} @@ -304,9 +304,9 @@ public: return m_receiver->WasKeyReleased(keycache.key[k]) || joystick.wasKeyReleased(k); } - virtual float getMovementSpeed(); + virtual float getJoystickSpeed(); - virtual float getMovementDirection(); + virtual float getJoystickDirection(); virtual bool cancelPressed() { @@ -388,8 +388,8 @@ public: virtual bool wasKeyPressed(GameKeyType k) { return false; } virtual bool wasKeyReleased(GameKeyType k) { return false; } virtual bool cancelPressed() { return false; } - virtual float getMovementSpeed() { return movementSpeed; } - virtual float getMovementDirection() { return movementDirection; } + virtual float getJoystickSpeed() { return joystickSpeed; } + virtual float getJoystickDirection() { return joystickDirection; } virtual v2s32 getMousePos() { return mousepos; } virtual void setMousePos(s32 x, s32 y) { mousepos = v2s32(x, y); } @@ -403,6 +403,6 @@ private: KeyList keydown; v2s32 mousepos; v2s32 mousespeed; - float movementSpeed; - float movementDirection; + float joystickSpeed; + float joystickDirection; }; diff --git a/src/client/localplayer.h b/src/client/localplayer.h index 815fafa8b..275f556e4 100644 --- a/src/client/localplayer.h +++ b/src/client/localplayer.h @@ -105,6 +105,8 @@ public: u8 last_camera_fov = 0; u8 last_wanted_range = 0; bool last_camera_inverted = false; + f32 last_movement_speed = 0.0f; + f32 last_movement_dir = 0.0f; float camera_impact = 0.0f; diff --git a/src/gui/touchcontrols.h b/src/gui/touchcontrols.h index 102c85f09..1787f6a5d 100644 --- a/src/gui/touchcontrols.h +++ b/src/gui/touchcontrols.h @@ -163,8 +163,8 @@ public: */ line3d getShootline() { return m_shootline; } - float getMovementDirection() { return m_joystick_direction; } - float getMovementSpeed() { return m_joystick_speed; } + float getJoystickDirection() { return m_joystick_direction; } + float getJoystickSpeed() { return m_joystick_speed; } void step(float dtime); inline void setUseCrosshair(bool use_crosshair) { m_draw_crosshair = use_crosshair; } diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index aeb827608..900492d05 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -962,6 +962,8 @@ enum ToServerCommand : u16 [2+12+12+4+4+4] u8 fov*80 [2+12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE) [2+12+12+4+4+4+1+1] u8 camera_inverted (bool) + [2+12+12+4+4+4+1+1+1] f32 movement_speed + [2+12+12+4+4+4+1+1+1+4] f32 movement_direction */ diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index c17d32e41..3e60e8cc8 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -477,12 +477,24 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, u8 bits = 0; // bits instead of bool so it is extensible later *pkt >> keyPressed; + player->control.unpackKeysPressed(keyPressed); + *pkt >> f32fov; fov = (f32)f32fov / 80.0f; *pkt >> wanted_range; + if (pkt->getRemainingBytes() >= 1) *pkt >> bits; + if (pkt->getRemainingBytes() >= 8) { + *pkt >> player->control.movement_speed; + *pkt >> player->control.movement_direction; + } else { + player->control.movement_speed = 0.0f; + player->control.movement_direction = 0.0f; + player->control.setMovementFromKeys(); + } + v3f position((f32)ps.X / 100.0f, (f32)ps.Y / 100.0f, (f32)ps.Z / 100.0f); v3f speed((f32)ss.X / 100.0f, (f32)ss.Y / 100.0f, (f32)ss.Z / 100.0f); @@ -501,8 +513,6 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, playersao->setWantedRange(wanted_range); playersao->setCameraInverted(bits & 0x01); - player->control.unpackKeysPressed(keyPressed); - if (playersao->checkMovementCheat()) { // Call callbacks m_script->on_cheat(playersao, "moved_too_fast"); diff --git a/src/player.cpp b/src/player.cpp index fd25626ca..7361549e0 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -173,6 +173,42 @@ u16 Player::getMaxHotbarItemcount() return mainlist ? std::min(mainlist->getSize(), (u32) hud_hotbar_itemcount) : 0; } +void PlayerControl::setMovementFromKeys() +{ + bool a_up = direction_keys & (1 << 0), + a_down = direction_keys & (1 << 1), + a_left = direction_keys & (1 << 2), + a_right = direction_keys & (1 << 3); + + if (a_up || a_down || a_left || a_right) { + // if contradictory keys pressed, stay still + if (a_up && a_down && a_left && a_right) + movement_speed = 0.0f; + else if (a_up && a_down && !a_left && !a_right) + movement_speed = 0.0f; + else if (!a_up && !a_down && a_left && a_right) + movement_speed = 0.0f; + else + // If there is a keyboard event, assume maximum speed + movement_speed = 1.0f; + } + + // Check keyboard for input + float x = 0, y = 0; + if (a_up) + y += 1; + if (a_down) + y -= 1; + if (a_left) + x -= 1; + if (a_right) + x += 1; + + if (x != 0 || y != 0) + // If there is a keyboard event, it takes priority + movement_direction = std::atan2(x, y); +} + #ifndef SERVER u32 PlayerControl::getKeysPressed() const @@ -231,6 +267,11 @@ void PlayerControl::unpackKeysPressed(u32 keypress_bits) zoom = keypress_bits & (1 << 9); } +v2f PlayerControl::getMovement() const +{ + return v2f(std::sin(movement_direction), std::cos(movement_direction)) * movement_speed; +} + static auto tie(const PlayerPhysicsOverride &o) { // Make sure to add new members to this list! diff --git a/src/player.h b/src/player.h index 53411fea4..972a04e02 100644 --- a/src/player.h +++ b/src/player.h @@ -86,6 +86,11 @@ struct PlayerControl movement_direction = a_movement_direction; } + // Sets movement_speed and movement_direction according to direction_keys + // if direction_keys != 0, otherwise leaves them unchanged to preserve + // joystick input. + void setMovementFromKeys(); + #ifndef SERVER // For client use u32 getKeysPressed() const; @@ -94,6 +99,7 @@ struct PlayerControl // For server use void unpackKeysPressed(u32 keypress_bits); + v2f getMovement() const; u8 direction_keys = 0; bool jump = false; @@ -102,7 +108,7 @@ struct PlayerControl bool zoom = false; bool dig = false; bool place = false; - // Note: These four are NOT available on the server + // Note: These two are NOT available on the server float pitch = 0.0f; float yaw = 0.0f; float movement_speed = 0.0f; diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp index cfb7484df..73240ce9b 100644 --- a/src/script/lua_api/l_localplayer.cpp +++ b/src/script/lua_api/l_localplayer.cpp @@ -260,12 +260,13 @@ int LuaLocalPlayer::l_get_control(lua_State *L) set("zoom", c.zoom); set("dig", c.dig); set("place", c.place); - // Player movement in polar coordinates and non-binary speed - lua_pushnumber(L, c.movement_speed); - lua_setfield(L, -2, "movement_speed"); - lua_pushnumber(L, c.movement_direction); - lua_setfield(L, -2, "movement_direction"); - // Provide direction keys to ensure compatibility + + v2f movement = c.getMovement(); + lua_pushnumber(L, movement.X); + lua_setfield(L, -2, "movement_x"); + lua_pushnumber(L, movement.Y); + lua_setfield(L, -2, "movement_y"); + set("up", c.direction_keys & (1 << 0)); set("down", c.direction_keys & (1 << 1)); set("left", c.direction_keys & (1 << 2)); diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index ebd1bde71..1a732d9c6 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -1622,6 +1622,13 @@ int ObjectRef::l_get_player_control(lua_State *L) lua_setfield(L, -2, "dig"); lua_pushboolean(L, control.place); lua_setfield(L, -2, "place"); + + v2f movement = control.getMovement(); + lua_pushnumber(L, movement.X); + lua_setfield(L, -2, "movement_x"); + lua_pushnumber(L, movement.Y); + lua_setfield(L, -2, "movement_y"); + // Legacy fields to ensure mod compatibility lua_pushboolean(L, control.dig); lua_setfield(L, -2, "LMB");