Commit Graph
135 Commits
Author SHA1 Message Date
AyeZeeBB 55a7342dc6 Multiplayer: dedicated servers require plugins, and players get them and the map on one card
A server lists Thunderstore packages in plugins.required ("Owner-Name", or "Owner-Name-1.2.3"
for at least that version, at most 16). It checks each on Thunderstore as it starts and leaves
out one not listed as a plugin, and sends the rest with its map offer (protocol 55).

A joining player's game checks them before the server's map loads. Those missing, switched off
or too old go on one card with the map when that is missing too, so the player is asked once.
GET ALL downloads and installs each, switches on any that were off, runs one live merge when
any brings game data, and joins; CANCEL leaves. The game fetches a package only when
Thunderstore lists it as a plugin and it installs as one (a reskate-plugin.json naming Luau
code), whatever a server asks; anything else is taken out again, so a cosmetic can never be
required.

map_download handles a list of packages instead of one map, and the Thunderstore chooser takes
the category it needs. A map alone keeps its card as it was.
2026-10-24 12:00:09 -05:00
Robin MoellerandClaude Opus 5.5 dd8e610482 Plugins: a dedicated server's plugins hide a player from others, or mute them
Hide and Seek, tag, spectating and ghost runs need one player to be
gone for some others, which no plugin could do.

reskate.server.hide(target, viewers, {skater, name, voice, sound}) and
reskate.server.unhide(target, viewers?) do it per viewer; viewers is a
player, a list of players or "all" (everyone on now but the target).

Wholly (the default) the target leaves those viewers' rosters, so their
games take the skater, its name tag and its objects away as for a player
who left, and the server sends them nothing of the target: poses, sound,
voice, outfit, effects and objects. No protocol change: the roster per
viewer is the one the cards and polls use, and the server's introduce
already holds a player back until shown. Shown again, the target is
introduced anew (outfit, then whole poses, then all its objects).

Without the skater (skater = false), only the voice and/or the sound is
held back and the player stays. Name and skater go together: a game has
no skater without its player, and hiding one without the other would
need a protocol change.

Refused while the target shares a party or a throwdown with one of the
viewers, whose game would lose a party member or a player mid-way; while
hidden, the two cannot come into one party. A plugin that stops shows
everyone it hid; a player who leaves is dropped from every list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYabE7FCPgj3SaWeF92EML
2026-10-24 12:26:31 +02:00
AyeZeeBB 194942779c Plugins: a dedicated server's plugins hear chat, know the map, change it and set the time of day
A server copy on a dedicated server had no chat, an empty session.world() and no way to change the
map or the time of day, though the server does each with its own commands. Now:

- chat (chat.read): each player's line as the server passes it on, the server's own lines to
  everyone, and a /command the server does not know itself, which goes to the plugins instead of
  "Unknown command" while one hears chat, so a game mode takes its own commands;
- session.world(): the server's world and the name of its map;
- travel.levels() and travel.load(): the server's maps, and a map change as the console's map does
  (checked, saved and logged);
- world.set_time(): the time of day for everyone, as tod does, when the server has its world
  layers and layer-sync is on.

The names custom chat commands may not take now include every server command: lock, unlock, locks,
throwdown-locks, words, bone-reach and poll-run were missing, so a custom command could hide them.
2026-10-23 18:39:28 -05:00
AyeZeeBB 823dfc27a2 Server: the READMEs no longer list the drones setting and command
sync_drones and "drones on|off" went with the drone's C++ when the drone became a plugin; the server has neither.
2026-10-23 17:37:58 -05:00
AyeZeeBB 60cd8395d9 Merge pull request #351 from moelrobi/plugins/server-cards-content-w2s
Plugins: server cards and polls, mods.content on dedicated servers, world_to_screen bounds

