Files
ReSkate/Engine/Game/Multiplayer/session_model.h
T
AyeZeeBB 76330e40bb Skater slot, menu and throwdown fixes; MY MODS rework; lobby parties
Skater slot, menu and throwdown fixes; MY MODS rework; lobby parties

Saved skaters (Extension/Customization, Extension/Skater)
- The selected slot, and slots before it, came up as the standard
  skater after a start or map change. The game asks for a slot's saved
  outfit once per level, and that happened before the cosmetics catalog
  could be read. The first selection now waits for the catalog (60 s at
  most), and later map changes check against the catalog already read.
- The retry from abdda77 is removed: the record it wrote to is a
  temporary on the game's stack, so it could never work.

Pause menu (Extension/UI/NativeMenu)
- ReSkate's entries stopped working after enough map changes: each
  level change took new slots in a fixed table of 1024 for the same
  buttons. A button now keeps its slot, and a full table hands out the
  slot that has gone longest unused.

Throwdowns (Extension/Throwdowns, Runtime/runtime_init.cpp)
- Multiplayer crashed when someone placed a throwdown after a map
  change. The event types are freed with the level but were cached for
  the process. They are now cleared when a level is left, checked
  before use, and looked for again in each level.

MY MODS (Launcher/gui_mods.cpp, gui.cpp)
- Search, a filter and an order; a switch per mod; a menu per row.
- Multi-select with tick boxes, Shift-click and Ctrl+A, and a bar to
  enable, disable, move, update or uninstall everything ticked.

Lobby parties (Extension/Multiplayer/Session)
- A lobby is no longer one party. Its host keeps parties the way a
  dedicated server does: invite, accept, join, leave, kick, promote,
  open and close, with "/p" party chat. The "Lobby party" switch is
  gone. Protocol version unchanged (38).

Mod merge (Engine/Vfs), follow-ups to e715b9b
- Files rewritten into the merged patch are tracked by position, so
  another mod's added assets no longer shift the lookup.
- Two mods adding different assets under one name is now noted, with
  whose copy the maps get.
- mod_merge_unshift_tests compiles with the launcher tests on.
2026-10-04 13:27:21 -05:00

170 lines
7.7 KiB
C++

