[CSM] Add send_chat_message and run_server_chatcommand API functions (#5747)

* [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
This commit is contained in:
Pierre-Adrien Langrognet 2017-05-21 23:06:51 +02:00 committed by Loïc Blot
parent ec490abf58
commit 39f4a2f607
9 changed files with 111 additions and 6 deletions

@ -51,3 +51,15 @@ core.register_chatcommand("disconnect", {
core.disconnect() core.disconnect()
end, 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

@ -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. # File in client/serverlist/ that contains your favorite servers displayed in the Multiplayer Tab.
serverlist_file (Serverlist file) string favoriteservers.txt 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] [*Graphics]
[**In-Game] [**In-Game]

@ -721,6 +721,12 @@ Call these functions only at load time!
### Player ### Player
* `minetest.get_wielded_item()` * `minetest.get_wielded_item()`
* Returns the itemstack the local player is holding * 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` * `minetest.localplayer`
* Reference to the LocalPlayer object. See [`LocalPlayer`](#localplayer) class reference for methods. * Reference to the LocalPlayer object. See [`LocalPlayer`](#localplayer) class reference for methods.

@ -343,6 +343,10 @@
# type: string # type: string
# serverlist_file = favoriteservers.txt # 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 ## Graphics
### In-Game ### In-Game

@ -104,6 +104,8 @@ Client::Client(
m_animation_time(0), m_animation_time(0),
m_crack_level(-1), m_crack_level(-1),
m_crack_pos(0,0,0), m_crack_pos(0,0,0),
m_last_chat_message_sent(time(NULL)),
m_chat_message_allowance(5.0f),
m_map_seed(0), m_map_seed(0),
m_password(password), m_password(password),
m_chosen_auth_mech(AUTH_MECHANISM_NONE), 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 Handle environment
*/ */
@ -1158,13 +1168,50 @@ void Client::sendInventoryAction(InventoryAction *a)
Send(&pkt); 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) void Client::sendChatMessage(const std::wstring &message)
{ {
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);
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;
m_chat_message_allowance -= 1.0f;
NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16)); NetworkPacket pkt(TOSERVER_CHAT_MESSAGE, 2 + message.size() * sizeof(u16));
pkt << message; pkt << message;
Send(&pkt); 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, 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"; return porting::path_user + DIR_DELIM + "client" + DIR_DELIM + "mod_storage";
} }

@ -38,6 +38,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "tileanimation.h" #include "tileanimation.h"
#include "mesh_generator_thread.h" #include "mesh_generator_thread.h"
#define CLIENT_CHAT_MESSAGE_LIMIT_PER_10S 10.0f
struct MeshMakeData; struct MeshMakeData;
class MapBlockMesh; class MapBlockMesh;
class IWritableTextureSource; class IWritableTextureSource;
@ -360,6 +362,7 @@ public:
const StringMap &fields); const StringMap &fields);
void sendInventoryAction(InventoryAction *a); void sendInventoryAction(InventoryAction *a);
void sendChatMessage(const std::wstring &message); void sendChatMessage(const std::wstring &message);
void clearOutChatQueue();
void sendChangePassword(const std::string &oldpassword, void sendChangePassword(const std::string &oldpassword,
const std::string &newpassword); const std::string &newpassword);
void sendDamage(u8 damage); void sendDamage(u8 damage);
@ -565,6 +568,8 @@ private:
inline std::string getPlayerName() inline std::string getPlayerName()
{ return m_env.getLocalPlayer()->getName(); } { return m_env.getLocalPlayer()->getName(); }
bool canSendChatMessage() const;
float m_packetcounter_timer; float m_packetcounter_timer;
float m_connection_reinit_timer; float m_connection_reinit_timer;
float m_avg_rtt_timer; float m_avg_rtt_timer;
@ -612,6 +617,9 @@ private:
//s32 m_daynight_i; //s32 m_daynight_i;
//u32 m_daynight_ratio; //u32 m_daynight_ratio;
std::queue<std::wstring> m_chat_queue; 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) // The authentication methods we can use to enter sudo mode (=change password)
u32 m_sudo_auth_methods; u32 m_sudo_auth_methods;

@ -57,6 +57,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("curl_verify_cert", "true"); settings->setDefault("curl_verify_cert", "true");
settings->setDefault("enable_remote_media_server", "true"); settings->setDefault("enable_remote_media_server", "true");
settings->setDefault("enable_client_modding", "false"); settings->setDefault("enable_client_modding", "false");
settings->setDefault("max_out_chat_queue_size", "20");
// Keymap // Keymap
settings->setDefault("remote_port", "30000"); settings->setDefault("remote_port", "30000");

@ -85,6 +85,23 @@ int ModApiClient::l_display_chat_message(lua_State *L)
return 1; 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() // get_player_names()
int ModApiClient::l_get_player_names(lua_State *L) 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(get_current_modname);
API_FCT(print); API_FCT(print);
API_FCT(display_chat_message); API_FCT(display_chat_message);
API_FCT(send_chat_message);
API_FCT(clear_out_chat_queue);
API_FCT(get_player_names); API_FCT(get_player_names);
API_FCT(set_last_run_mod); API_FCT(set_last_run_mod);
API_FCT(get_last_run_mod); API_FCT(get_last_run_mod);

@ -37,6 +37,12 @@ private:
// display_chat_message(message) // display_chat_message(message)
static int l_display_chat_message(lua_State *L); 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() // get_player_names()
static int l_get_player_names(lua_State *L); static int l_get_player_names(lua_State *L);