mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 13:57:05 +02:00
Add server direct messages: msg, msg-party, msg-admins and /w
Admins and the console can DM one player, a player's party or all online admins. Players can whisper with /w. Messages carry a [DM from ...] prefix. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhQ3AZZ7KQddTqV67tZMLc
This commit is contained in:
1 parent
dd1913b647
commit
3b3dbf8cd7
6 files changed
+93
-3
No files matched your search
@@ -67,6 +67,9 @@ if(DINGOSDK_BUILD_MULTIPLAYER_TESTS)
|
||||
Extension/Throwdowns/throwdown_wire.cpp)
|
||||
target_include_directories(dingosdk_server_activity_tests PRIVATE "${PROJECT_SOURCE_DIR}")
|
||||
add_test(NAME server_activity COMMAND dingosdk_server_activity_tests)
|
||||
add_executable(dingosdk_server_text_tests Server/Test/server_text_tests.cpp)
|
||||
target_include_directories(dingosdk_server_text_tests PRIVATE "${PROJECT_SOURCE_DIR}")
|
||||
add_test(NAME server_text COMMAND dingosdk_server_text_tests)
|
||||
add_executable(dingosdk_speed_check_tests Server/Test/speed_check_tests.cpp)
|
||||
target_include_directories(dingosdk_speed_check_tests PRIVATE "${PROJECT_SOURCE_DIR}")
|
||||
add_test(NAME server_speed_check COMMAND dingosdk_speed_check_tests)
|
||||
|
||||
@@ -123,6 +123,11 @@ and change voice, distances, placement and kicks from the Multiplayer menu.
|
||||
status Name, map, players, code.
|
||||
players Connected players and their SteamID64s.
|
||||
say <text> Chat as the server (console only).
|
||||
msg <player> <text> Private message, shown to them as "[DM from <you>] ...".
|
||||
Name start (one word) or SteamID64.
|
||||
msg-party <player> <text> Message everyone in that player's party ("[DM from <you> to party]").
|
||||
msg-admins <text> Message every admin who is online ("[DM from <you> to admins]").
|
||||
Players can whisper each other with /w <player> <text> in chat.
|
||||
kick <player> Until the server restarts. Name start or SteamID64.
|
||||
Admins cannot kick or ban each other; the console can.
|
||||
ban <player or id> [name] For good. unban <id> bans
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "Server/server_text.h"
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using dingosdk::server::dm_line;
|
||||
namespace {
|
||||
void check(bool ok, const std::string &message) {
|
||||
if (ok) return;
|
||||
std::cerr << message << '\n';
|
||||
std::exit(1);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
check(dm_line("Server", {}, "hi", 100) == "[DM from Server] hi", "plain DM");
|
||||
check(dm_line("Player", "party", "hi", 100) == "[DM from Player to party] hi", "party DM");
|
||||
check(dm_line("Player", "admins", "hi", 100) == "[DM from Player to admins] hi", "admins DM");
|
||||
const auto cut = dm_line("Server", {}, std::string(500, 'a'), 40);
|
||||
check(cut.size() == 40 && cut.starts_with("[DM from Server] "), "text is cut, marker kept");
|
||||
// Never cut through a UTF-8 character (\xC3\xA9 is one letter).
|
||||
const auto utf8 = dm_line("S", {}, "\xC3\xA9\xC3\xA9\xC3\xA9", 12);
|
||||
check(utf8 == "[DM from S] \xC3\xA9" || utf8 == "[DM from S] ", "UTF-8 boundary");
|
||||
check(dm_line("Server", {}, "hi", 3) == "[DM from Server] ", "tiny limit keeps no text");
|
||||
return 0;
|
||||
}
|
||||
+36
-1
@@ -42,7 +42,7 @@ std::uint64_t nonce() {
|
||||
return value;
|
||||
}
|
||||
constexpr std::string_view help_text =
|
||||
"status | players | say <text> | kick <player> | ban <player or SteamID64> [name] | unban <SteamID64> | bans\n"
|
||||
"status | players | say <text> | msg <player> <text> | msg-party <player> <text> | msg-admins <text> | kick <player> | ban <player or SteamID64> [name] | unban <SteamID64> | bans\n"
|
||||
"map <name, e.g. San Vansterdam> | maps | name <text> | password <text|off> | welcome <text|off> | listed on|off\n"
|
||||
"tps 20|30|60|120 | voice on|off | voice-range <50-1000> | distances <full> <half> <half-return> <low>\n"
|
||||
"placement everyone|admins|nobody | clear-objects | noclip on|off | nobail on|off | boosts on|off | tuning on|off\n"
|
||||
@@ -863,6 +863,41 @@ std::string Host::command(std::string_view line, std::uint64_t admin) {
|
||||
send_chat(argument);
|
||||
return "[chat] Server: " + clean_chat_text(argument);
|
||||
}
|
||||
if (name == "msg" || name == "msg-party" || name == "msg-admins") {
|
||||
// Direct messages from the console or an admin, marked "[DM from ...]" so nobody takes them for chat.
|
||||
const auto *sender = console ? nullptr : find(admin);
|
||||
const std::string from = sender ? guest_name(*sender) : "Server";
|
||||
const bool to_admins = name == "msg-admins";
|
||||
const auto [who, text] = to_admins ? std::pair<std::string_view, std::string_view>{{}, trim(argument)} : split(argument);
|
||||
if (text.empty()) return to_admins ? "msg-admins <text>" : name + " <player> <text>";
|
||||
std::vector<Guest *> recipients;
|
||||
std::string scope, label;
|
||||
if (to_admins) {
|
||||
scope = label = "admins";
|
||||
for (auto &[id, guest] : guests_)
|
||||
if (guest->handshaken && is_admin(id)) recipients.push_back(guest.get());
|
||||
} else {
|
||||
auto *guest = match_player(who);
|
||||
if (!guest) return "No single connected player matches \"" + std::string(who) + "\".";
|
||||
label = guest_name(*guest);
|
||||
if (name == "msg") {
|
||||
recipients.push_back(guest);
|
||||
} else {
|
||||
const auto *details = parties_.party(parties_.party_of(guest->member.id));
|
||||
if (!details) return label + " is not in a party.";
|
||||
scope = "party";
|
||||
label += "'s party";
|
||||
for (const auto member : details->members)
|
||||
if (auto *found = find(member); found && found->handshaken) recipients.push_back(found);
|
||||
}
|
||||
}
|
||||
if (recipients.empty()) return "No admins are online.";
|
||||
const auto line = dm_line(from, scope, text, multiplayer_chat_max_bytes);
|
||||
for (auto *guest : recipients) send_chat(line, guest);
|
||||
const auto done = "Sent to " + label + (recipients.size() > 1 || to_admins ? " (" + std::to_string(recipients.size()) + " players)" : "") + ".";
|
||||
if (!console) log_("[dm] " + from + " -> " + label + ": " + clean_chat_text(text));
|
||||
return done;
|
||||
}
|
||||
if (name == "kick") {
|
||||
auto *guest = target(argument);
|
||||
if (!guest || !guest->handshaken) return "No single connected player matches \"" + std::string(argument) + "\".";
|
||||
|
||||
@@ -44,6 +44,16 @@ inline void cut_text(std::string &text, std::size_t limit) {
|
||||
while (cut && (static_cast<unsigned char>(text[cut]) & 0xC0) == 0x80) --cut;
|
||||
text.resize(cut);
|
||||
}
|
||||
// A direct message as the recipient reads it: "[DM from <from>] text", or "[DM from <from> to <scope>] text" for
|
||||
// a group. The text is cut so the whole line fits `limit` bytes and the marker is never lost.
|
||||
inline std::string dm_line(std::string_view from, std::string_view scope, std::string_view text, std::size_t limit) {
|
||||
std::string head = "[DM from " + std::string(from);
|
||||
if (!scope.empty()) head += " to " + std::string(scope);
|
||||
head += "] ";
|
||||
std::string body(text);
|
||||
cut_text(body, limit > head.size() ? limit - head.size() : 0);
|
||||
return head + body;
|
||||
}
|
||||
// A command as the log shows it: the log is plain text, so a new password is left out.
|
||||
inline std::string loggable(std::string_view command) {
|
||||
const auto [verb, argument] = split(command);
|
||||
|
||||
+13
-2
@@ -68,14 +68,25 @@ void Host::chat_command(Guest &guest, std::string_view line) {
|
||||
if (votes & server_vote_time) text += "/vote tod <time>: vote for a time of day (morning, noon, night...)\n";
|
||||
if (votes) text += "/yes or /no: vote in the running vote\n";
|
||||
if (config_.parties) text += "/party: your party (invite, accept, leave...; /party help); /p <message>: party chat\n";
|
||||
if (is_admin(guest.member.id)) text += "Admins: any server command as /<command>, e.g. /kick, /map, /tpall, /votes\n";
|
||||
return reply(guest, text.empty() ? "This server has no player votes. Type /tp <player> to teleport." : text);
|
||||
if (is_admin(guest.member.id)) text += "Admins: any server command as /<command>, e.g. /kick, /map, /tpall, /votes, /msg, /msg-party, /msg-admins\n";
|
||||
const std::string whisper = "/w <player> <message>: send a private message";
|
||||
return reply(guest, (text.empty() ? "This server has no player votes. Type /tp <player> to teleport.\n" : text) + whisper);
|
||||
}
|
||||
if (verb == "party") return party_command(guest, rest);
|
||||
if (verb == "p") {
|
||||
if (!config_.parties) return reply(guest, "Parties are off on this server.");
|
||||
return party_chat(guest, rest);
|
||||
}
|
||||
if (verb == "w" || verb == "whisper" || verb == "tell") {
|
||||
// A private message to one player, marked "[DM from ...]"; the sender sees an echo.
|
||||
const auto [who, text] = split(rest);
|
||||
if (text.empty()) return reply(guest, "/w <player> <message>, e.g. /w player hello");
|
||||
auto *other = match_player(who);
|
||||
if (!other) return reply(guest, "No single connected player matches \"" + std::string(who) + "\".");
|
||||
if (other == &guest) return reply(guest, "You cannot message yourself.");
|
||||
send_chat(dm_line(guest_name(guest), {}, text, multiplayer_chat_max_bytes), other);
|
||||
return reply(guest, "[DM to " + guest_name(*other) + "] " + clean_chat_text(text));
|
||||
}
|
||||
if (verb == "yes" || verb == "y") return cast_vote(guest, true);
|
||||
if (verb == "no" || verb == "n") return cast_vote(guest, false);
|
||||
if (verb == "vote") {
|
||||
|
||||
Reference in new issue
Block a user