[CSM] Add send_chat_message and run_server_chatcommand API functions (#5747)
authorPierre-Adrien Langrognet <upsilon@langg.net>
Sun, 21 May 2017 21:06:51 +0000 (23:06 +0200)
committerLoïc Blot <nerzhul@users.noreply.github.com>
Sun, 21 May 2017 21:06:51 +0000 (23:06 +0200)
* [CSM] Add send_chat_message and run_server_chatcommand API functions

* Add client-side chat message rate limiting

* Limit out chat queue size

* [CSM] Add minetest.clear_out_chat_queue API function and .clear_chat_queue chatcommand

* Last fixes/cleanups before merge

builtin/client/chatcommands.lua
builtin/settingtypes.txt
doc/client_lua_api.md
minetest.conf.example
src/client.cpp
src/client.h
src/defaultsettings.cpp
src/script/lua_api/l_client.cpp
src/script/lua_api/l_client.h

index f425216f5ead3dc580499a5e19ef166393dac103..2b8cc4acd7c8526363e2db775e4f898ad56a01b7 100644 (file)
@@ -51,3 +51,15 @@ core.register_chatcommand("disconnect", {
                core.disconnect()
        end,
 })
+
+core.register_chatcommand("clear_chat_queue", {
+       description = core.gettext("Clear the out chat queue"),
+       func = function(param)
+               core.clear_out_chat_queue()
+               return true, core.gettext("The out chat queue is now empty")
+       end,
+})
+
+function core.run_server_chatcommand(cmd, param)
+       core.send_chat_message("/" .. cmd .. " " .. param)
+end
index 463ca0be9d304c259043e4ff02615124592f06ba..0ec33c628986f3fb847d86be3377a087bd0ac2e5 100644 (file)
@@ -312,6 +312,9 @@ serverlist_url (Serverlist URL) string servers.minetest.net
 #    File in client/serverlist/ that contains your favorite servers displayed in the Multiplayer Tab.
 serverlist_file (Serverlist file) string favoriteservers.txt
 
+#    Maximum size of the out chat queue. 0 to disable queueing and -1 to make the queue size unlimited
+max_out_chat_queue_size (Maximum size of the out chat queue) int 20
+
 [*Graphics]
 
 [**In-Game]
index ce0746df405978e538f98fbbcbcad0166765077a..b3e494cc13fbd89a57e4c7c60d928dc4e43a254d 100644 (file)
@@ -721,6 +721,12 @@ Call these functions only at load time!
 ### Player
 * `minetest.get_wielded_item()`
     * Returns the itemstack the local player is holding
+* `minetest.send_chat_message(message)`
+    * Act as if `message` was typed by the player into the terminal.
+* `minetest.run_server_chatcommand(cmd, param)`
+    * Alias for `minetest.send_chat_message("/" .. cmd .. " " .. param)`
+* `minetest.clear_out_chat_queue()`
+    * Clears the out chat queue
 * `minetest.localplayer`
     * Reference to the LocalPlayer object. See [`LocalPlayer`](#localplayer) class reference for methods.
 
@@ -836,7 +842,7 @@ Please do not try to access the reference until the camera is initialized, other
     * Returns with same syntax as above
 * `get_fov()`
     * Returns:
-    
+
 ```lua
      {
          x = number,
@@ -845,7 +851,7 @@ Please do not try to access the reference until the camera is initialized, other
          actual = number
      }
 ```
-    
+
 * `get_pos()`
     * Returns position of camera with view bobbing
 * `get_offset()`
index e9add15974c9ab478114d4a6b1af711cd453d3f9..5e1609de63dfcaf1d9e507cd9dda77311ab4d100 100644 (file)
 #    type: string
 # serverlist_file = favoriteservers.txt
 
+#    Maximum size of the out chat queue. 0 to disable queueing and -1 to make the queue size unlimited
+#    type: int min: -1
+max_out_chat_queue_size = 20
+
 ## Graphics
 
 ### In-Game
index a36f5413f58f62c23f429cec61be2dba4f8c0383..a5228132d629c5eae1927fc5f01cda0e36c0e66b 100644 (file)
@@ -104,6 +104,8 @@ Client::Client(
        m_animation_time(0),
        m_crack_level(-1),
        m_crack_pos(0,0,0),
+       m_last_chat_message_sent(time(NULL)),
+       m_chat_message_allowance(5.0f),
        m_map_seed(0),
        m_password(password),
        m_chosen_auth_mech(AUTH_MECHANISM_NONE),
@@ -400,6 +402,14 @@ void Client::step(float dtime)
                }
        }
 
+       /*
+               Send pending messages on out chat queue
+       */
+       if (!m_out_chat_queue.empty() && canSendChatMessage()) {
+               sendChatMessage(m_out_chat_queue.front());
+               m_out_chat_queue.pop();
+       }
+
        /*
                Handle environment
        */
@@ -1158,13 +1168,50 @@ void Client::sendInventoryAction(InventoryAction *a)
        Send(&pkt);
 }
 
