Add support for per-player FOV overrides and multipliers
authorAnand S <ClobberXD@gmail.com>
Sun, 15 Jul 2018 00:26:30 +0000 (05:56 +0530)
committersfan5 <sfan5@live.de>
Thu, 19 Sep 2019 18:14:16 +0000 (20:14 +0200)
13 files changed:
doc/lua_api.txt
src/client/camera.cpp
src/client/client.h
src/client/game.cpp
src/network/clientopcodes.cpp
src/network/clientpackethandler.cpp
src/network/networkprotocol.h
src/network/serveropcodes.cpp
src/player.h
src/script/lua_api/l_object.cpp
src/script/lua_api/l_object.h
src/server.cpp
src/server.h

index 5a991c278d136e622823d14b36b055a1d0c2525c..4eb9c797c6e21b2922f3c5027455b11d0901a3f1 100644 (file)
@@ -5580,12 +5580,21 @@ This is basically a reference to a C++ `ServerActiveObject`
   `set_look_vertical`.
 * `set_look_yaw(radians)`: sets look yaw - Deprecated. Use
   `set_look_horizontal`.
-* `get_breath()`: returns players breath
-* `set_breath(value)`: sets players breath
+* `get_breath()`: returns player's breath
+* `set_breath(value)`: sets player's breath
     * values:
         * `0`: player is drowning
         * max: bubbles bar is not shown
         * See [Object properties] for more information
+* `set_fov(fov, is_multiplier)`: Sets player's FOV
+    * `fov`: FOV value.
+    * `is_multiplier`: Set to `true` if the FOV value is a multiplier.
+      Defaults to `false`.
+    * Set to 0 to clear FOV override.
+* `get_fov()`:
+    * Returns player's FOV override in degrees, and a boolean depending on whether
+      the value is a multiplier.
+    * Returns 0 as first value if player's FOV hasn't been overridden.
 * `set_attribute(attribute, value)`:  DEPRECATED, use get_meta() instead
     * Sets an extra attribute with value on player.
     * `value` must be a string, or a number which will be converted to a