#pragma once
#include "distance_settings.h"
#include "object_placement.h"
#include "session_limits.h"
#include "tick_settings.h"
#include "voice_settings.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
namespace dingosdk {
struct MultiplayerLobby {
std::uint64_t id{}, owner{};
std::string name, map, code;
bool password_required{};
int players = 1, capacity = multiplayer_player_limit;
// A dedicated server rather than a player's lobby: joined by its code.
bool dedicated{};
int ping = -1; // ms, when the server answered directly
};
struct MultiplayerPlayer {
std::uint64_t id{};
std::string name;
bool connected{}, visible{};
std::string native_status, cosmetic_status, audio_status, ui_status;
std::string route;
float pose_hz{};
unsigned pose_target_tps = 20;
float distance_m = -1;
std::uint64_t pose_age_ms{};
float native_pose_hz{}, native_board_hz{};
float native_animation_hz{}, native_animation_ms{}, pose_apply_ms{};
std::uint64_t native_animation_skipped{};
std::string playback;
unsigned prediction_ms{};
bool correcting{};
std::uint64_t epoch{};
// The player's party (0 = none), whether they lead it, and whether it is the local player's.
std::uint32_t party{};
bool party_leader{}, party_member{};
};
// A party invite waiting for an answer (dedicated servers).
struct MultiplayerPartyInvite {
std::uint64_t from{};
std::string name;
};
struct MultiplayerClientTiming {
float callback_hz{}, gap_max_ms{}, work_ms{}, work_max_ms{};
// Capture/setup, receive/routing, encode/send, remote playback, diagnostics.
std::array<float, 5> mean_ms{}, peak_ms{};
};
// The host settings last used, saved in the local profile, so the host pages
// open with them. The password itself is never saved.
struct MultiplayerHostPreferences {
bool loaded{}, public_lobby{true}, password_required{};
int capacity = multiplayer_lobby_player_limit;
unsigned tps = multiplayer_default_tps;
std::string lobby_name;
};
// Session text chat. A message is UTF-8 without control characters, at most
// this many bytes; the log keeps the most recent lines of the session.
inline constexpr std::size_t multiplayer_chat_max_bytes = 200;
inline constexpr std::size_t multiplayer_chat_history = 50;
struct MultiplayerChatLine {
std::uint64_t sequence{}; // local arrival order, increasing within the process
std::uint64_t sender{}; // Steam id; 0 for a notice from ReSkate itself
std::string name, text;
bool local{}; // sent by this player
// The sender's role, as their nametag shows it: its colour (IM_COL32 layout, 0 = none)
// and a tag shown in a box before the name ("Dev", "Admin", "Host", "Friend" or empty).
std::uint32_t color{};
std::string tag;
};
// A command typed into chat with a leading "/" (shown as the player types "/").
struct MultiplayerChatCommand {
std::string name; // "/vote map"
std::string usage; // "/vote map <map>"
std::string description;
std::string argument; // what Tab completes after it: "player", "map", "time" or ""
};
// What the chat overlay reads each frame: cheap to copy, unlike the full model.
struct MultiplayerChat {
bool available{}; // in a session that can carry chat
std::uint64_t latest{}; // sequence of the newest line, 0 when empty
std::vector<MultiplayerChatLine> lines;
std::vector<MultiplayerChatCommand> commands; // what "/" offers in this session
std::vector<std::string> players, maps; // what their arguments complete to
};
// A player this PC's lobbies never admit, kept in the local profile.
struct MultiplayerBan {
std::uint64_t id{}; // SteamID64
std::string name; // as they were known when banned; may be empty
std::int64_t added{}; // Unix time
};
struct MultiplayerModel {
VoiceModel voice;
std::vector<MultiplayerBan> bans;
unsigned tps = multiplayer_default_tps;
ObjectPlacement object_placement = ObjectPlacement::everyone;
// Host setting: whether guests may use noclip / No Bail (the host and server admins always may).
bool guest_noclip{true}, guest_no_bail{true}, guest_boosts{true};
// Host setting: guests skate with the host's physics tuning (on a dedicated server: the
// game's own). Guest: what that does here, while enforced.
bool enforce_tuning{true};
std::string tuning_status;
// Local display preference: the floating name label above each peer.
bool nametags{true};
// Nametag style: ReSkate's own (name, distance, role colours, dots) or the game's.
bool custom_nametags{true};
// Local: whether session text chat shows at all (and T opens it).
bool chat_visible{true};
// Local: bad words in chat names and messages show as **** (on by default).
bool chat_filter{true};
float voice_range = default_voice_range; // how far the host (or server) forwards proximity voice
// In a dedicated server's session: the server is the host but not a player.
// Admins it lists may change its settings, and its map through Levels.
bool dedicated{}, server_admin{};
// `bans` is the dedicated server's list (for its admins) rather than this PC's.
bool server_bans{};
unsigned server_ban_total{};
// For its admins: the levels (assets) the dedicated server can switch to.
std::vector<std::string> server_maps;
MultiplayerHostPreferences saved_host;
bool force_world_layers{};
std::uint64_t host_id{};
std::string local_name;
std::string lobby_name;
std::string object_status;
MultiplayerClientTiming client_timing;
MultiplayerDistances distances;
bool password_required{};
int players = 1, capacity = multiplayer_player_limit;
std::vector<MultiplayerPlayer> roster;
// The local player's party. Players form their own, in a lobby as on a dedicated server
// (`parties`: invite, leave, kick... are available in any session).
std::uint32_t party{};
bool party_leader{}, party_open{}, parties{};
std::vector<MultiplayerPartyInvite> party_invites; // newest last
bool active{}, hosting{}, connected{}, echo{}, remote_visible{}, local_ready{};
std::uint64_t local_id{}, peer_id{}, sent{}, received{}, dropped{}, pose_updates{}, board_pose_updates{};
std::size_t skater_bones{}, board_bones{};
std::string invite, map;
std::string status = "Multiplayer is off.";
std::string native_status;
std::string cosmetic_status;
std::string audio_status;
std::uint64_t audio_captured{}, audio_played{};
std::string peer_name, player_ui_status;
std::uint64_t player_map_updates{};
bool public_host{}, lobby_listed{}, lobby_searching{}, lobby_joining{}, lobby_searched{};
std::uint64_t public_lobby{}; // Verified public listing, never a private invitation.
std::string lobby_status, browser_status = "Refresh to find public ReSkate lobbies.";
std::vector<MultiplayerLobby> lobbies;
bool network_telemetry{};
unsigned prioritized_connections{};
std::uint64_t cosmetic_queue_us{};
unsigned direct_connections{}, fallback_streams{}, direct_upload_limit{};
int ping_ms{}, send_rate{}, pending_bytes{};
float outgoing_bps{}, incoming_bps{}, delivery_local = -1, delivery_remote = -1;
std::uint64_t queue_us{}, skipped_updates{}, send_failures{}, invalid_messages{}, sent_bytes{},
raw_sent_bytes{};
};
inline bool multiplayer_controls_level(const MultiplayerModel& model) noexcept {
// A server admin's level choice is sent to the server, which moves everyone.
return model.lobby_joining || (model.active && !model.hosting && !model.echo && !model.server_admin);
}
} // namespace dingosdk