mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 13:57:05 +02:00
Server: its chat lines in colours the owner chooses; protocol 45
Server: its chat lines in colours the owner chooses; protocol 45 A dedicated server's own lines in chat (its welcome, console messages and answers) were made violet in #129. The owner can now pick the colours. - "chat_color": the "Server" badge and name, as "#RRGGBB" (default "#8E5CFF", violet). - "chat_text_color": the text of those lines (default "#D9C8FF", lavender). - Both are in the config's "server" section; a value that is not a colour stops the server from starting and names the setting. - Console: chat-color <#badge> [<#text>]. Saved, and sent to everyone on the server, so the next line uses it. - With the defaults, chat looks as it did. The colours travel in the roster, so this is protocol 45: servers and games must both update. Lobbies hosted by a player are unchanged.
This commit is contained in:
1 parent
e57bae499a
commit
0b58163de5
13 files changed
+72
-5
No files matched your search
@@ -108,6 +108,9 @@ struct MultiplayerChatLine {
|
||||
// the overlay can keep emote names the filter caught; empty when nothing was masked.
|
||||
std::string unmasked;
|
||||
bool server{}; // said by the dedicated server itself: its console, welcome or answers
|
||||
// The colour of the line's own text when it is not the usual one (IM_COL32 layout, 0 = usual):
|
||||
// a dedicated server's lines, in the colour its owner chose.
|
||||
std::uint32_t text_color{};
|
||||
};
|
||||
// A command typed into chat with a leading "/" (shown as the player types "/").
|
||||
struct MultiplayerChatCommand {
|
||||
|
||||
@@ -597,6 +597,8 @@ std::vector<std::uint8_t> encode(const Packet &p, bool compact_pose, std::uint32
|
||||
w.integer(p.object_clears, 4);
|
||||
if (!valid_multiplayer_tps(p.tps)) throw std::invalid_argument("Invalid session TPS");
|
||||
w.integer(p.tps, 1);
|
||||
w.integer(p.chat_badge & 0xffffff, 3); // red, green, blue
|
||||
w.integer(p.chat_text & 0xffffff, 3);
|
||||
w.integer(static_cast<std::uint8_t>(p.object_placement), 1);
|
||||
if (!valid_object_limit(p.object_limit)) throw std::invalid_argument("Invalid object limit");
|
||||
w.integer(p.object_limit, 2);
|
||||
@@ -903,6 +905,8 @@ std::optional<Packet> decode(std::span<const std::uint8_t> bytes) noexcept {
|
||||
p.object_clears = static_cast<std::uint32_t>(r.integer(4));
|
||||
p.tps = static_cast<unsigned>(r.integer(1));
|
||||
if (!valid_multiplayer_tps(p.tps)) return {};
|
||||
p.chat_badge = 0xff000000U | static_cast<std::uint32_t>(r.integer(3));
|
||||
p.chat_text = 0xff000000U | static_cast<std::uint32_t>(r.integer(3));
|
||||
const auto placement = r.integer(1);
|
||||
if (!valid_object_placement(placement)) return {};
|
||||
p.object_placement = static_cast<ObjectPlacement>(placement);
|
||||
|
||||
@@ -25,7 +25,9 @@ namespace dingosdk::multiplayer {
|
||||
constexpr std::size_t max_skater_bones = 512, max_board_bones = 64;
|
||||
constexpr std::size_t max_packet = 24576;
|
||||
constexpr std::size_t packet_header_size = 64;
|
||||
constexpr std::uint16_t protocol_version = 44;
|
||||
constexpr std::uint16_t protocol_version = 45;
|
||||
// A dedicated server's chat lines unless its owner says otherwise: violet (#8E5CFF) and lavender (#D9C8FF).
|
||||
inline constexpr std::uint32_t default_server_chat_badge = 0xffff5c8eU, default_server_chat_text = 0xffffc8d9U;
|
||||
constexpr std::size_t max_throwdown_message = 4096;
|
||||
// Packet::tuning: the host's SkatePhysicsTuning differences (Extension/Skater/physics_tuning.h).
|
||||
constexpr std::size_t max_physics_tuning = 16384;
|
||||
@@ -176,6 +178,9 @@ struct Packet {
|
||||
// Bumped each time the host deletes all guest objects. Guests delete their
|
||||
// own session objects when it changes after their first roster.
|
||||
std::uint32_t object_clears{};
|
||||
// Roster: the colours of a dedicated server's own chat lines, its badge and name and the
|
||||
// text after them (IM_COL32 layout; the server's owner chooses them).
|
||||
std::uint32_t chat_badge = default_server_chat_badge, chat_text = default_server_chat_text;
|
||||
bool force_world_layers{};
|
||||
WorldLayerState layers = WorldLayerState(world_layers().size()); // all "default"
|
||||
ParkChoices parks; // One allowlisted selection byte per shared native park slot.
|
||||
|
||||
@@ -155,6 +155,8 @@ void stop(Session &s, std::string reason) {
|
||||
s.next_send = s.last_hello = s.last_roster = s.last_cosmetic_capture = s.last_routes =
|
||||
s.last_network_log = 0;
|
||||
s.sequence = s.roster_sequence = 0;
|
||||
s.server_chat_badge = default_server_chat_badge;
|
||||
s.server_chat_text = default_server_chat_text;
|
||||
s.pose_streams.clear();
|
||||
s.pose_ack = {};
|
||||
s.pose_ack_due = false;
|
||||
|
||||
@@ -212,6 +212,8 @@ struct Session {
|
||||
// and the server's voice range from it.
|
||||
bool server_admin{};
|
||||
float roster_voice_range = default_voice_range;
|
||||
// The colours of the dedicated server's own chat lines, as its roster gives them.
|
||||
std::uint32_t server_chat_badge = default_server_chat_badge, server_chat_text = default_server_chat_text;
|
||||
// The server's ban list, as sent to us while we are one of its admins.
|
||||
std::vector<MultiplayerBan> server_bans;
|
||||
std::uint32_t server_ban_total{};
|
||||
|
||||
@@ -307,6 +307,8 @@ void apply_roster(Session &s, const Packet &p, std::uint64_t now) {
|
||||
}
|
||||
s.capacity = p.capacity;
|
||||
s.roster_voice_range = p.voice_range;
|
||||
s.server_chat_badge = p.chat_badge;
|
||||
s.server_chat_text = p.chat_text;
|
||||
++s.party_revision; // anyone's party may have changed
|
||||
// A dedicated server knows players only by the name each sent in their hello.
|
||||
for (auto &peer : active_peers(s))
|
||||
|
||||
@@ -579,7 +579,7 @@ std::pair<std::uint32_t, std::string> mark_role(IdentityList list) {
|
||||
std::pair<std::uint32_t, std::string> player_role(Session &s, std::uint64_t sender, bool local, bool marks) {
|
||||
if (!sender) return {};
|
||||
const bool dedicated = dedicated_host(s);
|
||||
if (dedicated && sender == s.host_id) return {nametag_server, "Server"}; // the server itself
|
||||
if (dedicated && sender == s.host_id) return {s.server_chat_badge, "Server"}; // the server itself
|
||||
const auto *peer = local ? nullptr : find_peer(s, sender);
|
||||
const bool vouched = local || (peer && steam_vouched(s, *peer));
|
||||
// Who the backend says a player is comes before what they are in this lobby, unless they
|
||||
@@ -600,10 +600,11 @@ void add_chat(Session &s, std::uint64_t sender, std::string name, std::string te
|
||||
// The dedicated server's own lines (its chat, and its answers sent to this player alone) stand out.
|
||||
const bool server = dedicated_host(s) && (sender ? sender == s.host_id : name == "Server");
|
||||
if (server && !sender) {
|
||||
color = nametag_server;
|
||||
color = s.server_chat_badge;
|
||||
tag = "Server";
|
||||
}
|
||||
s.chat.push_back({++s.chat_sequence, sender, now_us(), std::move(name), std::move(text), local, color, std::move(tag), {}, server});
|
||||
s.chat.push_back({++s.chat_sequence, sender, now_us(), std::move(name), std::move(text), local, color, std::move(tag), {}, server,
|
||||
server ? s.server_chat_text : 0U});
|
||||
while (s.chat.size() > multiplayer_chat_history) s.chat.pop_front();
|
||||
publish_chat(s);
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ ImU32 name_colour(const MultiplayerChatLine& line) {
|
||||
|
||||
// A message's own colour: the server's lines in lavender, everyone else's white.
|
||||
ImU32 text_colour(const MultiplayerChatLine& line) {
|
||||
return line.server ? multiplayer::nametag_server_text : theme::paper;
|
||||
return line.text_color ? line.text_color : line.server ? multiplayer::nametag_server_text : theme::paper;
|
||||
}
|
||||
|
||||
// "name:"; a role tag is drawn in its own box before it (role_badge.h).
|
||||
|
||||
@@ -51,6 +51,11 @@ time the server starts.
|
||||
name Shown in the browser: 1-64 letters, numbers, spaces and - _ / [ ] ( ).
|
||||
password Empty for anyone; otherwise players type it to join.
|
||||
welcome_message A chat line sent to each player as they join.
|
||||
chat_color The colour of the server's own lines in chat: its "Server" badge
|
||||
and name, as "#RRGGBB" (default "#8E5CFF", violet).
|
||||
chat_text_color The colour of the text of those lines (default "#D9C8FF",
|
||||
lavender). Pick one that reads on a dark background.
|
||||
Console: chat-color <#badge> [<#text>].
|
||||
listed false hides the server; players then need the code.
|
||||
max_players 1-249.
|
||||
port, query_port Steam game server ports (default 27015, 27016).
|
||||
|
||||
@@ -192,6 +192,15 @@ int run() {
|
||||
"A config in sections did not read back as it was written");
|
||||
std::filesystem::remove_all(folder / "data");
|
||||
}
|
||||
// The server's chat colours: violet and lavender unless set, red first as written.
|
||||
{
|
||||
ServerConfig colours;
|
||||
check(colours.chat_color == "#8E5CFF" && parse_colour(colours.chat_color) == 0xffff5c8eU && parse_colour("d9c8ff") == 0xffffc8d9U,
|
||||
"The default chat colours are not violet and lavender");
|
||||
check(!parse_colour("#12345") && !parse_colour("#12345G") && !parse_colour("") && !parse_colour("#1234567"), "A bad colour was read");
|
||||
colours.chat_text_color = "blue";
|
||||
check(config_error(colours).find("chat_text_color") != std::string::npos, "A chat colour that is not one was accepted");
|
||||
}
|
||||
// Bans have a file of their own; a config that still holds them has them moved there.
|
||||
{
|
||||
std::filesystem::remove_all(folder / "data");
|
||||
|
||||
@@ -64,6 +64,8 @@ Layout layout(const ServerConfig &c) {
|
||||
server.set("name", c.name);
|
||||
server.set("password", c.password);
|
||||
server.set("welcome_message", c.welcome);
|
||||
server.set("chat_color", c.chat_color);
|
||||
server.set("chat_text_color", c.chat_text_color);
|
||||
server.set("listed", c.listed);
|
||||
server.set("max_players", c.max_players);
|
||||
server.set("port", static_cast<unsigned>(c.port));
|
||||
@@ -207,6 +209,8 @@ ServerConfig load_config(const std::filesystem::path &file, std::vector<std::str
|
||||
c.name = get("server", "name", c.name);
|
||||
c.password = get("server", "password", c.password);
|
||||
c.welcome = get("server", "welcome_message", c.welcome, {"welcome"});
|
||||
c.chat_color = get("server", "chat_color", c.chat_color);
|
||||
c.chat_text_color = get("server", "chat_text_color", c.chat_text_color);
|
||||
c.listed = get("server", "listed", c.listed);
|
||||
c.max_players = get("server", "max_players", c.max_players);
|
||||
// Checked here, not in config_error: once narrowed, 70000 is just port 4464, and the
|
||||
@@ -368,8 +372,22 @@ bool may_join(const ServerConfig &config, std::uint64_t id, std::size_t on) noex
|
||||
const auto listed = [&](const std::vector<std::uint64_t> &ids) { return std::find(ids.begin(), ids.end(), id) != ids.end(); };
|
||||
return (listed(config.reserved) || listed(config.admins)) && on < config.max_players + extra_slots(config);
|
||||
}
|
||||
std::optional<std::uint32_t> parse_colour(std::string_view text) noexcept {
|
||||
if (!text.empty() && text.front() == '#') text.remove_prefix(1);
|
||||
if (text.size() != 6) return std::nullopt;
|
||||
std::uint32_t rgb{};
|
||||
for (const char c : text) {
|
||||
const int digit = c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : c >= 'A' && c <= 'F' ? c - 'A' + 10 : -1;
|
||||
if (digit < 0) return std::nullopt;
|
||||
rgb = rgb << 4 | static_cast<std::uint32_t>(digit);
|
||||
}
|
||||
// Written red first; held with red lowest and opaque.
|
||||
return 0xff000000U | (rgb & 0xff) << 16 | (rgb & 0xff00) | rgb >> 16;
|
||||
}
|
||||
std::string config_error(const ServerConfig &c) {
|
||||
using namespace multiplayer;
|
||||
if (!parse_colour(c.chat_color)) return "chat_color must be a colour like #8E5CFF.";
|
||||
if (!parse_colour(c.chat_text_color)) return "chat_text_color must be a colour like #D9C8FF.";
|
||||
if (!valid_server_name(c.name)) return std::string("name must be ") + server_name_rule + ".";
|
||||
for (const auto id : c.reserved)
|
||||
if (!individual_steam_id(id)) return "reserved_players_slots must be SteamID64s (17 digits starting 7656119).";
|
||||
|
||||
@@ -62,6 +62,9 @@ struct ServerConfig {
|
||||
std::vector<std::uint64_t> reserved;
|
||||
std::string password; // empty: anyone may join
|
||||
std::string welcome; // sent to each player as they join
|
||||
// The colours of the server's own lines in chat, as "#RRGGBB": its badge and name, and the
|
||||
// text after them.
|
||||
std::string chat_color = "#8E5CFF", chat_text_color = "#D9C8FF";
|
||||
bool listed = true; // shown in the in-game server browser
|
||||
// A Steam game server login token (steamcommunity.com/dev/managegameservers, app 3354750).
|
||||
// With one the server signs in to its own account and keeps the same Steam ID every start,
|
||||
@@ -123,6 +126,8 @@ std::size_t extra_slots(const ServerConfig &config) noexcept;
|
||||
// max_players are on; the reserved players and the admins after that too, in the extra slots
|
||||
// (a full server of 32 shows 33/32 with one of them on).
|
||||
bool may_join(const ServerConfig &config, std::uint64_t id, std::size_t on) noexcept;
|
||||
// "#RRGGBB" (or "RRGGBB") as a colour in the layout the protocol and the overlay use, or nothing.
|
||||
std::optional<std::uint32_t> parse_colour(std::string_view text) noexcept;
|
||||
// Why `config` cannot run, or empty.
|
||||
std::string config_error(const ServerConfig &config);
|
||||
// A scoring fingerprint as the config and console write it (16 hex digits), and read back
|
||||
|
||||
@@ -578,6 +578,8 @@ void Host::send_roster() {
|
||||
auto p = packet(PacketKind::roster, now_);
|
||||
p.voice_policy = voice_policy_;
|
||||
p.voice_range = config_.voice_range;
|
||||
p.chat_badge = parse_colour(config_.chat_color).value_or(multiplayer::default_server_chat_badge);
|
||||
p.chat_text = parse_colour(config_.chat_text_color).value_or(multiplayer::default_server_chat_text);
|
||||
p.distances = config_.distances;
|
||||
p.object_placement = config_.object_placement;
|
||||
p.object_limit = config_.object_limit;
|
||||
@@ -1727,6 +1729,15 @@ std::string Host::command(std::string_view line, std::uint64_t admin) {
|
||||
config_.welcome = argument == "off" ? std::string{} : std::string(argument);
|
||||
return changed(config_.welcome.empty() ? "Welcome message removed." : "Welcome message set.");
|
||||
}
|
||||
if (name == "chat-color" || name == "chat-colour") {
|
||||
// chat-color <#badge> [<#text>]: the server's own lines in chat.
|
||||
const auto [badge, text] = split(argument);
|
||||
if (badge.empty() || !parse_colour(badge) || (!text.empty() && !parse_colour(text)))
|
||||
return "chat-color <#RRGGBB badge> [<#RRGGBB text>] (now " + config_.chat_color + " " + config_.chat_text_color + ")";
|
||||
config_.chat_color = std::string(badge);
|
||||
if (!text.empty()) config_.chat_text_color = std::string(text);
|
||||
return changed("The server's chat lines are " + config_.chat_color + " with " + config_.chat_text_color + " text.");
|
||||
}
|
||||
if (name == "announce-throwdowns") {
|
||||
const auto value = on_off(argument);
|
||||
if (!value) return std::string("announce-throwdowns on|off (now ") + (config_.announce_throwdowns ? "on" : "off") + ")";
|
||||
|
||||
Reference in new issue
Block a user