#pragma once #include "server_activity.h" #include "worker_pool.h" #include "server_config.h" #include "board_check.h" #include "speed_check.h" #include "Engine/Game/Multiplayer/chat_rate.h" #include "Extension/Multiplayer/Net/delta_codec.h" #include "Extension/Multiplayer/Net/pose_batch.h" #include "Extension/Multiplayer/Net/sound_codec.h" #include "Extension/Multiplayer/Session/object_state.h" #include "Extension/Multiplayer/Session/party_book.h" #include "Extension/Multiplayer/Session/password.h" #include "Extension/Multiplayer/Session/room.h" #include "Extension/Multiplayer/Steam/steam_transport.h" #include "Engine/Game/World/world_layers.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace dingosdk::server { class ServerPlugins; using namespace multiplayer; // The host side of a ReSkate session without a game: admission, the roster, // map changes and relaying every player's poses, outfits, voice, chat and // objects. It follows the protocol the in-game host speaks (session_*.cpp), // minus the host's own skater. class Host { public: using Log = std::function; Host(ServerConfig &config, SteamTransport &transport, Log log); // Opens the listener and starts a session under the given server identity. bool start(std::string &error); void tick(std::uint64_t now); // A console line, or an admin's request (`admin` = their SteamID64, 0 for the console). std::string command(std::string_view line, std::uint64_t admin = 0); void stop(const std::string &reason); std::string invite() const; std::string map_name() const; // The level players load for the map, as their games name it: a destination's level, after // its "|". What the server's plugins see as reskate.level.current(). std::string map_level() const; // "maps reload": reads Mods again and says what changed. Also after a map was updated. std::string reload_maps(); // The mod folder of the map in play while anyone is on (an update leaves it alone), or // nothing; and whether "maps update" was typed since this was last asked. std::string map_folder_in_play() const; bool take_map_update_request() { return std::exchange(map_update_requested_, false); } // The plugins players are told to have ("plugins.required"): the config's, less those a // check on Thunderstore found are not plugins (refuse_plugins). std::vector required_plugins() const; void refuse_plugins(std::set refused) { refused_plugins_ = std::move(refused); } unsigned players() const; std::uint64_t secret() const { return secret_; } // The UDP port players may connect straight to, or 0 (config connection). std::uint16_t direct_port() const { return direct_port_; } const ServerConfig &config() const { return config_; } bool voice_allowed() const { return voice_policy_.allowed; } bool has_password() const { return password_.has_value(); } struct PlayerInfo { std::uint64_t id{}; std::string name; unsigned ping{}; bool admin{}; bool voice_muted{}; bool text_muted{}; float x{}, y{}, z{}; }; std::vector player_list() const; void send_player_chat(std::uint64_t steam_id, std::string_view text); void broadcast_player_chat(std::string_view text); bool teleport_player(std::uint64_t steam_id, float x, float y, float z); bool is_banned(std::uint64_t id) const; // A player's locks (lock_*, server_config.h), and making them exactly `locks` as an admin's // lock and unlock would (logged, and the player told): the tools that changed. std::uint8_t player_locks(std::uint64_t id) const { return locks_of(id); } std::uint8_t set_locks(std::uint64_t id, std::uint8_t locks, const std::string &source); std::optional> player_position(std::uint64_t steam_id) const; // The role a player has (data/roles.json: given them, or the default), here or not; or none. const Role *player_role(std::uint64_t id) const { return role_of(id); } // Where each player in the world last said their skater was (their latest pose's root), and // how long ago that arrived: what the server's plugins see of the skaters. struct SkaterPose { std::uint64_t id{}; Transform root; std::uint64_t age_us{}; }; std::vector skater_poses() const; void drop(std::uint64_t id, const std::string &reason, const std::string &detail = {}); void change_map(std::string_view map); // a level name, level path or destination std::uint64_t world_id() const { return world_; } // A server plugin changes the map, or the time of day for everyone, with the checks of the // console's map and tod (and saved as they save): "" when done, else why not. std::string plugin_map(std::string_view map); std::string plugin_time_of_day(std::string_view time); // The last chat lines, for the server's plugins (chat.read): each player's line as it is passed // on, the server's own lines to everyone (sender 0), and a player's /command the server does // not know itself, while a plugin hears chat. Sequences count from 1. struct ChatRecord { std::uint64_t sequence{}, sender{}; std::string name, text; bool server{}; }; std::vector chat_since(std::uint64_t sequence) const; // The park mods set up (maps.park_mods), for the server's plugins: which fit the map running // now, and which are spawned, with how many objects and minutes left (0: until removed). struct ParkStatus { std::string name, map; bool here{}, spawned{}; std::size_t objects{}; unsigned minutes_left{}; }; std::vector park_status() const; // One spawned (for `minutes`, 0 until removed) or removed for a server plugin, as "park-mod add" // and "remove" do, with their checks and limits and their "[parks]" lines logged "by plugin // ": "" when it is done, else why not. std::string plugin_park(std::string_view plugin, bool spawn, std::string_view name, unsigned minutes); // A plugin message from the server's plugins (server_plugins.h) to one player, or everyone (0). void send_plugin_message(std::uint64_t to, const std::vector &bytes); // Cards and polls for the server's plugins (server_votes.cpp), on the players' screens: each // player's own, shown over the server's announcement and vote until it ends, then those again. // `players` empty is everyone on now. A card is up `seconds` (0: as long as an announcement); // a poll runs `seconds` (0: votes.poll_seconds), one id for all its players, closed for each // as they answer ("/2"); a player's newer poll replaces theirs. "" when shown, else why not. std::string plugin_card(std::string_view plugin, const std::vector &players, std::string_view text, unsigned seconds); std::string plugin_poll(std::string_view plugin, const std::vector &players, std::string_view question, const std::vector &answers, unsigned seconds, std::uint32_t &id); std::string plugin_poll_close(std::string_view plugin, std::uint32_t id); void plugin_screens_release(std::string_view plugin); // a plugin that stopped: its cards and polls go // A player's answer (from 1) to a plugin's poll, or (answer 0) the poll closed for them and why: // "timeout", "left", "replaced" or "closed". struct PollNews { std::string plugin; std::uint32_t id{}; std::uint64_t player{}; std::size_t answer{}; std::string text, why; }; std::vector take_poll_news() { return std::exchange(poll_news_, {}); } // One player hidden from others for the server's plugins (server_host.cpp). Wholly (`skater`): // they leave those players' rosters, and nothing of them (poses, sound, voice, outfit, effects, // objects) is sent to them, so their games take the skater away as for a player who left. // Otherwise only their voice and/or sound is held back. `viewers` empty is everyone on now but // the target. Refused while the target shares a party or a throwdown with one of them. Shown // again (`viewers` empty: everyone this plugin hid them from), they are introduced anew. // "" when done, else why not. std::string plugin_hide(std::string_view plugin, std::uint64_t target, const std::vector &viewers, bool skater, bool voice, bool sound); std::string plugin_unhide(std::string_view plugin, std::uint64_t target, const std::vector &viewers); void plugin_hides_release(std::string_view plugin); // a plugin that stopped: everyone it hid is shown again enum class VoteKind { map, kick, time, custom, poll, extend }; private: struct ChatBudget { std::uint64_t since{}; unsigned messages{}; // A few messages at once, then they recover every 5 s. bool accept(std::uint64_t now, unsigned burst = 6) noexcept { if (now < since || now - since >= 5000000) { since = now; messages = 0; } return ++messages <= burst; } }; // What is sent and received, in bytes, by what it carries (traffic_kind): poses, sound, // voice, outfits, objects, the rest. `last` is the last whole half minute. struct Traffic { std::array out{}, in{}; std::uint64_t snapshots{}; // whole states sent reliably; the rest of a stream is differences }; struct Counted { Traffic total, mark, last; }; struct Guest { Member member; Counted traffic; std::uint64_t password_challenge{}; bool handshaken{}, map_authorized{}, world_ready = true; std::uint64_t last_map_offer{}, travel_since{}, connected_at{}, last_packet{}; std::uint64_t loading_since{}; // not ready in this world since (reloading on their own) // When they first said they are fetching the map (Packet::map_fetching), 0 for never // in this world: while they go on saying so, within map_fetch_limit_us of then, the // time they are given to load starts over. std::uint64_t fetching_since{}; std::uint32_t ready_sequence{}; ReceiveBudget budget; DeltaSender sender; DeltaReceiver receiver; PoseBuffer poses; AudioBuffer audio; AppearanceBuffer appearance; std::vector cosmetic_packet; struct PendingCosmetics { Packet packet; std::uint64_t received{}; }; std::vector pending_cosmetics; std::optional latest_root; // Where they last stood and faced, and when they last moved from it: a player standing // still (in a menu, away from the keyboard, watching) is sent on at the low rate. // Their earlier poses, as encoded: the one before, and ones kept a quarter of a second // and a second (measure_pose). struct Earlier { std::vector raw; std::uint64_t time{}; }; Earlier pose_last, pose_quarter, pose_second; // Their own last poses, rounded (pose_codec.h): what the others' differences are built // on. `keep` is how long one stays, by the slowest rate anyone was sent it at: a // player sent this one once a second must still find it here when their ack arrives. struct KeptPose { std::uint32_t sequence{}; std::uint64_t time_us{}, kept_at{}; std::uint8_t keep{}; pose_codec::QuantPose pose; }; std::deque kept_poses; // What this player is being sent of each other one, and what of it they acked. pose_batch::Sender pose_sender; // Their own poses arrive the same way (receive_poses): the last ones rebuilt, and which // of their messages were read in full, which they are told once a pass. std::unordered_map upload_streams; pose_batch::Ack upload_ack; bool upload_ack_due{}; // Poses due to them this pass (flush_poses). struct QueuedPose { std::uint64_t source{}; std::uint32_t sequence{}; pose_batch::Rate rate{}; bool collision{}, hold_fingers{}; std::uint8_t tier{}; // 0 full rate, 1 half, 2 low, 3 out of sight }; std::vector queued_poses; // Skaters' sound (sound_codec.h): what this player holds of each other one's, the // samples due to them this pass, and what is held here of their own. sound_codec::Sender sound_sender; struct QueuedSound { std::uint64_t source{}, time_us{}; std::uint32_t sequence{}; std::shared_ptr> samples; }; std::vector queued_sound; std::unordered_map sound_in; Transform still_at; std::uint64_t moved_at{}; // When they last did something a player at their game does: moved, spoke, typed in chat // or changed their objects (0 until their game has loaded the map). And whether they have // been told they are about to be removed for being away. std::uint64_t active_at{}; bool away_warned{}; std::uint64_t present_at{}; // their game's last presence notice counted (PacketKind::present) std::uint64_t pose_arrival{}; std::array pose_delivery; CrowdLimits crowd; // how far the full and half rates reach for them in a crowd std::vector direct_routes; std::uint64_t route_reported{}; std::uint32_t route_sequence{}; bool received_voice{}; std::uint32_t voice_sequence{}; VoiceBudget voice_budget; OutfitBudget outfit_budget; SoundBudget sound_budget; EffectBudget effect_budget; // Their poses read this pass, passed on once all of the pass's messages are read // (relay_poses), and how many of their effects packets were passed on in it. std::vector relay_poses; unsigned effects_pass{}; ChatRate chat_rate; std::uint64_t typing_notice{}; // last accepted "still typing", so a flood is not relayed bool typing{}; // an "on" was passed along and no "off" since ChatBudget admin_budget, throwdown_budget, party_budget, plugin_budget; SpeedCheck speed; // how fast their game runs, from their pose timestamps bool speeding{}; // flagged: out of linked activities (config speed_check) std::uint64_t speed_normal_since{}; // flagged, but measuring normal again since then BoardCheck board; // whether their skateboard is being kept on another player bool board_hidden{}; // flagged: their board is not shown to the others (config board_check) // How their mods change trick scoring, as they reported it (Engine/Vfs/mod_scoring.h): // nothing until the report arrives, 0 for the game's own. std::optional scoring; std::string scoring_mods; bool scoring_flagged{}; // out of linked activities (config score_check) ChatBudget scoring_budget; std::uint64_t bans_sent{}; // the ban list revision this admin has std::uint64_t undelivered_since{}; // Steam has refused what they must be sent since // The players they have not been shown yet (introduce): on joining, and after a map // change, a player is sent the others a few a second, nearest first, instead of every // outfit and every stream at once. Nothing of a player in here is sent to them. std::set unmet; std::uint64_t next_introduction{}; bool maps_sent{}; // this player has the server's map list (send_maps) std::uint64_t permissions_sent{}; // the roles revision their permissions were sent at std::uint64_t words_sent{}; // the words revision the server's filtered words were sent at std::uint64_t next_maps_sent{}; // the revision the next vote's maps were sent at // The owner's own upload, and what the server shares of it. ObjectState objects, shared; std::uint64_t shared_from{}; std::set cleared; // Objects they placed in the last minute, and until when none of theirs are shared, for // placing more than a person does (sync_objects). std::uint64_t placed_since{}, placed{}, objects_held_until{}; struct ObjectDelivery { std::map> sent; std::vector chunks; std::uint64_t source{}, epoch{}; std::size_t next{}, cursor{}; std::uint64_t failing_since{}; // Steam has refused their object updates since } object_delivery; }; ServerConfig &config_; SteamTransport &transport_; Log log_; bool map_update_requested_{}; std::set refused_plugins_; ActivityLog activity_; // what players do, for the console (config_.activity_log) // The running player vote (server_votes.cpp), and when each player may start another. // A poll is one too: it has answers instead of yes and no, and runs nothing (but the console's poll-run). struct Vote { VoteKind kind{}; std::size_t custom{}; // custom: which of config_.votes.custom std::uint32_t id{}; // told to games, so each can tell one vote from the next std::uint64_t starter{}, target{}; // target: the player a kick vote is about std::string value, label; // value: the map, time or command; label: "change the map to ..." or the question std::set yes, no; std::vector answers; // poll std::map chosen; // poll: each voter's answer std::string run; // poll-run: the console command for the winner ("{answer}") bool next_map{}; // poll: the rotation's vote for the next map (start_next_map_vote) std::uint64_t ends{}; }; std::optional vote_; // What games are shown of the vote (the roster carries it): the running one with its // tally, or the one just finished with how it ended, for a few seconds. multiplayer::ServerVote vote_shown_; std::uint64_t vote_shown_until_{}; std::uint32_t vote_ids_{}; void show_vote(const Vote &vote, std::uint8_t outcome); // The announcement games show (the roster carries it), until announcement_until_. multiplayer::ServerAnnouncement announcement_; std::uint64_t announcement_until_{}; std::uint64_t announcement_for_{}; // the one player shown it, or 0: everyone std::uint32_t announcement_ids_{}; // The plugins' cards and polls, by the player they are shown to (plugin_card, plugin_poll). struct PluginCard { std::string plugin; multiplayer::ServerAnnouncement card; std::uint64_t until{}; }; struct PluginPoll { std::string plugin; std::uint32_t id{}; std::string question; std::vector answers; std::uint64_t ends{}; }; std::map plugin_cards_; std::map plugin_polls_; std::vector poll_news_; // The players the server's plugins hide (plugin_hide), by who they are hidden from: each // target's plugin, and what of them is held back. struct Hidden { std::string plugin; bool skater{}, voice{}, sound{}; }; std::map