Files
ReSkate/Extension/Multiplayer/Session/session.h
T
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

67 lines
4.0 KiB
C++

#pragma once
#include "Engine/Game/Multiplayer/session_model.h"
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
namespace dingosdk::multiplayer {
// Commands and tick execute on the game's client thread. model() is thread-safe.
std::string command(std::string_view action, std::string_view argument = {}, std::string_view password = {});
// A dedicated server's vote is running and this player may answer it (queue_command "vote" yes|no).
bool server_vote_open() noexcept;
// A dedicated server's poll is running and this player may answer it: how many answers it has
// (queue_command "vote" 1 to that many). 0: no poll.
unsigned server_poll_answers() noexcept;
// Private menu queue: passwords never pass through console history/logging.
bool queue_command(std::string_view action, std::string_view argument, std::string_view password);
// Called just before the host's validated native load is submitted, on the client thread.
void host_map_change(std::string_view destination);
enum class MapLoadResult { waiting, queued, failed, missing }; // missing: the map is not installed
// Runs on the client thread; only catalog-registered destinations may be queued.
using MapLoader = MapLoadResult (*)(std::string_view map, bool submitted, std::string &detail);
void tick(std::uintptr_t base, std::uintptr_t client, bool gameplay_ready, std::string_view map,
MapLoader loader = nullptr);
MultiplayerModel model();
std::string take_leave_notice(); // why the session just ended over a missing map, once; client thread
// The same map when the host said which Thunderstore package it is from, so it can be offered
// for download instead (Extension/Assets/map_download.h): the session does not end. It tells
// the host the map is being fetched, which is given much longer than a load, and goes on
// trying the map, so it loads as soon as it is installed. Once per map; client thread.
// The downloader ends the session (queue_command "stop") when the player says no or the map
// cannot be had. Should the session end meanwhile for another reason,
// queue_command("rejoin", "", "") goes back to it, with the password it was joined with.
struct MapNeed {
std::string package; // protocol.h: valid_map_package, never empty
std::string map; // its name for people
bool server{}; // a dedicated server's map, not a player's lobby
bool moved{}; // the session changed to this map with the player in it (else: joining)
// The version of it installed here, when that is another than the host's: the host's is
// fetched in its place. Empty: the map is not installed.
std::string have;
};
std::optional<MapNeed> take_map_need();
// Whether a session is waiting on a map being fetched right now. Thread-safe.
bool fetching_map() noexcept;
// Held while a map is being downloaded, installed or applied: the session does not load the
// host's map meanwhile, and waits as it does for one that is missing. A map that is being
// replaced by another version is one the game already knows, so without this it would load as
// soon as the new files are in place, before the mods are applied again. Thread-safe.
void hold_map_load(bool hold) noexcept;
// How a host learns the package of the map it is on, to tell its guests: the level's asset in,
// the package (or nothing) out. Called on the client thread, often; it should be quick.
using MapPackageLookup = std::string (*)(std::string_view asset);
// The maps a dedicated server's vote for the next map will offer, as it said about a minute
// before the vote (protocol.h: next_maps): each one's name in the vote and its Thunderstore
// package (may be empty). None outside such a server, and after the map changed. Thread-safe;
// the serial changes whenever the list does.
struct NextMapWanted {
std::string name, package;
};
std::vector<NextMapWanted> next_maps();
unsigned next_maps_serial() noexcept;
void set_map_package_lookup(MapPackageLookup lookup) noexcept;
// The session's chat lines for the overlay. Thread-safe and cheap: callable every frame.
MultiplayerChat chat();
} // namespace dingosdk::multiplayer