From ba62808fe8b1d980915d686921482b9de0d08502 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 19 Feb 2025 18:45:45 +0100 Subject: [PATCH] Basic camera control API (#15796) --- doc/lua_api.md | 8 ++++ src/client/camera.cpp | 3 ++ src/client/camera.h | 7 ++-- src/client/client.h | 1 + src/client/clientevent.h | 10 +++-- src/client/game.cpp | 59 ++++++++++++++++++----------- src/network/clientopcodes.cpp | 2 +- src/network/clientpackethandler.cpp | 14 ++++++- src/network/networkprotocol.h | 5 +++ src/network/serveropcodes.cpp | 2 +- src/particles.h | 10 +---- src/player.cpp | 7 ++++ src/player.h | 13 +++++++ src/script/lua_api/l_object.cpp | 35 +++++++++++++++++ src/script/lua_api/l_object.h | 6 +++ src/server.cpp | 9 +++++ src/server.h | 3 ++ src/util/enum_string.h | 10 +++++ 18 files changed, 162 insertions(+), 42 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index ba72e97e9..4bcd0a1fb 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -8827,6 +8827,14 @@ child will follow movement and rotation of that bone. Same limits as for `thirdperson_back` apply. Defaults to `thirdperson_back` if unspecified. * `get_eye_offset()`: Returns camera offset vectors as set via `set_eye_offset`. +* `set_camera(params)`: Sets camera parameters. + * `mode`: Defines the camera mode used + - `any`: free choice between all modes (default) + - `first`: first-person camera + - `third`: third-person camera + - `third_front`: third-person camera, looking opposite of movement direction + * Supported by client since 5.12.0. +* `get_camera()`: Returns the camera parameters as a table as above. * `send_mapblock(blockpos)`: * Sends an already loaded mapblock to the player. * Returns `false` if nothing was sent (note that this can also mean that diff --git a/src/client/camera.cpp b/src/client/camera.cpp index 344676f6e..1eb5bc34d 100644 --- a/src/client/camera.cpp +++ b/src/client/camera.cpp @@ -375,6 +375,9 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 tool_reload_ratio) { v3f eye_offset = player->getEyeOffset(); switch(m_camera_mode) { + case CAMERA_MODE_ANY: + assert(false); + break; case CAMERA_MODE_FIRST: eye_offset += player->eye_offset_first; break; diff --git a/src/client/camera.h b/src/client/camera.h index fe05ed329..e23618258 100644 --- a/src/client/camera.h +++ b/src/client/camera.h @@ -57,8 +57,6 @@ struct Nametag } }; -enum CameraMode {CAMERA_MODE_FIRST, CAMERA_MODE_THIRD, CAMERA_MODE_THIRD_FRONT}; - /* Client camera class, manages the player and camera scene nodes, the viewing distance and performs view bobbing etc. It also displays the wielded tool in front of the @@ -169,7 +167,8 @@ public: void drawWieldedTool(irr::core::matrix4* translation=NULL); // Toggle the current camera mode - void toggleCameraMode() { + void toggleCameraMode() + { if (m_camera_mode == CAMERA_MODE_FIRST) m_camera_mode = CAMERA_MODE_THIRD; else if (m_camera_mode == CAMERA_MODE_THIRD) @@ -185,7 +184,7 @@ public: } //read the current camera mode - inline CameraMode getCameraMode() + inline CameraMode getCameraMode() const { return m_camera_mode; } diff --git a/src/client/client.h b/src/client/client.h index a02a7d8c6..7a183a4fe 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -217,6 +217,7 @@ public: void handleCommand_MediaPush(NetworkPacket *pkt); void handleCommand_MinimapModes(NetworkPacket *pkt); void handleCommand_SetLighting(NetworkPacket *pkt); + void handleCommand_Camera(NetworkPacket* pkt); void ProcessData(NetworkPacket *pkt); diff --git a/src/client/clientevent.h b/src/client/clientevent.h index f21b8220f..3d627c934 100644 --- a/src/client/clientevent.h +++ b/src/client/clientevent.h @@ -36,6 +36,7 @@ enum ClientEventType : u8 CE_SET_STARS, CE_OVERRIDE_DAY_NIGHT_RATIO, CE_CLOUD_PARAMS, + CE_UPDATE_CAMERA, CLIENTEVENT_MAX, }; @@ -66,11 +67,14 @@ struct ClientEventHudChange struct ClientEvent { + // TODO: should get rid of this ctor + ClientEvent() : type(CE_NONE) {} + + ClientEvent(ClientEventType type) : type(type) {} + ClientEventType type; union { - // struct{ - //} none; struct { u16 amount; @@ -86,8 +90,6 @@ struct ClientEvent std::string *formspec; std::string *formname; } show_formspec; - // struct{ - //} textures_updated; ParticleParameters *spawn_particle; struct { diff --git a/src/client/game.cpp b/src/client/game.cpp index acac7b0e7..12a39e6ee 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -564,6 +564,7 @@ protected: void updatePauseState(); void step(f32 dtime); void processClientEvents(CameraOrientation *cam); + void updateCameraMode(); // call after changing it void updateCameraOffset(); void updateCamera(f32 dtime); void updateSound(f32 dtime); @@ -665,6 +666,7 @@ private: void handleClientEvent_OverrideDayNigthRatio(ClientEvent *event, CameraOrientation *cam); void handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation *cam); + void handleClientEvent_UpdateCamera(ClientEvent *event, CameraOrientation *cam); void updateChat(f32 dtime); @@ -1921,6 +1923,9 @@ void Game::processKeyInput() toggleFog(); } else if (wasKeyDown(KeyType::TOGGLE_UPDATE_CAMERA)) { toggleUpdateCamera(); + } else if (wasKeyPressed(KeyType::CAMERA_MODE)) { + camera->toggleCameraMode(); + updateCameraMode(); } else if (wasKeyPressed(KeyType::TOGGLE_DEBUG)) { toggleDebug(); } else if (wasKeyPressed(KeyType::TOGGLE_PROFILER)) { @@ -2575,6 +2580,7 @@ const ClientEventHandler Game::clientEventHandler[CLIENTEVENT_MAX] = { {&Game::handleClientEvent_SetStars}, {&Game::handleClientEvent_OverrideDayNigthRatio}, {&Game::handleClientEvent_CloudParams}, + {&Game::handleClientEvent_UpdateCamera}, }; void Game::handleClientEvent_None(ClientEvent *event, CameraOrientation *cam) @@ -2879,6 +2885,13 @@ void Game::handleClientEvent_CloudParams(ClientEvent *event, CameraOrientation * clouds->setSpeed(v2f(event->cloud_params.speed_x, event->cloud_params.speed_y)); } +void Game::handleClientEvent_UpdateCamera(ClientEvent *event, CameraOrientation *cam) +{ + // no parameters to update here, this just makes sure the camera is in the + // state it should be after something was changed. + updateCameraMode(); +} + void Game::processClientEvents(CameraOrientation *cam) { while (client->hasClientEvents()) { @@ -2935,12 +2948,7 @@ void Game::updateCamera(f32 dtime) ClientEnvironment &env = client->getEnv(); LocalPlayer *player = env.getLocalPlayer(); - /* - For interaction purposes, get info about the held item - - What item is it? - - Is it a usable item? - - Can it point to liquids? - */ + // For interaction purposes, get info about the held item ItemStack playeritem; { ItemStack selected, hand; @@ -2950,23 +2958,6 @@ void Game::updateCamera(f32 dtime) ToolCapabilities playeritem_toolcap = playeritem.getToolCapabilities(itemdef_manager); - if (wasKeyPressed(KeyType::CAMERA_MODE)) { - GenericCAO *playercao = player->getCAO(); - - // If playercao not loaded, don't change camera - if (!playercao) - return; - - camera->toggleCameraMode(); - - if (g_touchcontrols) - g_touchcontrols->setUseCrosshair(!isTouchCrosshairDisabled()); - - // Make the player visible depending on camera mode. - playercao->updateMeshCulling(); - playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); - } - float full_punch_interval = playeritem_toolcap.full_punch_interval; float tool_reload_ratio = runData.time_from_last_punch / full_punch_interval; @@ -2981,6 +2972,25 @@ void Game::updateCamera(f32 dtime) } } +void Game::updateCameraMode() +{ + LocalPlayer *player = client->getEnv().getLocalPlayer(); + + // Obey server choice + if (player->allowed_camera_mode != CAMERA_MODE_ANY) + camera->setCameraMode(player->allowed_camera_mode); + + if (g_touchcontrols) + g_touchcontrols->setUseCrosshair(!isTouchCrosshairDisabled()); + + GenericCAO *playercao = player->getCAO(); + if (playercao) { + // Make the player visible depending on camera mode. + playercao->updateMeshCulling(); + playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST); + } +} + void Game::updateCameraOffset() { ClientEnvironment &env = client->getEnv(); @@ -3057,6 +3067,9 @@ void Game::processPlayerInteraction(f32 dtime, bool show_hud) core::line3d shootline; switch (camera->getCameraMode()) { + case CAMERA_MODE_ANY: + assert(false); + break; case CAMERA_MODE_FIRST: // Shoot from camera position, with bobbing shootline.start = camera->getPosition(); diff --git a/src/network/clientopcodes.cpp b/src/network/clientopcodes.cpp index 27d56c311..9a9cb5968 100644 --- a/src/network/clientopcodes.cpp +++ b/src/network/clientopcodes.cpp @@ -83,7 +83,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_MOVEMENT", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Movement }, // 0x45 { "TOCLIENT_SPAWN_PARTICLE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_SpawnParticle }, // 0x46 { "TOCLIENT_ADD_PARTICLESPAWNER", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_AddParticleSpawner }, // 0x47 - null_command_handler, + { "TOCLIENT_CAMERA", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Camera }, // 0x48 { "TOCLIENT_HUDADD", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudAdd }, // 0x49 { "TOCLIENT_HUDRM", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudRemove }, // 0x4a { "TOCLIENT_HUDCHANGE", TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HudChange }, // 0x4b diff --git a/src/network/clientpackethandler.cpp b/src/network/clientpackethandler.cpp index 1024988bd..3bceb2bbf 100644 --- a/src/network/clientpackethandler.cpp +++ b/src/network/clientpackethandler.cpp @@ -1530,7 +1530,19 @@ void Client::handleCommand_EyeOffset(NetworkPacket* pkt) *pkt >> player->eye_offset_third_front; } catch (PacketError &e) { player->eye_offset_third_front = player->eye_offset_third; - }; + } +} + +void Client::handleCommand_Camera(NetworkPacket* pkt) +{ + LocalPlayer *player = m_env.getLocalPlayer(); + assert(player); + + u8 tmp; + *pkt >> tmp; + player->allowed_camera_mode = static_cast(tmp); + + m_client_event_queue.push(new ClientEvent(CE_UPDATE_CAMERA)); } void Client::handleCommand_UpdatePlayerList(NetworkPacket* pkt) diff --git a/src/network/networkprotocol.h b/src/network/networkprotocol.h index 0d63bd24f..a0fa6b96d 100644 --- a/src/network/networkprotocol.h +++ b/src/network/networkprotocol.h @@ -442,6 +442,11 @@ enum ToClientCommand : u16 */ + TOCLIENT_CAMERA = 0x48, + /* + u8 allowed_camera_mode + */ + TOCLIENT_HUDADD = 0x49, /* u32 id diff --git a/src/network/serveropcodes.cpp b/src/network/serveropcodes.cpp index 43febbb1b..b50e13082 100644 --- a/src/network/serveropcodes.cpp +++ b/src/network/serveropcodes.cpp @@ -183,7 +183,7 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] = { "TOCLIENT_MOVEMENT", 0, true }, // 0x45 { "TOCLIENT_SPAWN_PARTICLE", 0, true }, // 0x46 { "TOCLIENT_ADD_PARTICLESPAWNER", 0, true }, // 0x47 - null_command_factory, // 0x48 + { "TOCLIENT_CAMERA", 0, true }, // 0x48 { "TOCLIENT_HUDADD", 1, true }, // 0x49 { "TOCLIENT_HUDRM", 1, true }, // 0x4a { "TOCLIENT_HUDCHANGE", 1, true }, // 0x4b diff --git a/src/particles.h b/src/particles.h index 00d04dbeb..81c8c4809 100644 --- a/src/particles.h +++ b/src/particles.h @@ -20,12 +20,6 @@ namespace ParticleParamTypes { - template - using enableIf = typename std::enable_if::type; - // std::enable_if_t does not appear to be present in GCC???? - // std::is_enum_v also missing. wtf. these are supposed to be - // present as of c++14 - template using BlendFunction = T(float,T,T); #define DECL_PARAM_SRZRS(type) \ void serializeParameterValue (std::ostream& os, type v); \ @@ -57,12 +51,12 @@ namespace ParticleParamTypes * that's hideous and unintuitive. instead, we supply the following functions to * transparently map enumeration types to their underlying values. */ - template ::value, bool> = true> + template , bool> = true> void serializeParameterValue(std::ostream& os, E k) { serializeParameterValue(os, (std::underlying_type_t)k); } - template ::value, bool> = true> + template , bool> = true> void deSerializeParameterValue(std::istream& is, E& k) { std::underlying_type_t v; deSerializeParameterValue(is, v); diff --git a/src/player.cpp b/src/player.cpp index 3704f177d..10f66ec11 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -15,6 +15,13 @@ #include "porting.h" // strlcpy #include +const struct EnumString es_CameraMode[] = { + {CAMERA_MODE_ANY, "any"}, + {CAMERA_MODE_FIRST, "first"}, + {CAMERA_MODE_THIRD, "third"}, + {CAMERA_MODE_THIRD_FRONT, "third_front"}, + {0, nullptr} +}; bool is_valid_player_name(std::string_view name) { diff --git a/src/player.h b/src/player.h index 25c80039c..80a3c98c1 100644 --- a/src/player.h +++ b/src/player.h @@ -126,6 +126,17 @@ struct PlayerPhysicsOverride } }; +/// @note numeric values are part of network protocol +enum CameraMode { + // not a mode. indicates that any may be used. + CAMERA_MODE_ANY = 0, + CAMERA_MODE_FIRST, + CAMERA_MODE_THIRD, + CAMERA_MODE_THIRD_FRONT +}; + +extern const struct EnumString es_CameraMode[]; + class Map; struct HudElement; class Environment; @@ -160,6 +171,8 @@ public: return size; } + CameraMode allowed_camera_mode = CAMERA_MODE_ANY; + v3f eye_offset_first; v3f eye_offset_third; v3f eye_offset_third_front; diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 2fce24906..816f42857 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -492,6 +492,39 @@ int ObjectRef::l_get_eye_offset(lua_State *L) return 3; } +int ObjectRef::l_set_camera(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkObject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + luaL_checktype(L, 2, LUA_TTABLE); + + lua_getfield(L, -1, "mode"); + if (lua_isstring(L, -1)) + string_to_enum(es_CameraMode, player->allowed_camera_mode, lua_tostring(L, -1)); + lua_pop(L, 1); + + getServer(L)->SendCamera(player->getPeerId(), player); + return 0; +} + +int ObjectRef::l_get_camera(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + ObjectRef *ref = checkObject(L, 1); + RemotePlayer *player = getplayer(ref); + if (player == nullptr) + return 0; + + lua_newtable(L); + setstringfield(L, -1, "mode", enum_to_string(es_CameraMode, player->allowed_camera_mode)); + + return 1; +} + // send_mapblock(self, pos) int ObjectRef::l_send_mapblock(lua_State *L) { @@ -2900,6 +2933,8 @@ luaL_Reg ObjectRef::methods[] = { luamethod(ObjectRef, respawn), luamethod(ObjectRef, set_flags), luamethod(ObjectRef, get_flags), + luamethod(ObjectRef, set_camera), + luamethod(ObjectRef, get_camera), {0,0} }; diff --git a/src/script/lua_api/l_object.h b/src/script/lua_api/l_object.h index 900ec243d..43415f5ef 100644 --- a/src/script/lua_api/l_object.h +++ b/src/script/lua_api/l_object.h @@ -378,6 +378,12 @@ private: // get_eye_offset(self) static int l_get_eye_offset(lua_State *L); + // set_camera(self, {params}) + static int l_set_camera(lua_State *L); + + // get_camera(self) + static int l_get_camera(lua_State *L); + // set_nametag_attributes(self, attributes) static int l_set_nametag_attributes(lua_State *L); diff --git a/src/server.cpp b/src/server.cpp index 89f597a88..2f59b7443 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -1944,6 +1944,15 @@ void Server::SendSetLighting(session_t peer_id, const Lighting &lighting) Send(&pkt); } +void Server::SendCamera(session_t peer_id, Player *player) +{ + NetworkPacket pkt(TOCLIENT_CAMERA, 1, peer_id); + + pkt << static_cast(player->allowed_camera_mode); + + Send(&pkt); +} + void Server::SendTimeOfDay(session_t peer_id, u16 time, f32 time_speed) { NetworkPacket pkt(TOCLIENT_TIME_OF_DAY, 0, peer_id); diff --git a/src/server.h b/src/server.h index ff2f8dbcf..74f192195 100644 --- a/src/server.h +++ b/src/server.h @@ -45,6 +45,7 @@ class BanManager; class Inventory; class ModChannelMgr; class RemotePlayer; +class Player; class PlayerSAO; struct PlayerHPChangeReason; class IRollbackManager; @@ -409,6 +410,7 @@ public: void SendMovePlayerRel(session_t peer_id, const v3f &added_pos); void SendPlayerSpeed(session_t peer_id, const v3f &added_vel); void SendPlayerFov(session_t peer_id); + void SendCamera(session_t peer_id, Player *player); void SendMinimapModes(session_t peer_id, std::vector &modes, @@ -546,6 +548,7 @@ private: void SendCloudParams(session_t peer_id, const CloudParams ¶ms); void SendOverrideDayNightRatio(session_t peer_id, bool do_override, float ratio); void SendSetLighting(session_t peer_id, const Lighting &lighting); + void broadcastModChannelMessage(const std::string &channel, const std::string &message, session_t from_peer); diff --git a/src/util/enum_string.h b/src/util/enum_string.h index c4f84ee09..5c084c718 100644 --- a/src/util/enum_string.h +++ b/src/util/enum_string.h @@ -5,6 +5,7 @@ #pragma once #include +#include struct EnumString { @@ -14,4 +15,13 @@ struct EnumString bool string_to_enum(const EnumString *spec, int &result, std::string_view str); +template , bool> = true> +bool string_to_enum(const EnumString *spec, T &result, std::string_view str) +{ + int result_int = result; + bool ret = string_to_enum(spec, result_int, str); + result = static_cast(result_int); + return ret; +} + const char *enum_to_string(const EnumString *spec, int num);