The chat filter matched short listed words anywhere inside a longer one, so
entries like "gger", "spik", "crip" and "coon" in the backend's lists blocked
"bigger", "spike", "script" and "raccoon".
- A listed word of four or five letters now matches inside a longer word only
when the rest of it is what goes around a swear word ("motherfuckers",
"bullshit", "shithead", "shitty", "xxfuckxx"). Words of six letters or more
still match anywhere.
- An ordinary word that equals a listed phrase without its spaces
("the rapist" / "therapist") no longer matches.
- bad_words.txt is gone: the filter has no words until the backend's lists
arrive.
Dedicated servers:
- "use_global_word_filter" (default true) turns the backend's lists off for a
server. Console: words global on|off.
- data/words_filtered.txt and data/words_blocked.txt hold a server's own
words, read at start and by "words reload". Blocked words follow
word_warnings. Filtered words are sent to players (new packet, kind 33),
whose games star them for players with the chat filter on and drop them on
leaving.
Multiplayer outfits: a player whose mods add or remove skater or board slots
no longer makes everyone look like the local player. A peer's items are put
in this game's slots by slot hash; slots the peer lacks stay empty and items
in slots this game lacks are left out.
- maps.park_mods binds a Park Editor park mod (Mods/<folder>/parks/<key>.park.json)
to a name and the map it was built on; maps.park_limits caps the parks and
objects spawned at once (3 and 512 by default)
- park-mod list | add <name> [<name>...] [minutes] | remove <name> [<name>...] | clear,
from the console, as /park-mod by admins, and by players through custom votes
- A spawned park is the layout of the server's own SteamID and epoch, sent like a
player's: games since 1.0.0 already show it, it counts against no player's
object limit, nobody can move or delete it, and it stays when players leave
- Objects players' games would not take (not Build Kit items, broken values) are
skipped and logged; a missing or broken park is an error line when spawning
- Optional expiry (1-1440 min) with a warning in chat a minute before; every
park goes on a map change and none survive a restart
- [parks] log lines name who did it: console, the admin, or "a vote"; a vote's
refusal reaches chat
- park-mod is reserved for custom commands; README.txt documents the settings,
commands and a park/unpark vote
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2G4APEkhHgVs59RDL3XWD
Dedicated servers: roles, with their tags, commands and permissions
A dedicated server had one list of admins who could do everything, and
everyone else could do nothing. It now has roles, kept in
data/roles.json beside the bans: each a name, the tag its players wear
in the game, the server commands it may use, what it may do besides
commands, and a rank.
A server starts with five: player (everyone given no role: chat, votes,
parties, private messages), vip (a reserved slot), moderator (kick, ban,
messages, giving out lower roles), admin (everything) and muted. The
default role is whoever was given none; a role has what the default has
as well as its own unless it says it does not inherit. A reserved slot
is a permission, so a role's players have one without being listed.
Nobody kicks, bans or changes the role of a player whose rank is theirs
or higher, or gives out a role that is; the console is above every role.
"*" is every command but role and reserved, which a role has only when
it lists them.
New commands: role <player or SteamID64> <role>|none, roles, and roles
reload. "admin add|remove" and "admins" are gone: the admins an older
config lists are moved into the admin role the first time the server
starts, and leave the config.
In the game, a player's role tag and colour show in chat and on their
nametag, and each control in the Multiplayer menu, the map, time of
day, world layers and parks follows the command it needs rather than
one admin flag, as do the commands chat offers.
Protocol 48: the roster carries each player's tag, and a new
permissions packet tells a player what their role gives them. Older
games and servers do not connect to newer ones.
Also fixes a name clash that kept the server config tests from
compiling.
### Overview
Hey! This PR adds an optional native DirectX 12 graphical interface to the dedicated server (`ReSkateServer.exe`), making it easier to monitor and manage a server without having to rely solely on the raw terminal.
### What's included:
- **DirectX 12 / ImGui Management GUI**:
- Live console with color coding and command input.
- Connected players list with real-time ping, position coordinates (click to copy), and kick/ban controls.
- Live map switcher and world layer manager.
- Ban list & admin manager.
- Real-time tick rate / latency graph and live telemetry table.
- Server configuration & release update checker in Settings.
- **In-Game Badges & Roles**: Synced with official nametags/role badges (`DEVELOPER`, `CREATOR`, `HOMIE`, etc.) just like in-game HUDs.
- **Headless CLI Support**: Kept full compatibility for headless/VPS setups — running with `--headless`, `--cli`, or `--no-gui` (or on Linux) runs standard terminal mode as usual.
- **Thread Safety & Performance**: The GUI runs on the main Win32/DX12 thread while server host/networking ticks run asynchronously on worker threads so tick rate is never blocked.
Tested on Windows Server 2022 with Release x64 builds against local and Steam SDR sessions. Let me know if you'd like any adjustments!
A dedicated server with word_warnings set to 0 passed such messages on.
It now never does: at 0 the message is dropped, its player is told it
was not sent, and nobody is warned or kicked. The team's lists are read
on every server for this, including one with use_global_bans off, which
still lets globally banned players in.
The bad-word list now comes from the ReSkate backend's Words page, in
the same /api/v1/steam-ids answer the game and servers already read.
It has two lists: filtered words, and words not allowed in chat at
all. Until the backend has any words the built-in list stays in use;
once it does, its lists replace the built-in one.
Filtered words work as before: masked for players with the chat filter
on, and not allowed in server or lobby names. Words not allowed at all
are filtered the same way, and a chat message with one is not passed
on. Its player is warned each time, and after three warnings the next
one gets them kicked.
Dedicated servers: new players setting word_warnings (default 3, 1 to
10, 0 switches the rule off). The count lasts while the server runs,
so a kicked player who joins again is kicked at their next one. Party
chat and commands are checked too, and each warning and kick is logged
under [words]. The lists are read whenever global bans or word
warnings are on.
Player lobbies: the host's game applies the same rule to its guests,
with three warnings.
Conflicts in Server/server_host.cpp: main changed the command code after this
branch moved it to Server/server_commands.cpp. The moved copy now has what
main added since: the "chat-color" command, "park random", and its line in
the help text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Server config:
- Written in sections, in reading order: server, access, maps, players,
anti_cheat, network, votes. A config from an older version, flat and
under older names, is read as it is and written back this way on first
start; only settings that are really new are reported as new.
- Renamed: no_bail, noclip, boosts, parties, voice_chat to allow_*;
global_bans to use_global_bans; welcome to welcome_message; speed_check
to speed_hack; score_check to modified_scoring; score_allow to
allowed_scoring_mods; map_pool and map_rotation_minutes to maps.pool and
maps.rotation_minutes. Console and admin commands are unchanged.
- Bans are kept in data/bans.json beside the config. A config that holds
them has them moved there; a bans file that cannot be read stops the
server from starting.
- Reserved slots are one list, "reserved_players_slots": a slot is kept
for each listed player who is not on. "reserved_slots" and the
"reserved slots <n>" command are gone; admins may take a kept slot but
none is kept for them.
- "tps" is no longer in the file: dedicated servers run at 20.
- "bone_scale_limit" is 1 by default: resized body parts are off unless a
server allows them.
- New: "pack_ms" (10), how long Steam may hold a message to pack it with
others; "finger_distance" (25); "steam_debug", which logs what Steam's
networking says, for connection faults.
Poses and skater sound between a dedicated server and its players use new
formats. Lobbies and the links between games are unchanged.
Poses (pose_codec.h, pose_batch.h):
- A pose is rounded once (rotations to 0.08 degrees, positions to a
millimetre) and sent as its differences from a pose of the same player
the receiver has acknowledged. Several players' poses share one message.
- The receiver acknowledges messages once a tick; the sender builds only on
acknowledged poses, or on the whole pose it sent reliably. Loss costs the
poses in that packet and nothing more.
- Fingers are not sent to players further than "finger_distance" (25 m).
- Games upload their own poses the same way, and the server acknowledges.
- About 300 B a pose at full rate against about 660 B before.
- The server no longer rounds rotations by distance: it saved little.
Sound (sound_codec.h):
- Each sample is sent as what changed since the one before, numbers rounded
to 1/1024. One reliable message a tick carries every skater a player can
hear. A stream is sent whole at its start and every two seconds.
- About 106 B a message against 368 B before, on a recording of real play.
Direct connections:
- "connection": "relay" (default) or "direct", with "direct_address" and
"direct_port". A direct server listens on UDP and is listed with its
address; players still sign in through Steam.
- Games connect straight to servers that offer it and fall back to the
relays after 5 s. Setting and "mp direct-connections" to turn it off.
Server:
- "bone_scale_limit" is 1 by default: resized body parts are off unless a
server allows them.
- Logs Steam's relay network status at start-up and in "net", and watches
the Steam sign-in.
- "pack_ms" (10): how long Steam may hold a message to pack it with others.
- "steam_debug": logs what Steam's networking says, for connection faults.
- "relay_everything": sends poses and sound through the server even between
games that have linked, for testing with few players.
- "net" shows pose sizes by rate, the share sent without fingers, and whole
poses sent.
Game:
- Only ReSkate's nametags are shown; the game's own are removed.
- "mp pose-dump <seconds>" records the local skater's poses and sound.
Tests: pose_codec_tests covers exact round trips, chains of differences,
held fingers, damaged input, the sender and receiver over a link that
delays, loses and reorders, and the sound format; with a recording as its
argument it prints sizes on real play.
Protocol 44: servers and games must both update.
Server browser
- Official servers (the backend's "official_servers") sort first and are
drawn in ReSkate blue
- Servers Steam friends are skating in sort next, drawn in green with the
friends' names; searching a friend's name finds their server
- A player in a session they joined can join another server straight from
the list; hosts still end their session first, and a Steam friend's join
offer never replaces a session
- Joining a dedicated server from the list records it as the player's
public session, so friends see it and the row shows HERE
- Blocked servers (the backend's "blocked_servers") are hidden and cannot
be joined, by code either
- When the backend sets "server_tokens_required", dedicated servers that
signed in without a login token are hidden, except on the player's own
network
- A search that gets no internet list from Steam keeps the last list for
two minutes, and waits six seconds before treating it as empty
Roles
- New Staff tag from the backend's "staff" list: green name, badge and
item animation, ranked after Dev
- Staff can pick a rainbow for their items on the Special page; it is sent
as a solid colour older builds can show, so the protocol is unchanged
Dedicated server
- steam_token: sign in with a Steam game server login token and keep the
same Steam ID every start; empty signs in anonymously as before
- A listed server without a token says so at startup, and when the token
rule starts or stops hiding it
- Linux servers update themselves from the release's "server_linux"
tarball, with the machine's curl and tar, pinned by SHA-256, and carry
on in the same process
Launcher
- Only create the MiniDumpWriteDump API-set shim under Wine/Proton
In a server folder outside the ANSI code page, path.string() threw while building the error for a failed config write. At startup that second throw escaped the catch in main, so the server crashed with an empty log instead of saying it could not write the config.
- Read Dev / Creator / Homie Steam IDs from api.reskate.dev instead of the hardcoded list
- Creator (red) and Homie (gold) tags and hoodie/board colours; Admin tag is now pink
- Global bans: lobby hosts and dedicated servers turn banned players away
("global_bans": false in ReSkateServer.json opts a server out)
- SPECIAL menu page for listed players to hide their own tag and items
- Fix hoodie and board keeping the animated colour after the animation turns off
- Multiplayer protocol 39 -> 40
The Linux console reader split stdin on '\n' only, so a line ending in
"\r\n" kept its '\r': "quit" did not stop the server and every command
answered Unknown command. Lines from a script saved on Windows, a hosting
panel or telnet end that way. (The Windows reader uses std::getline on a
text-mode stream, which already drops the '\r'.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A double-clicked server closed its window with the error still in it. It only
waits when it is alone on a visible console, so terminals, hosting panels and
hidden scheduled tasks still see it exit at once.
Control pipes driven by one-shot writers (echo cmd > fifo, fleet
tools) close the write end after each line; read()==0 then means
'no writers right now', not EOF. Only treat 0 as end-of-input for
non-FIFOs (/dev/null, files, terminals). Sleep briefly on empty
FIFO polls so POLLHUP does not spin.
- console reader: poll()+read() instead of iostream getline; joinable
thread joined before Steam shutdown/exit (iostream lock deadlock
needed Ctrl+D before)
- README: Linux dedicated-server setup guide
- CI: version input, tag builds, ReSkateServer-Linux-<version> asset
with setup script staged at top level