Commit Graph
680 Commits
Author SHA1 Message Date
mauwpixandClaude Opus 5.5 61092a4950 Customization: outfit.worn lists a full-body costume instead of filtering it out as a body part
reskate.outfit.worn() is meant to list full-body costumes: it has a "Costume" slot for them, between
the head and the feet. But no costume ever reached it. With a costume on, the list came back without
it, so a plugin that goes with a costume mod could not tell whether its costume was worn. Now it is
listed like any other item: {slot = "Costume", item = "Own_Hobkin", title = "Hobkin"}.

Why: the list leaves out the body's own parts (head shape, nose, skin...) by their catalog categories,
which start with "cust" (custhead, custnoses, custbodies...) or name the body. A full-body costume's
category is "cust_fullBodyCostume", so it matched both and was dropped before its slot was looked up.
A category that names a costume is now kept; everything else is decided as before.

The rule (category to slot) moves out of worn_items.cpp into outfit_kind.h as it was, so it can be
tested without the game: outfit_kind_tests (with the plugin tests) checks clothes by their slots, the
costume, the body's own parts, and an unknown category.

Checked in the game: with a costume mod's costume on, outfit.worn() returns
Costume | Own_Hobkin | Hobkin, where it returned nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 03:13:19 +01:00
AyeZeeBB 7fcb7ea798 Live mods: research notes on applying mods without a level reload
What the live apply already does without one, how the engine loads and holds bundles (the resource
manager's load operations, the level's compartments), why cosmetics need the reload (the root
level's cas_main_sharedbundle), and a plan for new items: a child BundleRefTable, a delta bundle
and the cosmetics manager. With experiments to run in game and the open questions. On hold for now.
2026-10-24 19:56:18 -05:00
AyeZeeBB e47c91d13a Multiplayer: applying mods in a session reloads your own map and keeps you in it
The reload after a live apply was a console "load", which in a session either ended it (a guest's
map going away is "your local map changed"), was refused (a guest without map rights), or moved
everyone (a server admin's load changes the server's map; a lobby host's reload sent every guest
through it too).

Now client_tick tells the session first (multiplayer::begin_local_reload) and loads the same map
locally with no multiplayer rule (queue_local_load). The session keeps the reload as travelling in
the same world: a guest keeps telling the host or server it is loading (world_ready, so it does not
time out) and that it is back; a host says it is not in the world for that long, so its guests wait
instead of moving, and nothing is a host map change. A reload refused by the loader is taken back
(cancel_local_reload); one that never starts ends after 20 s; in no state to reload (changing maps,
joining) the mods show at the next level load. The dedicated server already handles a player
reloading on their own.
2026-10-24 19:56:09 -05:00
AyeZeeBB 725c18ef3d Mod Manager: a pause menu tab after Mod Options to browse, install, update and switch mods in game
A third ReSkate tab in the game's pause menu (native_menu.cpp slot 2, the Store's tab art), drawn by
the overlay as the other two are (mod_manager_page.inl, included into hub_page.cpp):

- BROWSE: search, categories in a panel of their own (shown only, hidden or either, kept in the
  profile), sort, a featured carousel and rows (most popular, recently updated, new, the biggest
  categories), a grid for results, and a mod's own page with its README, what it brings with it,
  and INSTALL, switch on or off and uninstall. Works from a controller: the carousel's card is
  one stop that left and right turn, and B goes back from a mod's page (the page's Back is held
  meanwhile).
- MY MODS: the Mods folder with the states the ReSkate menu shows; UPDATES: Update all.
- The foot of the page shows the install's progress and the apply's, and Apply (View on a pad).
  Leaving the menu with changes not applied asks first (a two-answer card in message_card.cpp,
  answered by the runtime as the message is); LATER closes the menu.

Behind it, Extension/Assets/mod_store: Thunderstore's listing (cached on disk), the installed mods,
and a queue that downloads and installs packages with their dependencies, updates and uninstalls
them. An update or uninstall of a mod the game holds open waits in Mods/.reskate-pending and the
launcher makes it before the next start (launcher_mods::apply_pending). Package icons load into
their own SRV slots (store_icons.cpp), sharing the launcher's icon cache.

