diff --git a/builtin/client/chatcommands.lua b/builtin/client/chatcommands.lua index f425216f5..2b8cc4acd 100644 --- a/builtin/client/chatcommands.lua +++ b/builtin/client/chatcommands.lua @@ -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 diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index 463ca0be9..0ec33c628 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -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] diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md index ce0746df4..b3e494cc1 100644 --- a/doc/client_lua_api.md +++ b/doc/client_lua_api.md @@ -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()` diff --git a/minetest.conf.example b/minetest.conf.example index e9add1597..5e1609de6 100644 --- a/minetest.conf.example +++ b/minetest.conf.example @@ -343,6 +343,10 @@ # 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 diff --git a/src/client.cpp b/src/client.cpp index a36f5413f..a5228132d 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -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(); } 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"; } - diff --git a/src/client.h b/src/client.h index cc0d4699d..b4145c76f 100644 --- a/src/client.h +++ b/src/client.h @@ -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 m_chat_queue; + std::queue 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; diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index c583220bd..0a44069fd 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -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"); diff --git a/src/script/lua_api/l_client.cpp b/src/script/lua_api/l_client.cpp index 09b832ccf..0b7450af2 100644 --- a/src/script/lua_api/l_client.cpp +++ b/src/script/lua_api/l_client.cpp @@ -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); diff --git a/src/script/lua_api/l_client.h b/src/script/lua_api/l_client.h index 09b74addd..fe5780fb1 100644 --- a/src/script/lua_api/l_client.h +++ b/src/script/lua_api/l_client.h @@ -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);