diff --git a/Extension/Multiplayer/word_lists.h b/Extension/Multiplayer/word_lists.h index 31845210..7133dd37 100644 --- a/Extension/Multiplayer/word_lists.h +++ b/Extension/Multiplayer/word_lists.h @@ -24,4 +24,7 @@ inline constexpr unsigned word_warnings_default = 3, word_warnings_most = 10; inline constexpr std::string_view word_kick_notice = "Kicked for using words that are not allowed here."; // What the player is told at their `count`th warning of `warnings`. std::string word_warning(unsigned count, unsigned warnings); +// And where nobody is warned or kicked (a server's "word_warnings": 0): the message is still +// not passed on. +inline constexpr std::string_view word_blocked_notice = "Your message was not sent: it has a word that is not allowed here."; } // namespace dingosdk::multiplayer diff --git a/Server/README.txt b/Server/README.txt index 0ced175f..7013c19a 100644 --- a/Server/README.txt +++ b/Server/README.txt @@ -122,11 +122,12 @@ afk_kick_minutes Remove a player who has been away this many minutes, 1-1440 never removed for it. Console: afk-kick |off. word_warnings The ReSkate team keeps a list of words that are not allowed in chat at all (read from api.reskate.dev with the global - bans). A message with one is not passed on, and its player - is warned. After this many warnings, 1-10 (default 3), the - next one gets them kicked; they can join again, and are - kicked again at the next. Warnings last until the server - restarts. 0: such messages are passed on like any other. + bans). A message with one is never passed on, whatever + this is set to, and its player is warned. After this many + warnings, 1-10 (default 3), the next one gets them kicked; + they can join again, and are kicked again at the next. + Warnings last until the server restarts. 0: nobody is + warned or kicked; the message is still not passed on. allow_voice_chat Allow voice chat. voice_range How far proximity voice reaches, 50-1000 m. object_placement everyone, admins (only admins can build), or nobody. diff --git a/Server/main.cpp b/Server/main.cpp index 9ab7fc11..5638c57a 100644 --- a/Server/main.cpp +++ b/Server/main.cpp @@ -478,7 +478,8 @@ int run(int argc, char **argv, bool skip_update) { std::future update_check; bool update_now{}, update_waiting{}, restart{}; // The backend's ban list (global_bans.h): read now and every ten minutes, a minute after a - // failure. "global_bans": false leaves it unread and lets those players in. + // failure. "global_bans": false lets those players in; the answer is still read, for the + // chat word lists that come in it. std::future ban_check; auto next_ban_check = next_advertise; bool bans_unread{}; @@ -552,8 +553,9 @@ int run(int argc, char **argv, bool skip_update) { } update_now = false; } - // (The chat word lists come in the same answer, so it is read for them too.) - if ((config.global_bans || config.word_warnings) && !ban_check.valid() && now_time >= next_ban_check) + // (The chat word lists come in the same answer, and hold on every server: it is read + // for them whether or not the bans are used.) + if (!ban_check.valid() && now_time >= next_ban_check) ban_check = std::async(std::launch::async, read_global_bans); if (ban_check.valid() && ban_check.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { const auto check = ban_check.get(); @@ -564,10 +566,10 @@ int run(int argc, char **argv, bool skip_update) { if (check.ok && check.words_changed) write_log("Word lists: " + std::to_string(check.filtered_words) + " filtered and " + std::to_string(check.forbidden_words) + " not allowed at all, from the ReSkate team's lists." + - (config.word_warnings ? "" : " (\"word_warnings\" is 0: nobody is warned or kicked for them here.)")); + (config.word_warnings ? "" : " (\"word_warnings\" is 0: a message with one is not passed on, and nobody is warned or kicked.)")); else if (!check.ok && !bans_unread) - write_log("The global ban list could not be read (" + check.problem + "). Trying again every minute; " + - "until then the bans already read hold."); + write_log("The ReSkate team's lists (global bans and chat words) could not be read (" + check.problem + + "). Trying again every minute; until then the ones already read hold."); bans_unread = !check.ok; } if (update_waiting && !restart && host.players() == 0) { diff --git a/Server/server_config.h b/Server/server_config.h index 6e063da1..b334f0f6 100644 --- a/Server/server_config.h +++ b/Server/server_config.h @@ -113,9 +113,9 @@ struct ServerConfig { // Minutes a player may be away (not moving, talking, typing or building) before the server // removes them, 1 to 1440; 0: never. Admins are never removed for it. unsigned afk_kick = 0; - // A chat message with a word the ReSkate team does not allow at all (word_lists.h) is not - // passed on and its player warned; after this many warnings, 1 to 10, the next gets them - // kicked. 0: such messages are passed on like any other. + // A chat message with a word the ReSkate team does not allow at all (word_lists.h) is never + // passed on. Its player is warned; after this many warnings, 1 to 10, the next gets them + // kicked. 0: nobody is warned or kicked, and the message is still not passed on. unsigned word_warnings = 3; // Players whose game runs fast (a speedhack; Server/speed_check.h): "warn" takes them out of // throwdowns and coop challenges and tells the admins, "kick" also removes them, "off" does not check. diff --git a/Server/server_host.cpp b/Server/server_host.cpp index 8654dd94..ea231d30 100644 --- a/Server/server_host.cpp +++ b/Server/server_host.cpp @@ -701,7 +701,13 @@ void Host::send_chat(std::string_view text, Guest *only) { else broadcast(message, true, false); } bool Host::allowed_words(Guest &guest, std::string_view text) { - if (!config_.word_warnings || !text::contains_forbidden_words(text)) return true; + if (!text::contains_forbidden_words(text)) return true; + // Never passed on. Without warnings ("word_warnings": 0) that is all that happens. + if (!config_.word_warnings) { + log_("[words] " + guest_name(guest) + " said a word that is not allowed; the message was not passed on."); + send_chat(multiplayer::word_blocked_notice, &guest); + return false; + } const auto id = guest.member.id; const auto count = ++word_warnings_[id]; if (count > config_.word_warnings) {