Conflicts in the plugin and server plugin tests, where #348's role tests and this PR's tests were
added in the same places: both kept.
2026-10-23 15:56:02 -05:00
zeex64 e34c4ab94f Merge pull request #348 from moelrobi/plugins/server-roles
Plugins: server plugins know each player's role
2026-10-23 15:50:02 -05:00
AyeZeeBB 8ae812a10c Multiplayer: a player using the controls counts as active on a dedicated server, so flying a drone is not away
The server took a player as away unless their skater moved or they chatted, talked or changed
objects, so a pilot of the FPV drone plugin, whose skater stands still, was removed by the away
limit. The game now notes when the player works the controls (keys, mouse, a gamepad's buttons,
sticks and triggers, a joystick a plugin reads) and tells the server every 20 s while they do,
with a new presence packet (protocol 54). The server counts it as activity, once in 10 s at most.
2026-10-23 14:29:05 -05:00
Robin MoellerandClaude Opus 5.5 d6cf244c26 Plugins: a dedicated server's plugins show players cards and ask them polls
A server plugin could only reach players through chat lines (say, tell).
A mode that asks players something (which track next, join the next
round) had to read chat commands, and could not show a card.

reskate.server.card(who, text, seconds) and reskate.server.poll(who,
question, answers, {seconds}) put a card or a poll on the screens of a
player, a list of players or "all", in the slot the roster already has
for the server's announcement and vote, so games need nothing new and
the protocol is unchanged. Each player's is their own and shows over the
server's until it ends, then the server's shows again. A poll has one id
for everyone it asks; a player's answer ("/2", as the game sends it)
closes it for them and comes back to the plugin that asked as a
poll_answer event; a poll that runs out, is replaced by a newer one for
that player, is closed with reskate.server.poll_close, or whose player
leaves comes back as poll_closed with why. The plugin counts the answers.

Both need the "server" permission and a server copy on a dedicated
server; a lobby's host has no roster slot for them. Answering a plugin's
poll is not a server vote, so it does not ask the player's role for
"votes". A plugin that stops takes its cards and polls with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYabE7FCPgj3SaWeF92EML
2026-10-23 15:51:15 +02:00
Robin MoellerandClaude Opus 5.5 0706730e79 Plugins: mods.content and read_content read the server's Mods on a dedicated server
A server plugin with mods.read could call reskate.mods.content, but the
server's game had no content of its own, so it got nil and "not
available". A mode that keeps its data in mods (a race reading start
and finish from a park mod) could only read it in the players' games.

The server now answers from every mod folder in its Mods, by name, as it
finds its maps there; it has no load order or mods.json. A hidden folder
(.reskate_plugin_data) and names a plugin could not ask for are left out,
and a mod's title is its manifest.json's "name".

The listing and reading moved from plugin_game_runtime.cpp into
plugin_content.cpp, which the game and the server share, so both keep
the same limits: the folders the manifest's "content" names, 1000 files,
four folders deep, 1 MB a file, nothing outside the mod's folder. The
game's side is unchanged apart from calling it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYabE7FCPgj3SaWeF92EML
2026-10-23 15:50:55 +02:00
Robin MoellerandClaude Opus 5.5 71e3460182 Plugins: server plugins know each player's role
A server plugin could not see the roles of a dedicated server (data/roles.json), so a plugin that
wanted only moderators to use some of its commands had to keep its own list of SteamIDs. In a
server copy with the server permission, a player table now has role: a read-only {name, rank,
staff}, the role the player has now (the one given them, or the server's default), read from the
server each time. staff is whether the role gives any of the server's commands. It is nil for a
player with no role, for the server itself and on a lobby's host. Each read counts as a server call.

dingosdk_server_plugin_tests reads a given role, the default role and a role given while the
player is on, and checks the table is read-only and a plugin without server is refused;
dingosdk_plugin_tests checks the API on a server copy, and that a player's copy and a plugin
without server cannot read it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hs7xxJDf6SDcQUaXnzmtL5
2026-10-23 10:43:34 +02:00
Robin MoellerandClaude Opus 5.5 5b10469bad Plugins: server plugins spawn and remove the server's park mods
A dedicated server's park mods could only be spawned from its console, an admin's chat or a vote,
so a server plugin could not bring out the park its game is played in. A new permission,
server.parks, gives a server copy reskate.server.parks(), which lists the park mods set up with
whether each fits the map running now and is spawned (its objects and minutes left), and
reskate.server.park_spawn(name, minutes?) and park_remove(name), which return ok or false and why.

They go through park-mod add and remove, so the park limits, the map check and the chat lines stay
the server's. Their "[parks]" lines are logged "by plugin <id>". A name is a park mod's own (1 to 16
of a-z 0-9 - _) and minutes are 1 to 1440, or none for until removed. Reading the park files is not
part of this: a plugin brings what it needs to know about a park in its own package.

dingosdk_server_plugin_tests spawns and removes a park mod through a plugin and checks what it is
refused; dingosdk_plugin_tests checks the API on a server copy, and that a player's copy and a
plugin without server.parks cannot use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hs7xxJDf6SDcQUaXnzmtL5
2026-10-22 20:43:01 +02:00
Robin MoellerandClaude Opus 5.5 fd5f95467f Plugins: server plugins know the map and hear it change
On a dedicated server reskate.level.current() was empty and level_loaded and level_unloading never
fired, so a server plugin could not tell which map ran or that it had changed. The server's
plugins are now told the map's level each tick, named as players' games name it (a destination's
level, after its "|"). When the server changes the map, they hear level_unloading for the old one
and level_loaded for the new one, as a game's plugins do; players load it after.

dingosdk_server_plugin_tests checks level.current() and both events across a map change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hs7xxJDf6SDcQUaXnzmtL5
2026-10-22 20:38:22 +02:00
Robin MoellerandClaude Opus 5.5 45d4c64c75 Plugins: a dedicated server's plugins see the skaters
reskate.session.skaters() returned an empty list on a dedicated server: its game for plugins did
not say where anyone was, though the host holds every player's latest pose. A server copy now gets
each player in the world with a pose: their id, the pose's root position, its heading (read as a
client reads it) and how long ago it arrived. A server plugin can now follow players itself, for
a race's checkpoints or a game of tag, instead of trusting what each player's copy reports.

