diff --git a/Extension/Multiplayer/Session/session_receive.cpp b/Extension/Multiplayer/Session/session_receive.cpp index a5efa25..6fb3137 100644 --- a/Extension/Multiplayer/Session/session_receive.cpp +++ b/Extension/Multiplayer/Session/session_receive.cpp @@ -1030,7 +1030,8 @@ void networking(Session &s, const NativeFrame &local, std::uint64_t now) { continue; } // Grey dots above the sender. Not a chat line. "On" is held to a few a second so a - // modified client cannot flood the relay; "off" always goes through. + // modified client cannot flood the relay, and "off" only clears dots that are up, so + // it cannot outpace "on". if (p.kind == PacketKind::typing) { auto *sender = find_peer(s, p.source); if (direct_link || !sender || !sender->handshaken) continue; @@ -1041,6 +1042,7 @@ void networking(Session &s, const NativeFrame &local, std::uint64_t now) { sender->typing_notice = now; sender->typing_until = now + typing_hold_us; } else { + if (!sender->typing_until) continue; sender->typing_until = 0; } sender->last_packet = now; diff --git a/Extension/Multiplayer/Test/mesh_tests.cpp b/Extension/Multiplayer/Test/mesh_tests.cpp index bc48341..ce0aa71 100644 --- a/Extension/Multiplayer/Test/mesh_tests.cpp +++ b/Extension/Multiplayer/Test/mesh_tests.cpp @@ -2291,6 +2291,12 @@ void typing_checks() { sim.run(4); check(until(host, id(first)) == 0 && until(second, id(first)) == 0 && first.local_typing_until == 0, "Stopping typing left the dots up"); + // A stop with no dots up is not passed on: the other guest would lose this marker if it were. + find_peer(second, id(first))->typing_until = sim.now + typing_hold_us; + send_typing(first, false); + sim.run(4); + check(until(second, id(first)) != 0, "A stop notice with no dots up was relayed"); + find_peer(second, id(first))->typing_until = 0; send_typing(host, true); sim.run(4); check(until(first, id(host)) > sim.now && until(second, id(host)) > sim.now && host.local_typing_until != 0, diff --git a/Extension/UI/Overlay/chat_overlay.cpp b/Extension/UI/Overlay/chat_overlay.cpp index 31ca11c..62a95a4 100644 --- a/Extension/UI/Overlay/chat_overlay.cpp +++ b/Extension/UI/Overlay/chat_overlay.cpp @@ -274,7 +274,8 @@ bool is_emote_text(std::string_view text, std::string_view before, std::string_v } // The session relays this. Dots mean the input changed in the last three seconds, not that -// the box is merely open. A failed queue is tried again next frame. +// the box is merely open. A failed queue is tried again next frame. A "/" line is a command, +// a private message or party chat: not everyone's business, so it shows no dots. void note_typing(ChatState& c, bool open) { const auto now = Clock::now(); if (!open) { @@ -284,6 +285,13 @@ void note_typing(ChatState& c, bool open) { return; } const std::string_view text(c.input.data()); + if (const auto first = text.find_first_not_of(' '); first != std::string_view::npos && text[first] == '/') { + if (c.typing_on && queue_multiplayer_action("typing", "0")) c.typing_on = false; + c.typing_seen.assign(text); + c.typing_watch = true; + c.typing_changed_valid = false; + return; + } if (!c.typing_watch) { c.typing_seen.assign(text); c.typing_watch = true; diff --git a/Server/server_host.cpp b/Server/server_host.cpp index e61b503..6290d3b 100644 --- a/Server/server_host.cpp +++ b/Server/server_host.cpp @@ -1267,12 +1267,17 @@ void Host::receive(std::uint64_t peer, std::span bytes, std: return; } // Grey dots above the sender. Not a chat line, and not logged. A player who may not - // chat sends none. "On" is held to a few a second; "off" always goes through. + // chat sends none. "On" is held to a few a second, and "off" is passed on only after an + // "on", so neither can flood the relay. if (p.kind == PacketKind::typing) { if (!routed_source(p, link->member, peer, true, id_) || !may(peer, multiplayer::role_chat)) return; if (p.typing) { if (link->typing_notice && now_ < link->typing_notice + 200000) return; link->typing_notice = now_; + link->typing = true; + } else { + if (!link->typing) return; + link->typing = false; } link->last_packet = now_; active(*link); diff --git a/Server/server_host.h b/Server/server_host.h index d3c0d6d..09af046 100644 --- a/Server/server_host.h +++ b/Server/server_host.h @@ -189,6 +189,7 @@ class Host { unsigned effects_pass{}; ChatRate chat_rate; std::uint64_t typing_notice{}; // last accepted "still typing", so a flood is not relayed + bool typing{}; // an "on" was passed along and no "off" since ChatBudget admin_budget, throwdown_budget, party_budget; SpeedCheck speed; // how fast their game runs, from their pose timestamps bool speeding{}; // flagged: out of linked activities (config speed_check)