mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 13:57:05 +02:00
Map downloader - A host or dedicated server now tells joiners which Thunderstore package its map comes from (the Mods folder name plus manifest.json). - A player without the map gets a card: icon, name, author, version and description, with DOWNLOAD and CANCEL. It works with mouse, keyboard and controller. - Only packages listed as Maps in the ReSkate community are offered, and never deprecated, rejected or NSFW ones. - On yes the package is downloaded, installed into Mods and applied as a live mod, then the session loads the map. A small card on the right shows the progress, so the player can keep skating. - The player stays connected while it downloads: the host gives a player who says they are fetching the map up to 45 minutes instead of the usual load time. - When the server changes map with the player in it, the card says so. - Protocol version 47: map package on map_offer and world_state, and a "fetching the map" flag on map_request and world_ready. Joining - The pause-menu server browser shows a CONNECTING card over the page while a join is under way, and a COULDN'T CONNECT card with the reason when it does not get in. - The session and voice tabs no longer show before the player is in. - Leaving without the map (declined or cancelled download) no longer counts towards the failed-join limit, on servers and player hosts. Input - A card that asks something holds the game's input until the buttons are released. - Steam's overlay hooks XInput in front of ReSkate and answers the game itself, so a controller reached the game behind menus. ReSkate now hooks that hook as well. Mod merging - Mods are read on several threads, at launch and for live merges. A live merge runs below the game's priority. - The paid-cosmetics check reads the game's files and the mods on several threads. - A live merge reuses the paid-cosmetics verdicts it already has and reports its progress and timings. Launcher - NSFW mods and their categories are not shown in the mod browser.
551 lines
30 KiB
C++
551 lines
30 KiB
C++
#pragma once
|
|
#include "Engine/Game/Multiplayer/chat_rate.h"
|
|
#include "session.h"
|
|
#include "Extension/Multiplayer/developer_identity.h"
|
|
#include "Extension/Multiplayer/word_lists.h"
|
|
#include "Engine/Core/Text/word_filter.h"
|
|
#include "Extension/Customization/developer_hoodie.h"
|
|
#include "Extension/Customization/developer_board.h"
|
|
#include "Extension/Multiplayer/Remote/native_skater.h"
|
|
#include "Extension/Multiplayer/Voice/voice_chat.h"
|
|
#include "Extension/Multiplayer/Steam/steam_transport.h"
|
|
#include "Extension/Throwdowns/throwdown_relay.h"
|
|
#include "Extension/Multiplayer/Steam/steam_lobbies.h"
|
|
#include "Extension/Multiplayer/Steam/steam_server_browser.h"
|
|
#include "room.h"
|
|
#include "party_book.h"
|
|
#include "Extension/Multiplayer/Net/delta_codec.h"
|
|
#include "Extension/Multiplayer/Net/pose_batch.h"
|
|
#include "Extension/Multiplayer/Net/sound_codec.h"
|
|
#include "password.h"
|
|
#include "Engine/Game/World/world_names.h"
|
|
#include "client_timing.h"
|
|
#include "monotonic_clock.h"
|
|
#include <atomic>
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <deque>
|
|
#include <map>
|
|
#include <unordered_map>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <span>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
// Session state shared by session.cpp and the session_*.cpp files.
|
|
namespace dingosdk::multiplayer::session_detail {
|
|
enum class Mode { off, host, join, echo };
|
|
// Flood control for throwdown messages (chat has ChatRate, Engine/Game/Multiplayer/chat_rate.h).
|
|
struct ChatBudget {
|
|
std::uint64_t since{};
|
|
unsigned messages{};
|
|
bool accept(std::uint64_t now, unsigned burst = 6) noexcept {
|
|
if (now < since || now - since >= 5000000) { since = now; messages = 0; }
|
|
return ++messages <= burst;
|
|
}
|
|
};
|
|
// Throwdown messages per sender per 5 s: offers, joins, starts and ~4 scores a second.
|
|
inline constexpr unsigned throwdown_burst = 60;
|
|
// The party the echo test shows its mirrored player in (a real lobby has none until its
|
|
// players form one).
|
|
inline constexpr std::uint32_t lobby_party = 1;
|
|
struct Peer {
|
|
// Guests: this owner's layout as relayed by the host. Host: the owner's own
|
|
// upload, which is never forwarded directly. The host publishes `shared`
|
|
// instead, under its own revisions, so it alone decides what others see.
|
|
ObjectState objects, shared;
|
|
std::uint64_t shared_from{};
|
|
// Host: object IDs removed by "delete all guest objects". Re-uploads of
|
|
// them stay hidden; newly placed objects get fresh IDs and sync normally.
|
|
std::set<std::uint64_t> cleared;
|
|
struct ObjectDelivery {
|
|
std::map<std::uint64_t, std::pair<std::uint64_t, std::uint64_t>> sent;
|
|
std::vector<ObjectChunk> chunks;
|
|
std::uint64_t source{}, epoch{};
|
|
std::size_t next{}, cursor{};
|
|
} object_delivery;
|
|
struct PendingCosmetics { Packet packet; std::uint64_t received{}; };
|
|
std::vector<PendingCosmetics> pending_cosmetics;
|
|
Member member;
|
|
DeltaSender sender;
|
|
DeltaReceiver receiver;
|
|
std::uint64_t password_challenge{};
|
|
bool handshaken{}, render_failed{}, visible{}, direct_ready{}, map_authorized{};
|
|
std::uint64_t last_map_offer{};
|
|
bool world_ready = true;
|
|
std::uint64_t travel_since{};
|
|
std::uint64_t fetching_since{}; // a guest's: when they first said they are fetching the map (0: never, this world)
|
|
std::uint32_t ready_sequence{};
|
|
std::uint64_t next_dial{}, last_direct_hello{}, last_direct_pose{}, route_reported{};
|
|
std::uint32_t route_sequence{};
|
|
std::vector<Member> direct_routes;
|
|
std::uint64_t pose_arrival{}, pose_count{}, rate_at{}, rate_count{};
|
|
float pose_hz{};
|
|
std::uint64_t native_rate_count{}, board_rate_count{};
|
|
float native_pose_hz{}, native_board_hz{};
|
|
NativeAnimationStats animation_rate_base;
|
|
float native_animation_hz{}, native_animation_ms{}, pose_apply_ms{};
|
|
std::uint32_t received_pose_interval = 50000;
|
|
std::optional<Transform> latest_root;
|
|
bool player_collision{}; // the player has the game's party collision on (from their poses)
|
|
// One entry per pose source relayed or sent to this recipient (at most max_players).
|
|
std::vector<PoseDelivery> pose_delivery;
|
|
std::uint64_t connected_at{}, last_packet{}, last_cosmetic_apply{};
|
|
// A skater removed for lack of poses is not spawned again before this: a spawn is a heavy
|
|
// native pass, and a player's poses may stop and start again and again.
|
|
std::uint64_t next_spawn{};
|
|
// Near enough to be shown as a skater; and whether a pose has placed them yet.
|
|
bool shown_wanted = true, placed{};
|
|
ReceiveBudget budget;
|
|
PoseBuffer poses;
|
|
Pose render_pose;
|
|
// Far from the local skater and the camera, render_pose is sampled every far_interval
|
|
// (0: every frame) and the skater keeps it in between.
|
|
std::uint64_t far_interval{}, next_far_sample{};
|
|
AppearanceBuffer appearance;
|
|
// Bumped per accepted outfit; render compares it with the one its actor wears
|
|
// (0 after a spawn) to spread native recipe applies over frames.
|
|
std::uint64_t cosmetic_revision{}, applied_cosmetics{};
|
|
DeveloperHoodieState developer_hoodie;
|
|
DeveloperBoardState developer_board;
|
|
AudioBuffer audio;
|
|
std::uint32_t voice_sequence{};
|
|
bool received_voice{};
|
|
VoiceBudget voice_budget;
|
|
OutfitBudget outfit_budget;
|
|
SoundBudget sound_budget;
|
|
EffectBudget effect_budget;
|
|
// Their skater's contacts with the world (effects.h), each with when to show it: as late
|
|
// as their poses are shown, so the sparks are where their skater is.
|
|
std::deque<std::pair<std::uint64_t, Impact>> impacts;
|
|
ChatRate chat_rate;
|
|
// Chat proofs (session_receive.cpp): the copies of their lines this player sent us over
|
|
// their own Steam connection, and the lines the host passed on as theirs that wait for one.
|
|
struct ChatProof {
|
|
std::uint32_t sequence{};
|
|
std::string text;
|
|
std::uint64_t at{};
|
|
};
|
|
std::vector<ChatProof> chat_proofs, chat_waiting;
|
|
ChatBudget throwdown_budget, party_budget;
|
|
// Host: how the guest's mods change trick scoring, as they reported it (Engine/Vfs/mod_scoring.h):
|
|
// nothing until the report arrives, 0 for the game's own. member.scoring is the verdict.
|
|
std::optional<std::uint64_t> scoring;
|
|
std::string scoring_mods;
|
|
ChatBudget scoring_budget;
|
|
std::optional<AudioState> presented_audio;
|
|
std::uint64_t next_audio_update{}, next_ui_update{};
|
|
bool ui_visible{};
|
|
std::vector<std::uint8_t> cosmetic_packet;
|
|
std::string native_status, cosmetic_status;
|
|
};
|
|
struct PrivateRequest {
|
|
std::string action, argument, password;
|
|
std::uint64_t queued{};
|
|
~PrivateRequest() { erase_password(password); }
|
|
};
|
|
struct Session {
|
|
VoiceChat voice;
|
|
VoiceSettings voice_settings;
|
|
VoicePolicy voice_policy;
|
|
unsigned tps = multiplayer_default_tps;
|
|
ObjectState local_objects;
|
|
std::uint64_t next_object_update{};
|
|
ClientTiming client_timing;
|
|
std::uint64_t next_publish{}, last_client_log{}, next_party_update{};
|
|
MultiplayerDistances distances;
|
|
ObjectPlacement object_placement = ObjectPlacement::everyone;
|
|
unsigned object_limit{}; // objects each player may have placed; 0: no limit
|
|
bool object_scaling{true}; // the dedicated server lets players resize what they place
|
|
bool sync_effects{true}; // the dedicated server has players see each other's skater effects
|
|
// What guests may use: the host's choice, or the host's roster for a guest.
|
|
bool guest_noclip = true, guest_no_bail = true, guest_boosts = true;
|
|
// Host: guests skate with its physics tuning. Guest: the host's roster says so (a
|
|
// dedicated server's guests skate with the game's own).
|
|
bool enforce_tuning = true;
|
|
// Host: players whose mods change trick scoring (itself included) are kept out of linked
|
|
// throwdowns and coop challenges. Guest: the report last sent to the host. Both: whether the
|
|
// roster flags the local player.
|
|
bool score_check = true;
|
|
std::optional<std::pair<std::uint64_t, std::string>> scoring_sent;
|
|
bool local_scoring{};
|
|
std::uint64_t next_scoring_check{};
|
|
// The local skater has the game's party collision on; sent with its poses.
|
|
bool local_player_collision{};
|
|
// Guest: the host's physics tuning differences, once it sent them.
|
|
std::optional<std::vector<std::uint8_t>> host_tuning;
|
|
// Host: the differences last sent, the packet that carried them (for players who join
|
|
// later) and when to look at its tuning again.
|
|
std::optional<std::vector<std::uint8_t>> sent_tuning;
|
|
std::vector<std::uint8_t> tuning_packet;
|
|
std::uint64_t next_tuning_check{};
|
|
// The physics the tuning does not carry (Engine/Game/Multiplayer/session_physics.h).
|
|
// Guest: the host's, once it sent them. Host: which of its own were last sent, the packet
|
|
// that carried them (for players who join later) and when to look again.
|
|
std::optional<std::vector<std::uint8_t>> host_extras;
|
|
std::uint64_t sent_extras{};
|
|
std::vector<std::uint8_t> extras_packet;
|
|
std::uint64_t next_extras_check{};
|
|
std::uint8_t server_votes{}; // guest of a dedicated server: the votes it runs
|
|
std::uint8_t server_polls{}; // ... who may start a poll there (ServerPolls)
|
|
std::vector<ServerCustomVote> server_custom_votes; // ... and its owner's own votes
|
|
// Host: bumped per "delete all guest objects". Guest: the last value seen
|
|
// (unset until the first roster) and whether a local wipe is outstanding.
|
|
std::optional<std::uint32_t> object_clears;
|
|
bool clear_pending{};
|
|
bool force_world_layers{};
|
|
// Players the host kicked. They cannot reconnect until the session ends.
|
|
std::set<std::uint64_t> banned;
|
|
// Warnings each guest of this lobby has had for words that are not allowed at all (word_lists.h).
|
|
std::map<std::uint64_t, unsigned> word_warnings;
|
|
// Host: Steam IDs whose attempts to join keep failing wait longer each time (room.h).
|
|
JoinBackoff join_backoff;
|
|
// Players banned for good (every session this PC hosts), from the local profile.
|
|
std::vector<MultiplayerBan> bans;
|
|
bool bans_loaded{};
|
|
// Sorted IDs of `bans` for the per-frame connection check; rebuilt when marked dirty.
|
|
std::vector<std::uint64_t> ban_ids;
|
|
bool ban_ids_dirty = true;
|
|
WorldLayerChoices layers{default_world_layers()};
|
|
ParkChoices parks;
|
|
std::uint64_t next_park_update{};
|
|
std::optional<PasswordKey> password;
|
|
std::string lobby_password, lobby_name;
|
|
std::mutex request_mutex;
|
|
std::deque<std::unique_ptr<PrivateRequest>> requests;
|
|
SteamTransport transport;
|
|
SteamLobbies lobbies{make_steam_lobby_api(), [](std::uint64_t host) { return banned_host(host) || reskate_banned(host); }};
|
|
SteamServerBrowser servers;
|
|
// Guest of a dedicated server: whether the roster lists us as an admin,
|
|
// and the server's voice range from it.
|
|
bool server_admin{};
|
|
float roster_voice_range = default_voice_range;
|
|
// The colours of the dedicated server's own chat lines, as its roster gives them.
|
|
std::uint32_t server_chat_badge = default_server_chat_badge, server_chat_text = default_server_chat_text;
|
|
// The dedicated server's vote as its roster last gave it, when it ends by this game's clock,
|
|
// and what this player answered in it (0 nothing yet, 1 yes, 2 no; in a poll 1 + the answer).
|
|
ServerVote vote;
|
|
std::uint64_t vote_ends{};
|
|
std::uint8_t vote_mine{};
|
|
// The dedicated server's announcement as its roster last gave it, and when it goes by this game's clock.
|
|
ServerAnnouncement announcement;
|
|
std::uint64_t announcement_ends{};
|
|
// The server's ban list, as sent to us while we are one of its admins.
|
|
std::vector<MultiplayerBan> server_bans;
|
|
std::uint32_t server_ban_total{};
|
|
std::vector<std::string> server_maps; // level assets the dedicated server allows
|
|
std::vector<std::string> server_map_pool; // admins: the pool's assets in rotation order (empty: every map)
|
|
unsigned server_map_rotation{}; // minutes per map (0: off)
|
|
std::string map_label; // the host's name for join_destination (may be empty)
|
|
std::string map_package; // the Thunderstore package it is from (may be empty)
|
|
// The map is not installed and is being fetched (take_map_need): since when, 0 for not.
|
|
// The host is told with what is sent while waiting (Packet::map_fetching).
|
|
std::uint64_t fetching_since{};
|
|
std::optional<MapNeed> map_need; // take_map_need()
|
|
// The session a missing map ended, for "rejoin": who hosted it and the password key it was joined with.
|
|
struct Rejoin {
|
|
Invite invite;
|
|
std::optional<PasswordKey> password;
|
|
};
|
|
std::optional<Rejoin> rejoin;
|
|
std::string leave_notice; // take_leave_notice()
|
|
std::uint64_t joined_public_lobby{};
|
|
std::array<Peer, max_remote_players> peers;
|
|
// Players take the lowest free slots, so every one sits below this mark
|
|
// (note_slot raises it, trim_slots lowers it). Loops stop here, not at max_players.
|
|
std::size_t used_slots{};
|
|
bool public_host{}, gameplay_ready{}, started_map{}, roster_dirty{};
|
|
bool awaiting_map{}, join_map_authorized{}, map_load_submitted{};
|
|
std::uint64_t join_started{}, last_map_request{}, last_map_load_check{};
|
|
std::string join_destination;
|
|
std::uint64_t world = 1, travel_started{}, last_world_state{}, last_world_ready{};
|
|
std::uint32_t world_state_sequence{};
|
|
bool travelling{}, host_world_ready = true;
|
|
unsigned capacity = max_players;
|
|
// Local display preferences, loaded once from the profile. Never sent to peers.
|
|
bool nametags = true, chat_visible = true, display_preferences_loaded{};
|
|
float nametag_distance = 120.f; // names within this many metres, dots past it
|
|
// Only players within this many metres have a skater (session.cpp); checked a few times a second.
|
|
float player_distance = 120.f;
|
|
bool prefer_direct = true; // straight to servers that offer it, not through the relays
|
|
// Poses from a dedicated server (pose_batch.h): each player's stream as this game holds it,
|
|
// and which of the server's messages were read in full, which it is told once a tick.
|
|
std::unordered_map<std::uint16_t, pose_batch::Stream> pose_streams;
|
|
pose_batch::Ack pose_ack;
|
|
bool pose_ack_due{};
|
|
// This game's own poses go to a dedicated server the same way: what was sent and what the
|
|
// server acked, and the last poses sent, to build the next on.
|
|
pose_batch::Sender pose_upload;
|
|
std::deque<pose_batch::Stream::Held> own_poses;
|
|
// Skaters' sound to and from a dedicated server (sound_codec.h).
|
|
std::unordered_map<std::uint16_t, sound_codec::In> sound_streams;
|
|
sound_codec::Sender sound_upload;
|
|
// pose-dump: this player's own poses, as encoded for sending, written to a file for a
|
|
// while. Real poses to measure encodings against (tools/research).
|
|
std::ofstream pose_dump;
|
|
std::uint64_t pose_dump_until{};
|
|
std::size_t pose_dump_count{};
|
|
std::uint64_t next_shown_rank{};
|
|
bool nametag_dots = true, nametags_friends{};
|
|
bool chat_filter = true; // bad words in chat show as **** (Engine/Core/Text/word_filter.h)
|
|
// Chat bubbles above each skater's head (Hud/custom_nametags.h, nametag_overlay.cpp).
|
|
bool chat_bubbles = true, chat_bubbles_own{};
|
|
float chat_bubbles_distance = 40.f, chat_bubbles_duration = 5.f;
|
|
int chat_bubbles_history = 3;
|
|
bool game_menu{}; // a game menu is up or the game's UI is hidden: no chat on screen
|
|
bool hooks_prepared{}; // the remote-player hooks were installed (once per process)
|
|
// Host settings remembered between sessions and game restarts.
|
|
struct HostPreferences {
|
|
bool loaded{}, public_lobby{true}, password_required{}, world_layer_sync{};
|
|
unsigned capacity = max_players, tps = multiplayer_default_tps;
|
|
std::string lobby_name;
|
|
MultiplayerDistances distances;
|
|
ObjectPlacement placement = ObjectPlacement::everyone;
|
|
unsigned object_limit = default_object_limit;
|
|
float voice_range = default_voice_range;
|
|
bool guest_noclip = true, guest_no_bail = true, guest_boosts = true;
|
|
bool enforce_tuning = true;
|
|
bool score_check = true;
|
|
} host_preferences;
|
|
// Host: how far proximity voice is forwarded at all (listeners fade it by their own distance).
|
|
float voice_range = default_voice_range;
|
|
DirectUploadBudget direct_upload;
|
|
Mode mode = Mode::off;
|
|
std::uintptr_t base{}, context{}, parent{};
|
|
std::uint64_t secret{}, epoch{}, map{}, host_id{}, next_send{}, last_hello{}, last_roster{},
|
|
last_cosmetic_capture{}, last_routes{}, last_network_log{}, network_now{};
|
|
std::uint32_t sequence{}, roster_sequence{};
|
|
std::uint64_t local_pose_count{}, logged_local_pose_count{};
|
|
std::size_t last_skater_bones{}, last_board_bones{};
|
|
std::optional<Appearance> sent_appearance;
|
|
std::optional<Transform> local_root;
|
|
std::vector<std::uint8_t> cosmetic_packet;
|
|
std::string available_map, cosmetic_capture_status, map_name, invite, status = "Multiplayer is off.",
|
|
native_status;
|
|
MultiplayerModel view;
|
|
// Text chat: the log lives on the client thread; chat_view is its copy for
|
|
// the overlay, guarded by `mutex` like `view`.
|
|
std::deque<MultiplayerChatLine> chat;
|
|
std::uint64_t chat_sequence{};
|
|
ChatRate local_chat_rate;
|
|
MultiplayerChat chat_view;
|
|
std::optional<std::uint64_t> chat_signature; // of everything chat_view shows (publish_chat)
|
|
// Masked names and text of the lines in `chat`, by sequence, while the filter is on.
|
|
std::map<std::uint64_t, std::pair<std::string, std::string>> chat_masked;
|
|
// Steam friends as sorted IDs, from the social snapshot revision they were read from.
|
|
std::optional<std::uint64_t> friend_revision;
|
|
std::vector<std::uint64_t> friend_ids;
|
|
// What the throwdown relay reads each tick, kept between ticks: its player list is
|
|
// rebuilt when the admitted players change, and names are refreshed once a second.
|
|
ThrowdownRelayInput throwdown_input;
|
|
std::uint64_t throwdown_names_at{};
|
|
// The remote object owners (id, epoch, revision) and map last handed to the native
|
|
// runtime; unchanged ones skip the rebuild (session_send.cpp, sync_objects).
|
|
std::vector<std::tuple<std::uint64_t, std::uint64_t, std::uint64_t>> object_owners_sent;
|
|
std::string object_map_sent;
|
|
bool object_owners_valid{};
|
|
std::uint64_t object_owners_at{};
|
|
// Throwdown messages received since the last relay tick: sender, encoded message.
|
|
std::vector<std::pair<std::uint64_t, std::vector<std::uint8_t>>> throwdown_inbox;
|
|
// Parties (session_party.cpp): the local player's own roster entry, and the invites the
|
|
// host passed on (newest last). Whoever hosts owns the parties: a dedicated server, or in
|
|
// a lobby the host's game, in `parties`. Nobody is in one until they form it.
|
|
PartyBook parties;
|
|
std::uint64_t parties_revision{}; // the book's revision the last roster was built from
|
|
std::uint32_t local_party{};
|
|
bool local_party_leader{}, local_party_open{};
|
|
bool local_speeding{}; // the dedicated server flagged our game speed: no linked activities
|
|
struct PartyInvite { std::uint64_t from{}, received{}; };
|
|
std::vector<PartyInvite> party_invites;
|
|
std::uint64_t party_revision{}; // bumped when any party membership or invite changes
|
|
std::mutex mutex;
|
|
};
|
|
std::uint64_t nonce();
|
|
Session &session();
|
|
// The slots below used_slots: every one that may hold a player.
|
|
inline std::span<Peer> active_peers(Session &s) { return {s.peers.data(), s.used_slots}; }
|
|
inline std::span<const Peer> active_peers(const Session &s) { return {s.peers.data(), s.used_slots}; }
|
|
inline void note_slot(Session &s, std::size_t slot) { s.used_slots = std::max(s.used_slots, slot + 1); }
|
|
// Lowers used_slots past free trailing slots. Freed slots were reset by reset_peer.
|
|
inline void trim_slots(Session &s) {
|
|
while (s.used_slots && !s.peers[s.used_slots - 1].member.id) --s.used_slots;
|
|
}
|
|
// Runs fn(peer) for each slot below used_slots with that slot selected for the native adapters.
|
|
template <class F> void each_active_peer(Session &s, F &&fn) {
|
|
for (std::size_t slot = 0; slot < s.used_slots; ++slot) {
|
|
const PeerScope scope(slot);
|
|
fn(s.peers[slot]);
|
|
}
|
|
}
|
|
// A guest whose host is a dedicated server (a Steam game server, not a player).
|
|
inline bool dedicated_host(const Session &s) { return s.mode == Mode::join && game_server_steam_id(s.host_id); }
|
|
// Whether Steam itself vouches for this player's identity to this PC: a host's guests and a
|
|
// guest's host are connected directly, and so is another guest once the direct handshake is
|
|
// done. Anyone else is known only from the host's roster, which a host can fill as it likes,
|
|
// so what rests on who a player is (the developer and friend marks) waits for this. An
|
|
// official server is the exception: Steam vouches for it to this PC and for each player to
|
|
// it, and it is ours, so its roster is taken at its word. On a busy one a player is
|
|
// connected directly to only the few nearest, and the rest would show no tag.
|
|
inline bool steam_vouched(const Session &s, const Peer &peer) {
|
|
return s.mode != Mode::join || peer.member.id == s.host_id || peer.direct_ready || official_server(s.host_id);
|
|
}
|
|
// Whether a player shows the tag the backend gives them, and whether the items that come with
|
|
// it animate: not until their appearance has arrived, and not when it says they turned that
|
|
// off (Appearance::hide_tag, Appearance::hide_items).
|
|
inline bool shows_tag(const Peer &peer) {
|
|
const auto &look = peer.appearance.value();
|
|
return look && !look->hide_tag;
|
|
}
|
|
inline bool shows_items(const Peer &peer) {
|
|
const auto &look = peer.appearance.value();
|
|
return look && !look->hide_items;
|
|
}
|
|
// How a player has each of their marked cosmetics animate: what their appearance says.
|
|
inline const MarkStyles &mark_styles(const Peer &peer) {
|
|
static const MarkStyles standard{};
|
|
const auto &look = peer.appearance.value();
|
|
return look ? look->marks : standard;
|
|
}
|
|
Peer *find_peer(Session &s, std::uint64_t id);
|
|
unsigned player_count(const Session &s);
|
|
void reset_peer(Session &s, std::size_t slot);
|
|
void stop(Session &s, std::string reason);
|
|
void clear_world(Session &s, std::uint64_t now);
|
|
bool world_playing(const Session &s, const NativeFrame &local);
|
|
// session_view.cpp
|
|
void load_host_preferences(Session &s);
|
|
void save_host_preferences(const Session &s);
|
|
void publish(Session &s, const NativeFrame *local = nullptr);
|
|
void publish_chat(Session &s);
|
|
void load_bans(Session &s);
|
|
void save_bans(const Session &s);
|
|
bool is_banned(Session &s, std::uint64_t id);
|
|
// Re-reads friend_ids when the Steam social snapshot has changed.
|
|
void refresh_friends(Session &s);
|
|
// On a dedicated server games do not send each other anything: all of it goes through the
|
|
// server, which holds it to its rules. The one thing a server cannot do for a game is prove
|
|
// who another player is, which the marks rest on (steam_vouched). So two games still link,
|
|
// for that proof alone, when one of the two players has a mark the other would show: either is
|
|
// on one of the backend's lists, or they are Steam friends. Both games decide the same.
|
|
bool identity_link(Session &s, std::uint64_t other);
|
|
// `marks`: whether the line may show the badge the backend gives its sender (player_role).
|
|
void add_chat(Session &s, std::uint64_t sender, std::string name, std::string text, bool local = false, bool marks = true);
|
|
// The colour and badge of one of the backend's categories.
|
|
std::pair<std::uint32_t, std::string> mark_role(IdentityList list);
|
|
// A player's role colour and badge ("Dev", "Staff", "Content Creator", "Centrix", "Homie", "Admin", "Host", "Friend" or none), shown in chat
|
|
// and on their nametag. `local`: the local player.
|
|
std::pair<std::uint32_t, std::string> player_role(Session &s, std::uint64_t id, bool local, bool marks = true);
|
|
// Sends one line from this player; returns why not when it cannot.
|
|
std::string send_chat(Session &s, std::string_view typed);
|
|
// A "/" command for a dedicated server (votes, and any server command for its admins): sent
|
|
// like chat but never shown as a line; the server answers in chat.
|
|
std::string send_chat_command(Session &s, std::string_view typed);
|
|
// Answers the dedicated server's running vote, as /yes or /no in chat does.
|
|
std::string cast_server_vote(Session &s, bool yes);
|
|
// Answers the dedicated server's running poll (0: its first answer), as /1, /2... in chat does.
|
|
std::string answer_server_poll(Session &s, std::size_t answer);
|
|
// Whether a vote the local player may answer is running: read by the game thread for the binds.
|
|
inline std::atomic<bool> server_vote_open_flag{};
|
|
inline std::atomic<MapPackageLookup> map_package_lookup{};
|
|
inline std::atomic<bool> fetching_map_flag{};
|
|
// A guest without the map is downloading it: the clock they are loading against starts over
|
|
// each time they say so, for as long as map_fetch_limit_us from the first time.
|
|
inline void guest_fetching(Peer &peer, std::uint64_t now) {
|
|
// (Only for a guest who is past the password.)
|
|
if (!peer.handshaken && !peer.map_authorized) return;
|
|
if (!peer.fetching_since) peer.fetching_since = now;
|
|
if (now - peer.fetching_since > map_fetch_limit_us) return;
|
|
if (!peer.handshaken) peer.connected_at = now;
|
|
else if (peer.travel_since) peer.travel_since = now;
|
|
}
|
|
// The package of the map a host is on, for its map offers and world states.
|
|
inline std::string host_map_package(std::string_view destination) {
|
|
const auto lookup = map_package_lookup.load(std::memory_order_relaxed);
|
|
if (!lookup || destination.empty()) return {};
|
|
try {
|
|
auto package = lookup(world_destination_asset(destination));
|
|
return valid_map_package(package) ? package : std::string();
|
|
} catch (...) {
|
|
return {};
|
|
}
|
|
}
|
|
inline std::atomic<unsigned> server_poll_answers_flag{};
|
|
// "/p <message>" in a lobby: one line for the local player's party only, relayed by the host.
|
|
std::string send_party_chat(Session &s, std::string_view typed);
|
|
// The "/" commands this session offers (the chat overlay lists them as the player types "/").
|
|
std::vector<MultiplayerChatCommand> chat_commands(const Session &s);
|
|
// One encoded throwdown message from this player to everyone else (through the host).
|
|
void send_throwdown(Session &s, std::vector<std::uint8_t> message);
|
|
// session_send.cpp
|
|
Packet packet(Session &s, PacketKind kind, std::uint64_t now);
|
|
void reset_direct(Session &s, Peer &p, std::uint64_t now);
|
|
void disconnect(Session &s, std::uint64_t id, const std::string &reason);
|
|
bool send_packet(Session &s, std::uint64_t id, const Packet &p, bool reliable, bool fresh,
|
|
std::span<const std::uint8_t> raw = {}, std::span<const std::uint8_t> wire = {});
|
|
void send_required(Session &s, std::uint64_t id, const std::vector<std::uint8_t> &bytes);
|
|
void send_world_state(Session &s, std::uint64_t now);
|
|
void begin_host_world(Session &s, std::string_view destination, std::uint64_t now);
|
|
void broadcast(Session &s, const Packet &packet, bool reliable, bool fresh, std::uint64_t now,
|
|
std::uint64_t except = 0);
|
|
void sync_objects(Session &s, const NativeFrame &local, std::uint64_t now);
|
|
void refresh_host_choices(Session &s, std::uint64_t now);
|
|
void send_roster(Session &s, std::uint64_t now);
|
|
// Host: sends its physics tuning when it changes. Guest: skates with the session's tuning
|
|
// while the host enforces it, and with its own otherwise.
|
|
void update_physics_tuning(Session &s, const NativeFrame &local, std::uint64_t now);
|
|
// How the local mods change trick scoring (Engine/Vfs/mod_scoring.h). Guest: reported to the host
|
|
// or dedicated server once known and whenever it changes. Host: judged like a guest's.
|
|
void update_scoring(Session &s, std::uint64_t now);
|
|
// Host: flags or clears a guest from its report and the host's choice.
|
|
void judge_scoring(Session &s, Peer &peer);
|
|
// The chat line for the local player's own mods changing scoring or physics.
|
|
std::string own_scoring_notice();
|
|
void send_local(Session &s, const NativeFrame &local, std::uint64_t now, std::uint64_t captured_at = 0,
|
|
bool pose_captured = true);
|
|
// session_receive.cpp
|
|
void apply_roster(Session &s, const Packet &p, std::uint64_t now);
|
|
// Parties (session_party.cpp).
|
|
std::uint32_t party_of(const Session &s, std::uint64_t id); // 0 = none; the local player too
|
|
// Another player in the local player's party.
|
|
bool party_member(const Session &s, std::uint64_t id);
|
|
std::uint64_t party_leader(const Session &s); // the local party's leader, 0 without one
|
|
// The local player's roster entry changed (roster from the host, or the host's own).
|
|
void set_local_party(Session &s, const Member &local);
|
|
void receive_party(Session &s, const Packet &p, std::uint64_t now);
|
|
void expire_party_invites(Session &s, std::uint64_t now);
|
|
// Sends a request to whoever owns the parties (a lobby's host answers its own at once);
|
|
// returns why it can't, or empty.
|
|
std::string send_party_request(Session &s, PartyAction action, std::uint64_t player);
|
|
// A lobby's host: answers `from`'s party request, as a dedicated server does.
|
|
void host_party_request(Session &s, std::uint64_t from, PartyAction action, std::uint64_t player, std::uint64_t now);
|
|
// A lobby's host: `from`'s "/p" line, relayed to the rest of their party only.
|
|
void host_party_chat(Session &s, std::uint64_t from, std::string_view text, std::uint64_t now);
|
|
// A lobby's host, every network tick: players who left are out of their parties, lapsed
|
|
// invites are withdrawn, and a change sends a new roster.
|
|
void tick_host_parties(Session &s, std::uint64_t now);
|
|
// A lobby's host: each roster member's party as the book has it.
|
|
void fill_roster_parties(Session &s, std::vector<Member> &members);
|
|
bool accept_data(Peer &peer, const Packet &p, std::uint64_t now);
|
|
// The same, moving a pose into the playback buffer (the packet's pose is left empty).
|
|
bool accept_data(Peer &peer, Packet &&p, std::uint64_t now);
|
|
void networking(Session &s, const NativeFrame &local, std::uint64_t now);
|
|
// session_commands.cpp
|
|
void apply_distances(Session &s, const MultiplayerDistances &distances);
|
|
void apply_object_placement(Session &s, ObjectPlacement policy);
|
|
// The session's limit, and this game's own share of it: none while hosting or as a server's admin.
|
|
void apply_object_limit(Session &s, unsigned limit);
|
|
// Stores what guests may use and applies it to the local player (the host and a dedicated
|
|
// server's admins are exempt).
|
|
void apply_guest_tools(Session &s, bool noclip, bool no_bail, bool boosts);
|
|
// Shows the chosen nametags: ReSkate's (and none of the game's nametags or compass
|
|
// arrows), the game's own, or none.
|
|
void apply_nametags(const Session &s);
|
|
} // namespace dingosdk::multiplayer::session_detail
|