Host::skater_poses() lists them for the plugins. dingosdk_server_plugin_tests drives the host with
simulated players sending poses and checks what a server plugin sees, and that a plugin without
session.skaters is still refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hs7xxJDf6SDcQUaXnzmtL5
2026-10-22 20:30:07 +02:00
AyeZeeBB 45346b122c Plugins: prop hunt APIs, and plugin commands in the console lookup
- reskate.entities.blueprints lists the game's object blueprints (meshes only if asked),
  and reskate.entities.bounds gives a blueprint's box, so a mod can pick a prop by list or at random
- Attached objects take upright, turn and ground (a floor found within reach below), the "root"
  joint, and a 6th options argument with solid; spawned props' bodies are found and ghosted
- reskate.skater.hide, reskate.world.pick, and skater.get gives right and switch
- reskate.console.command takes an optional 4th table of subcommands; the console lookup
  suggests plugin commands word by word, and plugin commands lists the subcommands
- objbody: a test command for finding which body setter takes a prop out of collision
2026-10-22 00:30:06 -05:00
zeex64 c745d605ae Merge pull request #310 from moelrobi/fix/poll-answer-repeat
Polls: answers no longer refused as repeats, and less chat noise
2026-10-21 18:45:38 -05:00
waguandClaude Opus 5.5 67e8e439f7 Server: lock ollies for a player
A lock tool like noclip and boosts. The player's game enforces it: the roster
carries guest_ollies (protocol 53), and the jump hook gives a locked ollie no
upward speed. Plugins get player.permissions.ollies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 09:53:31 -04:00
vtf 02d34012ae Plugins: server plugins ban, unban; player permissions; reskate.Enums 2026-10-19 20:09:30 -04:00
AyeZeeBB 2d3b77701d Plugins: drone, Board Break and Road Rash are plugin mods now; their C++ is gone
The three features are Lua plugins in contrib/plugins (fpv-drone, board-break,
road-rash), built on what this adds to the plugin API: flight input, gamepad and
joystick reads with input capture, a rolled camera, a package's own sounds and a
synthesised motor (plugin_sound, plugin_wave, motor_synth), the skater's physics
events (skater_events), items worn over the outfit (outfit_overlay, skater_items),
the board's bones (board_pose), skater animation (anim_core), plugin images,
throwdowns started for chosen players, and server copies of plugins on a lobby
host and the dedicated server (server_plugins).

Removed from core: Extension/RoadRash, Extension/BoardBreak, the drone's flight,
radio, follow, OSD, beacon, outfit and sound code, its prop models (native_prop),
the DRONE, BOARD BREAK and ROAD RASH menu pages, home tiles and native menu rows,
their console commands, the drone follow debug action, the drone marker packet
(kind 36 stays unused; the roster keeps its byte) and the server's drones setting.