Tests: mod_store_view (search, categories, the front page's rows, install plans), the pending
updates and uninstalls in mod_manager_tests, and the third tab in native_menu_view_tests.
2026-10-24 19:55:56 -05:00
AyeZeeBB 54716f2798 Mod profiles: with other mods kept on, a profile is in use whatever the player's own mods are
With "Keep my other mods on when loading a profile", in_use compared every enabled mod in order, so
a player's own mod placed above the profile's mods made the profile read NO PROFILE IN USE although
all its mods were on, and loading it again moved that mod back down. Only the profile's own mods
count now: which are on, and their order among themselves.
2026-10-24 18:04:21 -05:00
AyeZeeBB 403702126c Merge branch 'main' of https://github.com/Dingo-Shenanigans/ReSkate 2026-10-24 17:59:50 -05:00
zeex64 f498cb33f8 Merge pull request #335 from NxRoot/profiles-keep-other-mods
Mod profiles: an option to keep other mods on when loading a profile
2026-10-24 17:59:30 -05:00
zeex64 86968515d3 Merge pull request #320 from shumsic/patch-1
[Bug Fix] Throwdowns: fall back to full region scan when QueryWorkingSetEx reports no resident pages (#319)
2026-10-24 17:54:55 -05:00
AyeZeeBB 7dabd99a40 Crashes: the Trainer no longer writes into freed memory, throwdown models go before a level unload, and a gone texture draws nothing instead of crashing
From the first 6k Sentry reports of 55a7342 (2026-10-24), whose top sites were all new since 2.1.1:

- Trainer: scale_underflip_bands wrote the underflip rate bands' MinScale and MaxScale every tick on
  every copy it had ever found, checking only that the address was readable. A level's copies go back
  to the game's heap with it, so 1.0 and 0.6 landed in the upper half of whatever the game made there
  next (Skate.exe+0x157f930, +0x12a1549, +0x10084de, +0x11ef49e, +0x3db81ea, +0x12c21ca: about a
  third of the crashes, every dump with a pointer whose high dword is one of those floats). A copy is
  now written only while it still is that band and holds the game's band or the last one written;
  any other is forgotten.
- Throwdowns: models the page released were destroyed 3 s later by the menu tick, which does not run
  while a level loads, so the game's own flush of the unloaded level's types destroyed them after
  their assets were freed (Skate.exe+0x3dc5eaa, 18%). Before a level unload they are destroyed at
  once, as the native menu's own are.
- Textures: the guard's "default for the id" is the entry itself, so for a dead entry resolve gave a
  null texture, and the texture view cache asked it for its key (Skate.exe+0x3e6ddc1, 17%, every
  report with TheVlixOne-Nike_Pack). A stub at the cache insert hands back the empty view the cache
  already built when the texture is null; the guard is on only when that patch applies.
- Physics: the wake guard widens every pass because ReSkate raises the owner pool to 160000; its log
  line now says so once instead of claiming a prevented crash thousands of times a session.

Built; not yet checked in the game.
2026-10-24 17:19:32 -05:00
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
AyeZeeBB e2d12a701d Launcher: plugin mods show as Plugin instead of Nothing, and UPDATE sits after the version it brings
The mod list now notices a reskate-plugin.json that names an entry, reskate-objects.json and
reskate-music.json, and mods::mod_contents lists what a mod holds, most telling first. The
launcher's Type column shows the first of them (with "+" when there are more, all of them on
hover), and the in-game Mods page joins them. A reskate-plugin.json with only variants or
resident assets is not a plugin.

The UPDATE button moved from its own spot before the Type column to right after the blue
old -> new version, cutting a long name short rather than pushing into Type.
2026-10-24 11:24:20 -05:00
AyeZeeBB b739861fdf Docs: macOS and music source setup move to the docs site instead of files in the repo
MACOS-SETUP.md and MUSIC-SETUP.md are now docs.reskate.dev/macos and
docs.reskate.dev/music-sources; the README links them.
2026-10-24 11:17:36 -05:00
zeex64 cd45a77843 Merge pull request #353 from moelrobi/plugins/server-visibility
Plugins: server plugins hide a player from others, or mute them
2026-10-24 11:10:21 -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 7fa407ac36 Launcher: GET MODS installs plugin mods and mod packs instead of refusing them
A Thunderstore install was refused unless the package held layout.toc, reskate-levels.json or a
parks folder, so a package with only a plugin, or a pack whose manifest only lists dependencies,
could not be installed from GET MODS. reskate-plugin.json now counts as content (and finds the mod
inside a package like the other markers), and a manifest with dependencies is a mod pack, as
InstallOptions::require_content always said.
2026-10-23 16:35:42 -05:00
AyeZeeBB b978853bab remove old drone model 2026-10-23 16:07:44 -05:00
AyeZeeBB 1b613b0dce Merge pull request #347 from snubroot/feat/mod-object-families
Objects: mods name their Quick Drop objects and group them into families with reskate-objects.json
2026-10-23 16:00:06 -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 85f6874090 Merge pull request #349 from ryanc2112/docs/troubleshooting-and-bug-template
Docs: a Troubleshooting section in the README, and a bug report template
2026-10-23 15:52:06 -05:00
zeex64 579bc87129 Merge pull request #350 from moelrobi/build/plugin-tests-linux
Build: the plugin tests build and run on Linux
2026-10-23 15:50:29 -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 2ae6c59d3f Console: the menu command is back, so a key or combo bound to the ReSkate menu opens it again
"bind reskatemenu" runs the menu command, which went with the drone's commands when the drone
became a plugin, so every press said "Unknown command" and a player without an Insert key had
no way into the menu. At startup the log now warns about a bind whose command is not registered.
2026-10-23 14:35:41 -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 f763792732 Plugins: world_to_screen gives nil for points the drawing functions would refuse
A point just in front of the camera and off to the side projects to
millions of pixels. world_to_screen handed that back, and drawing it
(d:poly, d:line, ...) raised "must be a finite position", so a client
plugin that drew a world marker near the camera was switched off after
five errors. world_to_screen now returns nil when x, y or the depth is
not finite or beyond 1e6, the same bound the drawing functions use, as
it already did for a point behind the camera.

The draw test now also checks that its handler ran to the end; before,
a failing assert inside it went unnoticed.

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 4500184b48 Build: the plugin tests build and run on Linux
dingosdk_plugin_tests covers the plugin sandbox and every reskate.* API,
but it only built on Windows, so contributors without Windows could not
run it. Now it builds wherever DINGOSDK_BUILD_PLUGIN_TESTS is on. On
Linux the test target adds plugin_discovery.cpp and mod_list.cpp itself,
because the Linux plugins library leaves out finding plugins among the
mods. The other tests in that group stay Windows only.

mod_list.cpp replaced mods.json and the exclusion list with MoveFileExW.
That code is unchanged under _WIN32. Elsewhere it uses
std::filesystem::rename, which replaces the file in one step on POSIX,
and the error message comes from the error_code.

Ran on Linux (Debian trixie, linux-x64 preset with the plugin and
multiplayer tests on): dingosdk_plugin_tests, dingosdk_server and
dingosdk_server_plugin_tests build, and ctest passes plugins ("plugin
tests passed (15 skipped)", the gitignored contrib/plugins) and
server_plugins. The Windows build was not run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hs7xxJDf6SDcQUaXnzmtL5
2026-10-23 14:19:25 +02:00
Robin MoellerandClaude Opus 5.5 325af71214 Plugins: the role test ticks once before asking, so the session's players are there
The FakeGame test for player.role ran its console command straight after apply(), before the
host had taken the session's players (Host::follow_session runs from tick), so the server copy
indexed a nil player. It ticks each host once first, as plugins_server_copies_are_the_authority
does; the player's copy and the plugin without server are then refused for the reasons they test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hs7xxJDf6SDcQUaXnzmtL5
2026-10-23 14:07:40 +02:00
Ryan CampbellandClaude Opus 5.5 9f8996bb69 Docs: a Troubleshooting section in the README, and a bug report template
The README answers antivirus false alarms, the Steam folder refusal, the
menu not opening and crashes, and the template asks for the version,
system, how the game was started and whether mods were on (#133).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 11:10:12 +01: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
snubroot e8031fe8e3 Objects: mods name their Quick Drop objects and group them into families with reskate-objects.json
A mod's objects each got a tile of their own, titled with the raw key, so sizes or colours of one
object could not share a tile and X / Square had nothing to pick from. A reskate-objects.json in the
mod folder now gives them titles, families and categories, read at startup into a mod_objects map
kept apart from the game's own items. It only adds: taken ids and the game's items are left out.
2026-10-23 02:37:51 -04:00
AyeZeeBB e35e33d66d Plugins: board.parameters reads the board's shader values, and set_parameter holds its colours and numbers
Each part of the local board (deck, grip, trucks, wheels) has a material instance with a few
shader values of its own, found in the running game. reskate.board.parameters lists them by key
and type. set_parameter holds a colour or a number there each tick, published until it shows, and
the board's own value comes back when the plugin lets go or stops. Integers and switches pick how
the board is drawn, the store's foil looks among them, so plugins only read those. Only your own
screen changes.
2026-10-22 20:17:09 -05:00
AyeZeeBB 1671403500 Plugins: board.motion gives the board's spin, and where the board and its deck are in the world 2026-10-22 19:50:04 -05:00
AyeZeeBB 35824f4d1c Launcher: categories shown only or hidden in GET MODS and MY MODS, and the game's shader cache emptied when the mods change
- The category menu on GET MODS, and a new one on MY MODS, take each category as shown only, hidden or
  either (a click goes round them), with how many mods are in each; each page's choice is kept in the
  launcher's settings. On MY MODS a mod Thunderstore does not list, or lists in no category, is under
  "No category".
- Before a launch whose mods give the game other data than the last launch's (the mods with a layout,
  their files and load order, and the variants in play), the game's own shader cache
  (%LOCALAPPDATA%\ReSkate\Game\Skate\cache) is emptied: shaders kept from earlier mods were found to
  break the picture after many changes. The shader_cache folder the game ships with is left alone.
2026-10-22 19:05:51 -05:00
AyeZeeBB 118c489fdb Plugins: what the Filmer needs as a plugin: carry, who sees a pose, a step in the camera cycle
PR 343's Filmer becomes a Luau plugin mod (kept in contrib/plugins/filmer) on general APIs instead of
its own C++:

- reskate.skater.carry: something held out in one or both hands and aimed, the body crouched (feet
  kept) and hunched over it, turned on the spot after it (Extension/Skater/carry.cpp, from the PR's
  filmer_hold.cpp, with tests)
- shown = "everyone" | "others" | "me" on skater.pose, reach and carry: the pose sent to the other
  players and the one drawn here are split when asked
- reskate.camera.cycle and the camera_cycle event: a plugin's camera as the last step of the camera
  height button's cycle, after first person
- reskate.session.nametags(false) holds the names over the other players off
- board.hide(bone, {others = true}) hides it in the board the others are sent too (a sent-pose filter)
- outfit.set takes hand wear slots; input.bind takes an action with no key of its own
2026-10-22 18:46:49 -05:00
zeex64 8ede361755 Merge pull request #341 from snubroot/fix/item-grid-4096-rows
Customization: item tabs over 1,024 items load instead of showing blank warning triangles
2026-10-22 17:11:08 -05:00
zeex64 e2c188cb75 Merge pull request #342 from moelrobi/plugins/server-parks
Plugins: what a dedicated server's plugins need to run a game mode
2026-10-22 17:02:17 -05:00
snubroot 8e79fd62f5 Customization: item tabs over 1,024 items load instead of showing blank warning triangles
The item browser reads grids up to 4,096 rows, but the native list writer it
publishes them through refused more than 1,024, so a bigger tab failed with
"Native menu list is invalid." every pass. The writer now takes up to 4,096.
2026-10-22 15:02:01 -04: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
nxrootandClaude Opus 5.5 756104c1e8 Mod profiles: space the keep-other-mods checkbox's label like the settings page
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 13:52:06 +01:00
nxrootandClaude Opus 5.5 076c693e64 Mod profiles: an option to keep other mods on when loading a profile
Loading a profile switches every installed mod it does not list off. With
"Keep my other mods on when loading a profile" (saved in profiles.json),
a profile switches its own mods on and leaves the rest as they are, so a
shared profile (a pack of mods) can be added to a player's own set. A
profile then counts as in use when its own mods match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 12:27:58 +01: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
AyeZeeBB c35e9a357b Docs: AGENTS.md for coding agents, and the README says the game build is pinned
AGENTS.md gives agents the build and test commands and the project's rules: one game build for
good, addresses only in Engine/Game/Build/20260929 with byte checks, hooks through Engine/Core/Hooks,
general SDK APIs with modes as plugins, sandboxed plugin permissions, nothing that unlocks paid
items, no EA code in the repository. CLAUDE.md imports it.

README's "Supporting a new game build" is now "The supported game build": ReSkate stays on 20260929.
STRUCTURE.md and README no longer point at contrib/plugins, which is kept out of the repository.
2026-10-21 23:19:40 -05:00
AyeZeeBB 721ed9e64d Plugins: a general launch API in place of grind_exit; grind exits is all Lua, and 701 is a grind
The SDK knew grind exits: reskate.skater.grind_exit took a toe/heel/left/right wish, and C++ worked
out the rail's direction, toe and heel from the stance and facing, the 1.8 m/s and the strength.
That logic is now the grind-exits mod's own Lua (plugin/exits.lua, kept on this machine). What has
to run on the physics thread is general:
- Extension/Skater/launches: every launch of the local skater (ollie, grind, no_comply, boneless),
  classified by its call site and the state before it, with the board's direction of travel; the
  `launch` event for plugins with events.skater; reskate.skater.shape_launch(from, {across, along,
  up}) sets those speeds against the travel, one plugin a kind.
- reskate.skater.get() gains `right` (the root's right axis) and `switch` (the game's stance flag).
- Extension/Skater/pad_hide: reskate.input.hide's pad filter, out of the old grind exit file.
Removed: reskate.skater.grind_exit, the grind_exit event, GrindExitWish and GrindPop.

Physics state 701 is a grind too (the game checks it with 400 in about a dozen places; the
2026-10-11 measurement only reached the 400s): pops out of it were dropped, and skater_state called
it "other". Launches, skater_state ("grinding") and the trainer's grind pop all count it now.
Tests: the grind-exits plugin test drives the new path (11 checks of its own).
2026-10-21 21:41:04 -05:00
AyeZeeBB 97793b79fc Build: the Linux server tests link the server plugins, every core compiles, no vcpkg hook
The release script's Linux step failed: dingosdk_server_park_tests and dingosdk_server_lock_tests
build server_host.cpp and server_commands.cpp, which call ServerPlugins since the server Lua API,
without server_plugins.cpp or dingosdk_plugins. Both have them now; the 18 Linux tests pass.
MSVC compiles with /MP instead of /MP4, so a project uses every core rather than four (the
release build's clean ReSkate.dll was the slow part).
A user-wide "vcpkg integrate install" ran its DLL copy after every link with PowerShell 7
("'pwsh.exe' is not recognized"); ReSkate uses no vcpkg packages, and the projects now leave it
out (VCPkgLocalAppDataDisabled).
v2.1.2
2026-10-21 20:59:42 -05:00
AyeZeeBB 77542e4db9 Throwdowns: a plugin refusing its card no longer blocks every later flag, and joins say what they did
A plugin that calls flag.leave() on `open` (Tag and Infected outside a session) sent the game an
exit from a setup that never opened. The game never took it, and every later flag's setup waited
behind it. A registration left before its setup was asked for now just ends, from flag.leave()
and from the waiting card's Leave alike (leave_unopened).
The join card says which flag it read and how it was branded, or which buttons it has when it has
no Enroll; its branding errors were swallowed. Joining another player's plugin flag says whether
its plugin heard join, or why the player was taken out of its queue. A navigation that waits on
the game's says so every 3 s, and a setup that does not open in 15 s says so.
2026-10-21 20:42:49 -05:00