+bool Client::canSendChatMessage() const
+{
+       u32 now = time(NULL);
+       float time_passed = now - m_last_chat_message_sent;
+
+       float virt_chat_message_allowance = m_chat_message_allowance + time_passed *
+                       (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f);
+
+       if (virt_chat_message_allowance < 1.0f)
+               return false;
+
+       return true;
+}
+
 void Client::sendChatMessage(const std::wstring &message)
 {
-       NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16));
+       const s16 max_queue_size = g_settings->getS16("max_out_chat_queue_size");
+       if (canSendChatMessage()) {
+               u32 now = time(NULL);
+               float time_passed = now - m_last_chat_message_sent;
+               m_last_chat_message_sent = time(NULL);
 
-       pkt << message;
+               m_chat_message_allowance += time_passed * (CLIENT_CHAT_MESSAGE_LIMIT_PER_10S / 8.0f);
+               if (m_chat_message_allowance > CLIENT_CHAT_MESSAGE_LIMIT_PER_10S)
+                       m_chat_message_allowance = CLIENT_CHAT_MESSAGE_LIMIT_PER_10S;
 
-       Send(&pkt);
+               m_chat_message_allowance -= 1.0f;
+
+               NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16));
+
+               pkt << message;
+
+               Send(&pkt);
+       } else if (m_out_chat_queue.size() < (u16) max_queue_size || max_queue_size == -1) {
+               m_out_chat_queue.push(message);
+       } else {
+               infostream << "Could not queue chat message because maximum out chat queue size ("
+                               << max_queue_size << ") is reached." << std::endl;
+       }
+}
+
+void Client::clearOutChatQueue()
+{
+       m_out_chat_queue = std::queue<std::wstring>();
 }
 
 void Client::sendChangePassword(const std::string &oldpassword,
@@ -1924,4 +1971,3 @@ std::string Client::getModStoragePath() const
 {
        return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage";
 }
-
index cc0d4699d30abf89d14650eabb372b4dee993f67..b4145c76ff207127c08084f2dfcdb9f29e72e053 100644 (file)
@@ -38,6 +38,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "tileanimation.h"
 #include "mesh_generator_thread.h"
 
+#define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f
+
 struct MeshMakeData;
 class MapBlockMesh;
 class IWritableTextureSource;
@@ -360,6 +362,7 @@ public:
                const StringMap &fields);
        void sendInventoryAction(InventoryAction *a);
        void sendChatMessage(const std::wstring &message);
+       void clearOutChatQueue();
        void sendChangePassword(const std::string &oldpassword,
                const std::string &newpassword);
        void sendDamage(u8 damage);
@@ -565,6 +568,8 @@ private:
        inline std::string getPlayerName()
        { return m_env.getLocalPlayer()->getName(); }
 
+       bool canSendChatMessage() const;
+
        float m_packetcounter_timer;
        float m_connection_reinit_timer;
        float m_avg_rtt_timer;
@@ -612,6 +617,9 @@ private:
        //s32 m_daynight_i;
        //u32 m_daynight_ratio;
        std::queue<std::wstring> m_chat_queue;
+       std::queue<std::wstring> m_out_chat_queue;
+       u32 m_last_chat_message_sent;
+       float m_chat_message_allowance;
 
        // The authentication methods we can use to enter sudo mode (=change password)
        u32 m_sudo_auth_methods;
index c583220bdbaa5868a4fa180c0368b5232d984d13..0a44069fd79621eb4f15f9f764053494e6ef3f12 100644 (file)
@@ -57,6 +57,7 @@ void set_default_settings(Settings *settings)
        settings->setDefault("curl_verify_cert", "true");
        settings->setDefault("enable_remote_media_server", "true");
        settings->setDefault("enable_client_modding", "false");
+       settings->setDefault("max_out_chat_queue_size", "20");
 
        // Keymap
        settings->setDefault("remote_port", "30000");
index 09b832ccf194a60b0d2b7e7f416717d3ae37885f..0b7450af291d17e949e804e7d46272da95f85358 100644 (file)
@@ -85,6 +85,23 @@ int ModApiClient::l_display_chat_message(lua_State *L)
        return 1;
 }
 
+// send_chat_message(message)
+int ModApiClient::l_send_chat_message(lua_State *L)
+{
+       if (!lua_isstring(L,1))
+               return 0;
+       std::string message = luaL_checkstring(L, 1);
+       getClient(L)->sendChatMessage(utf8_to_wide(message));
+       return 0;
+}
+
+// clear_out_chat_queue()
+int ModApiClient::l_clear_out_chat_queue(lua_State *L)
+{
+       getClient(L)->clearOutChatQueue();
+       return 0;
+}
+
 // get_player_names()
 int ModApiClient::l_get_player_names(lua_State *L)
 {
@@ -317,6 +334,8 @@ void ModApiClient::Initialize(lua_State *L, int top)
        API_FCT(get_current_modname);
        API_FCT(print);
        API_FCT(display_chat_message);
+       API_FCT(send_chat_message);
+       API_FCT(clear_out_chat_queue);
        API_FCT(get_player_names);
        API_FCT(set_last_run_mod);
        API_FCT(get_last_run_mod);
index 09b74adddbee852f1fd01c029a2689e7950ec62b..fe5780fb1eee8025b8c98dd793501bc561f47cb2 100644 (file)
@@ -37,6 +37,12 @@ private:
        // display_chat_message(message)
        static int l_display_chat_message(lua_State *L);
 
+       // send_chat_message(message)
+       static int l_send_chat_message(lua_State *L);
+
+       // clear_out_chat_queue()
+       static int l_clear_out_chat_queue(lua_State *L);
+
        // get_player_names()
        static int l_get_player_names(lua_State *L);