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.
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.
- 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
- 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
- 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
- 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