index 025bd081d46c5272be5e30443b1da727677ff3fb..d1e76026da7a883962b12be2493c60f495649811 100644 (file)
@@ -448,12 +448,26 @@ void Camera::update(LocalPlayer* player, f32 frametime, f32 busytime, f32 tool_r
        if (m_camera_mode != CAMERA_MODE_FIRST)
                m_camera_position = my_cp;
 
-       // Get FOV
+       /*
+        * Apply server-sent FOV. If server doesn't enforce FOV,
+        * check for zoom and set to zoom FOV.
+        * Otherwise, default to m_cache_fov
+        */
+
        f32 fov_degrees;
-       // Disable zoom with zoom FOV = 0
-       if (player->getPlayerControl().zoom && player->getZoomFOV() > 0.001f) {
+       PlayerFovSpec fov_spec = player->getFov();
+       if (fov_spec.fov > 0.0f) {
+               // If server-sent FOV is a multiplier, multiply
+               // it with m_cache_fov instead of overriding
+               if (fov_spec.is_multiplier)
+                       fov_degrees = m_cache_fov * fov_spec.fov;
+               else
+                       fov_degrees = fov_spec.fov;
+       } else if (player->getPlayerControl().zoom && player->getZoomFOV() > 0.001f) {
+               // Player requests zoom, apply zoom FOV
                fov_degrees = player->getZoomFOV();
        } else {
+               // Set to client's selected FOV
                fov_degrees = m_cache_fov;
        }
        fov_degrees = rangelim(fov_degrees, 1.0f, 160.0f);
index dee60b6d59b0357306968124b49a6631d482cea9..e3c931837754eb0fba0b9b4a5824807321074d23 100644 (file)
@@ -193,6 +193,7 @@ public:
        void handleCommand_ActiveObjectRemoveAdd(NetworkPacket* pkt);
        void handleCommand_ActiveObjectMessages(NetworkPacket* pkt);
        void handleCommand_Movement(NetworkPacket* pkt);
+       void handleCommand_Fov(NetworkPacket *pkt);
        void handleCommand_HP(NetworkPacket* pkt);
        void handleCommand_Breath(NetworkPacket* pkt);
        void handleCommand_MovePlayer(NetworkPacket* pkt);
index ef846fe18467f3dbafe85c303a894035e929d665..042b61a78cb48d520748c5e267100f4c6ebfc7ed 100644 (file)
@@ -2332,7 +2332,7 @@ void Game::toggleFullViewRange()
 void Game::checkZoomEnabled()
 {
        LocalPlayer *player = client->getEnv().getLocalPlayer();
-       if (player->getZoomFOV() < 0.001f)
+       if (player->getZoomFOV() < 0.001f || player->getFov().fov > 0.0f)
                m_game_ui->showTranslatedStatusText("Zoom currently disabled by game or mod");
 }
 
index 8641cadecbbc92c0b02820df23fe92ca9bb3752a..498583df96a6ae644f486492ea694a6f3ebb6087 100644 (file)
@@ -78,7 +78,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] =
        { "TOCLIENT_HP",                       TOCLIENT_STATE_CONNECTED, &Client::handleCommand_HP }, // 0x33
        { "TOCLIENT_MOVE_PLAYER",              TOCLIENT_STATE_CONNECTED, &Client::handleCommand_MovePlayer }, // 0x34
        { "TOCLIENT_ACCESS_DENIED_LEGACY",     TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_AccessDenied }, // 0x35
-       null_command_handler,
+       { "TOCLIENT_FOV",                      TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Fov }, // 0x36
        { "TOCLIENT_DEATHSCREEN",              TOCLIENT_STATE_CONNECTED, &Client::handleCommand_DeathScreen }, // 0x37
        { "TOCLIENT_MEDIA",                    TOCLIENT_STATE_CONNECTED, &Client::handleCommand_Media }, // 0x38
        null_command_handler,
@@ -151,9 +151,9 @@ const ServerCommandFactory serverCommandFactoryTable[TOSERVER_NUM_MSG_TYPES] =
        null_command_factory, // 0x14
        null_command_factory, // 0x15
        null_command_factory, // 0x16
-       { "TOSERVER_MODCHANNEL_JOIN", 0, true }, // 0x17
-       { "TOSERVER_MODCHANNEL_LEAVE", 0, true }, // 0x18
-       { "TOSERVER_MODCHANNEL_MSG", 0, true }, // 0x19
+       { "TOSERVER_MODCHANNEL_JOIN",    0, true }, // 0x17
+       { "TOSERVER_MODCHANNEL_LEAVE",   0, true }, // 0x18
+       { "TOSERVER_MODCHANNEL_MSG",     0, true }, // 0x19
        null_command_factory, // 0x1a
        null_command_factory, // 0x1b
        null_command_factory, // 0x1c
index d47571d1436abace1e1ed3e2222619e09d8b3f11..b6e9defb00444c98e2b4fd1fe916640e37b39b55 100644 (file)
@@ -523,13 +523,22 @@ void Client::handleCommand_Movement(NetworkPacket* pkt)
        player->movement_gravity                = g * BS;
 }
 