Kept in core, since every board needs it whichever plugins run: the breakable
deck packs (breakable_deck: which deck, its label and wheel spacing, the
boarddeck command and the BREAKABLE DECK card on the Mods page), and board_pose
keeping a deck pack's stretches out of sight, the baseplates on the deck and the
wheels at the deck's spacing on every board.
2026-10-19 16:22:15 -05:00
Robin MoellerandClaude Opus 5.5 bd21a9d8ca Polls: chat no longer repeats the question and the result the card shows
A poll put its question and answers in chat when it started, and its result when it ended, while
the poll card already shows both. Chat now says neither; the result still goes to the log, and a
cancelled poll still says why in chat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:25:50 +02:00
Robin MoellerandClaude Opus 5.5 07cd31b8aa Votes: the same answer in the next poll is no longer turned away as a repeat
Chat's flood control refuses a line identical to the one before it within 15 s, which also
caught /2 in a poll right after /2 in the last one. An answer to the running vote or poll is now
exempt from that one rule, on the server and in the game; the pace still holds, and the vote
still counts one answer per player.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 02:02:56 +02:00
AyeZeeBB 1ba5c8ef28 Server: an admin's tool locks outlast leaving, and a throwdown-locks change mid-throwdown changes only the difference
A lock an admin sets is kept for the player's SteamID until unlocked or the server restarts, so
leaving and joining again no longer lifts it: they are locked again and told on joining. lock and
unlock take a player who is not here by SteamID64 (unlock also by the name they had), and locks
lists them as not here. A throwdown's own locks still end when the player leaves. Changing
throwdown-locks while a throwdown runs unlocks and locks just the tools that changed for its
players, saying the setting changed instead of that their throwdown is over.
2026-10-17 12:50:31 -05:00
zeex64 2f506016c3 Merge pull request #218 from moelrobi/server-tool-locks
Server: Per-player tool locks
2026-10-17 12:45:52 -05:00
zeex64 e7c6df0df6 Merge pull request #225 from Jean-LucTch/dev
Fix multiplayer custom map download and join timeout handling
2026-10-15 14:03:06 -05:00
zeex64 67d84d5bd7 Merge pull request #227 from neekgate/fpv-drone-pr
ReSkate FPV Drone
2026-10-15 13:20:11 -05:00
AyeZeeBB 6c45e9666d World layers: a server's catalog must be the supported build's, and a player is not put out over one that is not
2.0.8's dedicated server was released with a world-layers.json scanned from
a game Steam had updated: 263 layers, where every player's game has 261. A
server that shares its layers (world_layer_sync) sends one choice for each,
the player's game turned the message down as unreadable, and nobody could
join such a server ("Join code, sender identity, or multiplayer protocol
did not match", and "Disconnected." in the server's log).
- supported_build::world_layer_count (261): how many layers the supported
  build's levels have.
- Server: a world-layers.json of another count is not used; the log says
  why and layer sync stays off, where before every player was turned away.
- --export-world-layers refuses a game folder whose count differs, so a
  release cannot be built from an updated game again.
- Player's game: a layer list of another length is left unread and every
  layer stays as the player has it; the rest of the message is taken and
  they join.
2026-10-13 18:45:37 -05:00
AldoandClaude Opus 5.5 57d98307a7 Skater: FPV drone mode
An FPV drone flown from the free camera: real quad physics (thrust,
gravity, drag, Betaflight "Actual" rates), collisions with the world, and
a goggle-style display. Open it with `dronemenu` (or the drone window's
Start flying).

Flying
- Keyboard and mouse, gamepads, or USB RC radios (EdgeTX etc. in Joystick
  mode). "Set up controller" learns each stick and switch by asking for
  one at a time, reversed axes and springing gamepad throttles included.
  While a controller flies the drone, the game is kept off it, as
  freecam's controller mode does.
- Presets (Tinywhoop, Toothpick, 5 inch), arm/disarm, back to takeoff,
  and every setting live through `dronetune`, saved in the profile.
- Optional range: the picture breaks up and is lost with distance from
  the takeoff point; past it, failsafe cuts the motors.
- OSD, on-screen sticks, an analogue video look, synthesised motor sound.
- Goggles: the pilot's skater wears eyewear from the game's catalog while
  flying, and gets their own back on landing (Road Rash aware).
- Start flying hides the game UI, Stop flying brings it back.

Multiplayer
- New packet `drone` (36): where a player's drone is and which way it
  faces, 15 times a second, unreliable, relayed like effects. Markers
  glide between updates. Protocol version 52.
- FLYING DRONE above the name of a player who is flying.
- Players choose whether to share their drone and whether to see others'.
- Dedicated servers: `sync_drones` (players section) and `drones on|off`.
  Off: no drone packets are relayed, and players are told in the roster.

Changes to existing code
- `flyspeed` takes any speed from 0.6 to 1500, not only the seven listed
  (the drone's presets and top speed slider use speeds of their own).
- Overlay: begin/end_input_access, so the drone's controller read sees
  the device while the game is kept off it.
- Profile: local_surface_hit, the park editor's ray from one point to
  another, for the drone's collisions.

Tests: Skater/Test/drone_radio_tests (controller setup), and the drone
packet and roster rule in Multiplayer/Test/protocol_tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MArBL6UbYZPsp9rMnq2Ks5
2026-10-13 21:59:49 +01:00
AyeZeeBB 9b81c821e4 Kicks: a popup for the player who was put out, and vote kicks last 5 minutes
A player kicked or banned only found out why from the status line of the
multiplayer menu, if they opened it.
- What the host or server says as it puts a player out ("You were
  kicked...", "You are banned...", away too long, a mod or cheat it does
  not allow) is now shown in the middle of the screen with an OK button,
  under YOU WERE KICKED, BANNED or REMOVED. The same when they are refused
  on trying to join again early. A link that is simply lost shows nothing
  new.
- Answered with A or B on a controller, Enter or Esc, or a click on OK. The
  game's input is held while it is up, as for the vote for the next map, so
  the button that answers is not also a press in the game.
- Console: "popup test" shows it with a sample message, to try it without
  being kicked.
Server: a kick by a vote has a time of its own, votes.kick_minutes, 5 by
default (1-1440, or 0 for until the server restarts); it no longer goes by
players.kick_minutes, which is now an admin's kick only and is still until
the restart by default. Console: votes kick-minutes <minutes>|restart, and
"votes" shows it. A config without the key takes the 5 minutes.
2026-10-13 15:38:36 -05:00
Robin MoellerandClaude Opus 5.5 3bfabed705 Server: tools can be locked for single players, and for a throwdown's players
The server can take noclip (with teleporting), No Bail, the boosts and the
player's own physics tuning from one player while everyone else keeps what
the server allows: for fair throwdowns on a server that allows them for free
skating. Server only: a locked player gets a roster of their own with the
tool off (players with the same locks share one) and permissions without
"movement", so their role cannot give it back. Players without locks get
exactly the roster they got before.

- lock <player|SteamID64> <tool[,tool...]|all>, unlock <player|SteamID64>
  [<tools>|all], locks: from the console, or an admin in chat. In memory
  only; locks end when the player leaves. Logged as
  "[lock] <name> (<SteamID64>): nobail, boosts locked by <who>".
- players.throwdown_locks (default []): tools locked for the players of a
  throwdown from its start until they quit, it ends or they leave
  (console: throwdown-locks <tools>|off).
- Tests: Server/Test/server_lock_tests.cpp, and the key in the config tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PreNfxwh21xyPp8EA72cUN
2026-10-13 13:33:55 +02:00
copilot-swe-agent[bot]andJean-LucTch 8eddcab142 Coordinate custom-map downloads with the multiplayer deadline
Co-authored-by: Jean-LucTch <88255067+Jean-LucTch@users.noreply.github.com>
2026-10-13 11:11:27 +00:00
AyeZeeBB 28118efc27 Objects: other players' objects are ghosts by default, and their card is a visitor's
Objects other players place are spawned locally, so the game took every one
for the local player's own: all were solid, and their card offered the
owner's buttons and named the wrong player.
- Other players' objects are ghosts the skater passes through, as in the
  game's own sessions. Each player's row in the session menu has an
  "Objects: solid / ghost" switch, remembered by Steam ID; a dedicated
  server's own objects (its parks) are solid unless switched off.
  `mp objects-solid <SteamID64|server> on|off` does the same.
- The card's own Disable and Enable on one object stand over that player's
  setting until the setting is changed.
- The card on an object someone else placed no longer offers Edit or
  Delete: the game's "is this mine" and "may this be edited" checks answer
  for who really placed it. It offers Disable instead.
- The card names the owner of the object it is for, read from the card's
  own script, instead of the owner of the object nearest the skater.
- A dedicated server has a player card of its own, "Server", shown on what
  it placed.
Server: the Mods folder is kept when the configuration is reloaded. A park
mod was reported as not being in the Mods folder after a reload.
2026-10-13 03:37:45 -05:00
AyeZeeBB 0e542fc5a2 Server: kicks can be timed, by a setting and for one kick 2026-10-13 00:58:35 -05:00
AyeZeeBB 2e186b9839 Launcher: mod profiles, sharing them by code or file, and Thunderstore dependencies
The Mods page has a third tab, PROFILES. A profile is a saved list of which
mods are on, in what order and at what version; every mod stays installed
once, in the one Mods folder. Loading a profile rewrites mods.json, switches
off what it does not name, and downloads from Thunderstore what it names
that is missing. Profiles live in Mods/profiles.json.

A profile travels the way r2modman's do, so the two managers read each
other's: export.r2x inside a .r2z file, and a code on Thunderstore's
profile service. Config files in an r2modman profile are ignored.

Installing or updating a Thunderstore package now brings the packages it
depends on, needed ones first, leaving out any already installed at a new
enough version. A mod pack is a package that is nothing else.

A kick, by an admin or by a passed vote, used to keep the player out until
the server restarted. "kick_minutes" in the players section of the config
(1-1440; 0, the default, is still until the restart) and the console
command "kick-time <minutes>|restart" now set how long it lasts.

The player is told how long when they are kicked, and how many minutes are
left if they try to join early. A kick keeps the time it was given, so
changing the setting is for later kicks. Bans are unchanged.
2026-10-13 00:55:29 -05:00
AyeZeeBB 28231c0df2 Typing dots: none for commands and private messages, and no flood of stop notices
A line that starts with "/" is a command, a whisper or party chat, so typing
one no longer shows dots to the whole server. Dots already showing are cleared
when the line becomes one.

A "stopped typing" notice was relayed every time it arrived, while "typing"
was held to one per 200 ms, so a modified client could flood everyone through
the host. A lobby's host and a dedicated server now pass a stop on only when
that player's dots are up, which is at most once per accepted "typing".
2026-10-12 23:45:27 -05:00
wilu222 149da6f72e Show dots above a skater while they are typing a message 2026-10-12 19:50:45 -04:00
zeex64 c5d8456369 Merge pull request #194 from xThrasherrr/fix/headless-piped-output
fix(server): keep piped output when started headless on Windows
2026-10-12 15:33:53 -05:00
AyeZeeBB 1c5da074cc readme 2026-10-11 21:55:51 -05:00
AyeZeeBBandClaude Opus 5.5 d2b8e18097 2.0.7 hotfix: map updates, vote bar, ReSkate Admin tag, servers on the newest map version
Map updates
- A map the game already has, being replaced by the host's version,
  could load as soon as the new files were in Mods, while the mods
  were still being applied: it ended on "This map is not loading". The
  session now holds the load while a map is downloading, installing or
  applying, as it waits for one that is missing.
- A player whose version of a map is later than the host's keeps it and
  loads. Only an older one is replaced by the host's. Versions are
  compared number by number (1.10.0 is later than 1.9.2).

Dedicated servers
- maps.latest_versions (off by default): the server asks Thunderstore
  for the newest version of each of its custom maps, at start and every
  maps.latest_check_minutes (5-1440, default 60), and writes it into
  the mod's manifest.json. Nothing is downloaded: the server reads only
  a map's level list and version, and players, who are held to the
  server's version, fetch it as they join. The map being played waits
  until nobody is on it. A folder that is not a Thunderstore package is
  not touched, and the level list is left as it is.
- Console: maps update, maps latest on|off, maps latest every <minutes>.

Overlay
- Vote card: the pass mark is always in the middle of the bar. Green
  grows over the left half up to the yes votes needed and over the
  right half past them, whatever share the vote needs.
- The "Staff" tag is "ReSkate Admin", shimmering from #2500FF to
  #B2F9FF.

No protocol change. Tests cover the version comparison and the
manifest write; the Thunderstore lookup was checked against the site.
The map update flow, the vote bar and the tag were not seen in game,
and the server setting has not run on a live server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:19:49 -05:00
AyeZeeBB e344d585f4 Vote for the next map when a map's time is up
A dedicated server that rotates its maps can now ask players where to
go next instead of taking the pool's next map.

Server
- maps.rotation_vote: when the timer runs out, a vote between three
  maps drawn at random from the pool, "Random map" and "Stay here".
  The most votes wins, a tie is drawn, and with no votes it is the
  pool's next map. It ends early once everyone has voted.
- maps.rotation_vote_seconds (10-120, default 30) and
  maps.rotation_extend_minutes (0-240, default 15; 0 leaves "Stay
  here" out).
- maps.rotation_announce: a card when the map has 10, 5 and 1 minutes
  left.
- The vote's maps are drawn a minute ahead and sent to games
  (PacketKind::next_maps), so their pictures are ready when it opens.
- /vote extend: a built-in vote that gives the map in play
  rotation_extend_minutes more. On by default (votes.extend_map), and
  only runs while the maps are on a timer. "extend" is now a reserved
  vote name.
- Console: rotation vote on|off, rotation announce on|off,
  rotation stay <minutes>|off, rotation extend [minutes],
  votes extend ..., vote extend.

Game
- The vote opens as a popup in the middle of the screen: a tile per
  answer with its picture, name, share of the votes and count. It
  holds input as the map download card does, and works with a
  controller (D-pad, A, B), the keyboard and the mouse.
- Pictures: a mod map's icon from its folder, or from Thunderstore
  when it is not installed; the game's own maps show their loading
  screen picture, cut to its middle square. "Random map", "Stay here"
  and a map without a picture are drawn as shapes.
- Older servers and polls are unchanged: the vote is a poll, so it can
  still be answered with /1, /2... in chat.

Protocol 50: games and servers both need this build.
2026-10-11 17:21:27 -05:00
AyeZeeBB 8351654222 Votes by those who vote, map versions, maps reload, and a crash leaving a map
Votes: a vote is decided by the votes cast, not by everyone on the server. It
passes when yes is its percentage of them, at the end of its time, or sooner
once the players still to vote could not change it. A kick still needs two
yes. The card's bar is yes in green from the left and no in red from the
right, with a mark where yes has to reach. No protocol change.

Map versions: a map installed at another version than the host's is no longer
loaded as it is. The download card offers the host's version (MAP VERSION,
UPDATE), which replaces the installed one and is applied while the game runs.
For that the live merge keeps each mod's fingerprint with where its archives
were placed, and places a mod again when its files have changed since: before,
a mod replaced while the game ran was read where the old one lay. When the
host's version is gone from Thunderstore, or the old one's files are in use,
the player is told and leaves the session.

Dedicated server: "maps reload" reads the Mods folder again, so a map copied
in while the server runs can be switched to at once. The server window reads a
copy of the map list.

Map names: a custom map's level is dingolevel_reskate_<map>, and "reskate" was
left in its name ("/map reskate badlands"). The map's own name is shown and
typed now; the old spelling still finds it.

as a level unloads, the game takes each mesh variation out of its registry by the
entry's mesh and reads it unchecked, and this map's meshes unload before
their databases. A guard on that one function has the read of a mesh that is
gone go on with the next entry, and logs how many were passed over.
2026-10-11 16:12:57 -05:00
xThrasherrr f6b4ef2bcd fix(server): keep piped output when started headless on Windows
Reopening CONOUT$ closed the pipes or file a manager or service gave the
server, so nothing it wrote or was sent got through.
2026-10-11 14:27:29 -06:00
AyeZeeBB 314d31fe7b Update main.cpp 2026-10-11 14:09:29 -05:00
AyeZeeBB f7beec83c5 Server window: word filter toggle and a roles editor
Settings tab: a Chat Word Filter card with "Use Global Word Filter"
(use_global_word_filter), buttons that open data/words_filtered.txt and
data/words_blocked.txt, and one that has a running server read them again.

Moderation tab:
- ROLES: create, edit and delete roles (tag, tag colour, rank, default role,
  inheriting from it, permissions, commands). Saving checks the roles, writes
  data/roles.json and has a running server reload it. A role's name is fixed
  once made, since players are given roles by name, and a save keeps who has
  which role as the server has it then.
- PLAYER ROLES (was SERVER ADMINISTRATORS): giving a role picks any of the
  server's roles, not only admin.

The two word files are now made empty when they are missing, without the
explanation that was written into them.

Also: the README's Chat words section sat inside the Roles section, and the
server parks test did not link without server_words.cpp.
2026-10-11 13:39:01 -05:00
zeex64 0b28dbd7c9 Merge pull request #163 from moelrobi/server-park-mods
Dedicated server: park mods as server-owned objects
2026-10-11 13:20:30 -05:00
AyeZeeBB c27d6cb053 Word filter: fewer false flags, servers' own word lists; protocol 49
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.
2026-10-11 13:17:28 -05:00
Robin MöllerandClaude Opus 5.5 dbb5cf8caa Park mods: fit to roles
- park-mod is a command a role can list: the admin role's "*" gives it, the
  default moderator role does not (spawning parks and changing the saved
  setup are the owner's call); command() checks the caller's role as for
  every other command, and custom votes still run it as the console
- The client's "/" menu shows the park-mod rows by role like the other
  server commands there, by the row's first word (resolved in the rebased
  client commit)
- README-roles.txt lists park-mod; README.txt says who may use it
- Tests: the admin role has park-mod and the moderator and player roles do
  not; in game a moderator is refused and a role listing park-mod is not

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2G4APEkhHgVs59RDL3XWD
2026-10-11 17:52:49 +00:00
Robin MöllerandClaude Opus 5.5 f8b01dc3d5 Park mods: reload and set up parks while the server runs
- park-mod reload <name> [<name>...] reads a spawned park's file again and
  swaps its objects in place, keeping its timer; all or none, within
  park_limits, and players are told the park was updated
- park-mod config add <name> | <mod folder> | <map> [| <key>] and
  park-mod config remove <name> change maps.park_mods without a restart,
  validated like the startup config and saved like the map pool; removing a
  spawned park's entry removes the park too
- The client's admin "/" menu lists reload, config add and config remove
- README.txt and help cover the new commands; server_park_mods tests reload
  (in place, timer kept, refused when not spawned or broken) and config
  add/remove (saved, refusals, by an admin)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2G4APEkhHgVs59RDL3XWD
2026-10-11 17:46:45 +00:00
Robin MöllerandClaude Opus 5.5 c3751b9714 Park mods: name the Mods folder, not the server's path
- A missing mod folder reads "The mod folder "x" is not in the Mods folder."
  instead of printing where the server is installed; the reason can reach chat
  through an admin's command or a vote
- server_park_mods checks the exact message

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2G4APEkhHgVs59RDL3XWD
2026-10-11 17:46:32 +00:00
Robin MöllerandClaude Opus 5.5 0193e4fadc Server tests: park mods
- server_config: park_mods and park_limits are new settings with defaults,
  round-trip (folders with spaces), and refuse bad names, folders, maps, keys
  and limits; a missing folder is not a config error
- server_park_mods: reads the fixture parks (a shortened copy of a player's
  street park, objects to skip, a missing key, broken files) and drives the host
  over a fake transport: a player's game takes the park from the server's own
  SteamID through routed_source and ObjectState, several parks at once, the
  limits (all or none), expiry with its warning, admin and vote spawns, late
  joiners, clear, and removal on a map change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2G4APEkhHgVs59RDL3XWD
2026-10-11 17:46:05 +00:00
Robin MöllerandClaude Opus 5.5 6909fe3a2e Dedicated server: park mods as server-owned objects
- 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
2026-10-11 17:45:49 +00:00
AyeZeeBB 1f5fe75b55 Server: teleport command, and the server window builds with roles
eleport <players> <player|x y z>, from the console or for a role with
the command: one player (a name's start or a SteamID64) or up to 64
SteamID64s joined by commas, to a player or a place. Several stand in
tpall's ring round the target; one sent to a place lands on it. Those
not connected or not in the world yet are skipped and named, and each
use is logged. tpall and tphere share the ring code. The name is
reserved, and it is in help, the chat list, README.txt and
README-roles.txt.
The server window merged in #170 did not compile on main. Its admin
card listed the admins a config no longer has: it lists the players
given a role, and gives or takes the admin role with the role command.
The "autostart" setting it reads is added to the config (off unless
set). A second read of Steam's debug lines is removed: they already
come with the direct notes.
Co-Authored-By: Robin Möller <moelrobi@gmail.com>
2026-10-11 11:12:54 -05:00