-void Client::handleCommand_HP(NetworkPacket* pkt)
+void Client::handleCommand_Fov(NetworkPacket *pkt)
 {
+       f32 fov;
+       bool is_multiplier;
+       *pkt >> fov >> is_multiplier;
 
+       LocalPlayer *player = m_env.getLocalPlayer();
+       player->setFov({ fov, is_multiplier });
+}
+
+void Client::handleCommand_HP(NetworkPacket *pkt)
+{
        LocalPlayer *player = m_env.getLocalPlayer();
        assert(player != NULL);
 
-       u16 oldhp   = player->hp;
+       u16 oldhp = player->hp;
 
        u16 hp;
        *pkt >> hp;
index 52cdf489cebbcd4ea5ec7728c46f41e91c311c65..5a13c1353f5cfc1dafcfd83f3ffdf95943f4b670 100644 (file)
@@ -199,6 +199,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
                Incremental inventory sending mode
                Unknown inventory serialization fields no longer throw an error
                Mod-specific formspec version
+               Player FOV override API
 */
 
 #define LATEST_PROTOCOL_VERSION 38
@@ -370,7 +371,13 @@ enum ToClientCommand
                wstring reason
        */
 
-       TOCLIENT_PLAYERITEM = 0x36, // Obsolete
+       TOCLIENT_FOV = 0x36,
+       /*
+               Sends an FOV override/multiplier to client.
+
+               float fov
+               bool is_multiplier
+       */
 
        TOCLIENT_DEATHSCREEN = 0x37,
        /*
index 8c5579a369168cd543ca798ba2c06ce409b99d54..8c8d49955f0a07b671e9d8f33ed1913ae29a5eca 100644 (file)
@@ -104,9 +104,9 @@ const ToServerCommandHandler toServerCommandTable[TOSERVER_NUM_MSG_TYPES] =
        null_command_handler, // 0x4d
        null_command_handler, // 0x4e
        null_command_handler, // 0x4f
-       { "TOSERVER_FIRST_SRP",          TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_FirstSrp }, // 0x50
-       { "TOSERVER_SRP_BYTES_A",        TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_SrpBytesA }, // 0x51
-       { "TOSERVER_SRP_BYTES_M",        TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_SrpBytesM }, // 0x52
+       { "TOSERVER_FIRST_SRP",                TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_FirstSrp }, // 0x50
+       { "TOSERVER_SRP_BYTES_A",              TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_SrpBytesA }, // 0x51
+       { "TOSERVER_SRP_BYTES_M",              TOSERVER_STATE_NOT_CONNECTED, &Server::handleCommand_SrpBytesM }, // 0x52
 };
 
 const static ClientCommandFactory null_command_factory = { "TOCLIENT_NULL", 0, false };
@@ -115,51 +115,51 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
 {
        null_command_factory, // 0x00
        null_command_factory, // 0x01
-       { "TOCLIENT_HELLO",             0, true }, // 0x02
-       { "TOCLIENT_AUTH_ACCEPT",       0, true }, // 0x03
-       { "TOCLIENT_ACCEPT_SUDO_MODE",  0, true }, // 0x04
-       { "TOCLIENT_DENY_SUDO_MODE",    0, true }, // 0x05
+       { "TOCLIENT_HELLO",                    0, true }, // 0x02
+       { "TOCLIENT_AUTH_ACCEPT",              0, true }, // 0x03
+       { "TOCLIENT_ACCEPT_SUDO_MODE",         0, true }, // 0x04
+       { "TOCLIENT_DENY_SUDO_MODE",           0, true }, // 0x05
        null_command_factory, // 0x06
        null_command_factory, // 0x07
        null_command_factory, // 0x08
        null_command_factory, // 0x09
-       { "TOCLIENT_ACCESS_DENIED",     0, true }, // 0x0A
+       { "TOCLIENT_ACCESS_DENIED",            0, true }, // 0x0A
        null_command_factory, // 0x0B
        null_command_factory, // 0x0C
        null_command_factory, // 0x0D
        null_command_factory, // 0x0E
        null_command_factory, // 0x0F
-       { "TOCLIENT_INIT",              0, true }, // 0x10
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
+       { "TOCLIENT_INIT",                     0, true }, // 0x10
+       null_command_factory, // 0x11
+       null_command_factory, // 0x12
+       null_command_factory, // 0x13
+       null_command_factory, // 0x14
+       null_command_factory, // 0x15
+       null_command_factory, // 0x16
+       null_command_factory, // 0x17
+       null_command_factory, // 0x18
+       null_command_factory, // 0x19
+       null_command_factory, // 0x1A
+       null_command_factory, // 0x1B
+       null_command_factory, // 0x1C
+       null_command_factory, // 0x1D
+       null_command_factory, // 0x1E
+       null_command_factory, // 0x1F
        { "TOCLIENT_BLOCKDATA",                2, true }, // 0x20
        { "TOCLIENT_ADDNODE",                  0, true }, // 0x21
        { "TOCLIENT_REMOVENODE",               0, true }, // 0x22
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
+       null_command_factory, // 0x23
+       null_command_factory, // 0x24
+       null_command_factory, // 0x25
+       null_command_factory, // 0x26
        { "TOCLIENT_INVENTORY",                0, true }, // 0x27
-       null_command_factory,
+       null_command_factory, // 0x28
        { "TOCLIENT_TIME_OF_DAY",              0, true }, // 0x29
        { "TOCLIENT_CSM_RESTRICTION_FLAGS",    0, true }, // 0x2A
        { "TOCLIENT_PLAYER_SPEED",             0, true }, // 0x2B
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
+       null_command_factory, // 0x2C
+       null_command_factory, // 0x2D
+       null_command_factory, // 0x2E
        { "TOCLIENT_CHAT_MESSAGE",             0, true }, // 0x2F
        null_command_factory, // 0x30
        { "TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD", 0, true }, // 0x31
@@ -167,15 +167,15 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
        { "TOCLIENT_HP",                       0, true }, // 0x33
        { "TOCLIENT_MOVE_PLAYER",              0, true }, // 0x34
        { "TOCLIENT_ACCESS_DENIED_LEGACY",     0, true }, // 0x35
-       null_command_factory, // 0x36
+       { "TOCLIENT_FOV",                      0, true }, // 0x36
        { "TOCLIENT_DEATHSCREEN",              0, true }, // 0x37
        { "TOCLIENT_MEDIA",                    2, true }, // 0x38
        null_command_factory, // 0x39
-       { "TOCLIENT_NODEDEF",                  0, true }, // 0x3a
-       null_command_factory, // 0x3b
-       { "TOCLIENT_ANNOUNCE_MEDIA",           0, true }, // 0x3c
-       { "TOCLIENT_ITEMDEF",                  0, true }, // 0x3d
-       null_command_factory,
+       { "TOCLIENT_NODEDEF",                  0, true }, // 0x3A
+       null_command_factory, // 0x3B
+       { "TOCLIENT_ANNOUNCE_MEDIA",           0, true }, // 0x3C
+       { "TOCLIENT_ITEMDEF",                  0, true }, // 0x3D
+       null_command_factory, // 0x3E
        { "TOCLIENT_PLAY_SOUND",               0, true }, // 0x3f
        { "TOCLIENT_STOP_SOUND",               0, true }, // 0x40
        { "TOCLIENT_PRIVILEGES",               0, true }, // 0x41
@@ -203,12 +203,12 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
        { "TOCLIENT_MODCHANNEL_MSG",           0, true }, // 0x57
        { "TOCLIENT_MODCHANNEL_SIGNAL",        0, true }, // 0x58
        { "TOCLIENT_NODEMETA_CHANGED",         0, true }, // 0x59
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
-       null_command_factory,
+       null_command_factory, // 0x5A
+       null_command_factory, // 0x5B
+       null_command_factory, // 0x5C
+       null_command_factory, // 0x5D
+       null_command_factory, // 0x5E
+       null_command_factory, // 0x5F
        { "TOSERVER_SRP_BYTES_S_B",            0, true }, // 0x60
        { "TOCLIENT_FORMSPEC_PREPEND",         0, true }, // 0x61
 };
index b0c3b257407abe5a7306c56dd8249d1b330d2241..de7f427e91a956fb9219bb1e373ed4ce1618e86f 100644 (file)
@@ -32,6 +32,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #define PLAYERNAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
 #define PLAYERNAME_ALLOWED_CHARS_USER_EXPL "'a' to 'z', 'A' to 'Z', '0' to '9', '-', '_'"
 
+struct PlayerFovSpec
+{
+       f32 fov;
+       bool is_multiplier;
+};
+
 struct PlayerControl
 {
        PlayerControl() = default;
@@ -178,6 +184,16 @@ public:
        void setWieldIndex(u16 index);
        u16 getWieldIndex() const { return m_wield_index; }
 
+       void setFov(const PlayerFovSpec &spec)
+       {
+               m_fov_spec = spec;
+       }
+
+       const PlayerFovSpec &getFov() const
+       {
+               return m_fov_spec;
+       }
+
        u32 keyPressed = 0;
 
        HudElement* getHud(u32 id);
@@ -187,10 +203,12 @@ public:
 
        u32 hud_flags;
        s32 hud_hotbar_itemcount;
+
 protected:
        char m_name[PLAYERNAME_SIZE];
        v3f m_speed;
        u16 m_wield_index = 0;
+       PlayerFovSpec m_fov_spec = { 0.0f, false };
 
        std::vector<HudElement *> hud;
 private:
index 68cc1c0a9eb24cffb35f59637302651c762080ba..1ad79024fd107880a3d190bc1b3f85e9994de020 100644 (file)
@@ -1249,6 +1249,37 @@ int ObjectRef::l_set_look_yaw(lua_State *L)
        return 1;
 }
 
+// set_fov(self, degrees[, is_multiplier])
+int ObjectRef::l_set_fov(lua_State *L)
+{
+       NO_MAP_LOCK_REQUIRED;
+       ObjectRef *ref = checkobject(L, 1);
+       RemotePlayer *player = getplayer(ref);
+       if (!player)
+               return 0;
+
+       player->setFov({ static_cast<f32>(luaL_checknumber(L, 2)), readParam<bool>(L, 3) });
+       getServer(L)->SendPlayerFov(player->getPeerId());
+
+       return 0;
+}
+
+// get_fov(self)
+int ObjectRef::l_get_fov(lua_State *L)
+{
+       NO_MAP_LOCK_REQUIRED;
+       ObjectRef *ref = checkobject(L, 1);
+       RemotePlayer *player = getplayer(ref);
+       if (!player)
+               return 0;
+
+       PlayerFovSpec fov_spec = player->getFov();
+       lua_pushnumber(L, fov_spec.fov);
+       lua_pushboolean(L, fov_spec.is_multiplier);
+
+       return 2;
+}
+
 // set_breath(self, breath)
 int ObjectRef::l_set_breath(lua_State *L)
 {
@@ -1962,6 +1993,8 @@ luaL_Reg ObjectRef::methods[] = {
        luamethod(ObjectRef, set_look_vertical),
        luamethod(ObjectRef, set_look_yaw),
        luamethod(ObjectRef, set_look_pitch),
+       luamethod(ObjectRef, get_fov),
+       luamethod(ObjectRef, set_fov),
        luamethod(ObjectRef, get_breath),
        luamethod(ObjectRef, set_breath),
        luamethod(ObjectRef, get_attribute),
index 2390c51958371eaceb9ae7e3074a73dc20dd40f2..e817e1d33484ef318de9362f197ef1ca2460c326 100644 (file)
@@ -215,6 +215,9 @@ private:
        // add_player_velocity(self, {x=num, y=num, z=num})
        static int l_add_player_velocity(lua_State *L);
 
+       // get_fov(self)
+       static int l_get_fov(lua_State *L);
+
        // get_look_dir(self)
        static int l_get_look_dir(lua_State *L);
 
@@ -232,6 +235,9 @@ private:
        // get_look_yaw2(self)
        static int l_get_look_horizontal(lua_State *L);
 
+       // set_fov(self, degrees, is_multiplier)
+       static int l_set_fov(lua_State *L);
+
        // set_look_vertical(self, radians)
        static int l_set_look_vertical(lua_State *L);
 
index a848c0ae98f5b351c32f6e917ea1f07881380d58..c0ed02b9f1cbedc92791d4b54140fb5da68c202c 100644 (file)
@@ -1765,6 +1765,16 @@ void Server::SendMovePlayer(session_t peer_id)
        Send(&pkt);
 }
 
+void Server::SendPlayerFov(session_t peer_id)
+{
+       NetworkPacket pkt(TOCLIENT_FOV, 4 + 1, peer_id);
+
+       PlayerFovSpec fov_spec = m_env->getPlayer(peer_id)->getFov();
+       pkt << fov_spec.fov << fov_spec.is_multiplier;
+
+       Send(&pkt);
+}
+
 void Server::SendLocalPlayerAnimations(session_t peer_id, v2s32 animation_frames[4],
                f32 animation_speed)
 {
index aa7d6385a225806c9250993716d9a9ec4a24dc04..f017331799515a1cc1ca2f53dffb43d4085cac31 100644 (file)
@@ -336,6 +336,7 @@ public:
        void SendInventory(PlayerSAO *playerSAO, bool incremental);
        void SendMovePlayer(session_t peer_id);
        void SendPlayerSpeed(session_t peer_id, const v3f &added_vel);
+       void SendPlayerFov(session_t peer_id);
 
        void sendDetachedInventories(session_t peer_id, bool incremental);
 
@@ -515,7 +516,6 @@ private:
        /*
                Variables
        */
-
        // World directory
        std::string m_path_world;
        // Subgame specification
@@ -575,7 +575,6 @@ private:
        /*
                Threads
        */
-
        // A buffer for time steps
        // step() increments and AsyncRunStep() run by m_thread reads it.
        float m_step_dtime = 0.0f;
@@ -590,14 +589,14 @@ private:
        /*
                Time related stuff
        */
-
        // Timer for sending time of day over network
        float m_time_of_day_send_timer = 0.0f;
        // Uptime of server in seconds
        MutexedVariable<double> m_uptime;
+
        /*
-        Client interface
-        */
+               Client interface
+       */
        ClientInterface m_clients;
 
        /*