mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-20 05:16:26 +02:00
Server - Dedicated servers run at 20 network updates a second, fixed for now: whatever tps says in ReSkateServer.json is read as 20, and the tps command no longer changes it. A busy server's traffic, in and out, is its players' poses, and 20 a second is a third less than 30. Lobbies keep their own choice - A player who has not moved or turned for three seconds is sent on at five poses a second; the full rate is back with their first movement - "net" shows what a pose costs as sent, and what it would cost against a reference renewed every second, every quarter second, and against the pose before, measured on every fourth pose that arrives. It only measures: a nearer reference packs smaller, and this says by how much before the server is made to send them that way Game - The "/" menu on a dedicated server lists /w for everyone, and /msg, /msg-party and /msg-admins for admins. The server already answered them; the menu is the game's own list and never had them
245 lines
12 KiB
C++
245 lines
12 KiB
C++
#pragma once
|
|
#include "server_activity.h"
|
|
#include "server_config.h"
|
|
#include "speed_check.h"
|
|
#include "Engine/Game/Multiplayer/chat_rate.h"
|
|
#include "Extension/Multiplayer/Net/delta_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 <functional>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <string>
|
|
|
|
namespace dingosdk::server {
|
|
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<void(const std::string &)>;
|
|
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;
|
|
unsigned players() const;
|
|
std::uint64_t secret() const { return secret_; }
|
|
|
|
enum class VoteKind { map, kick, time };
|
|
|
|
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<std::uint64_t, 6> 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)
|
|
std::uint32_t ready_sequence{};
|
|
ReceiveBudget budget;
|
|
DeltaSender sender;
|
|
DeltaReceiver receiver;
|
|
PoseBuffer poses;
|
|
AudioBuffer audio;
|
|
AppearanceBuffer appearance;
|
|
std::vector<std::uint8_t> cosmetic_packet;
|
|
struct PendingCosmetics { Packet packet; std::uint64_t received{}; };
|
|
std::vector<PendingCosmetics> pending_cosmetics;
|
|
std::optional<Transform> 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<std::uint8_t> raw; std::uint64_t time{}; };
|
|
Earlier pose_last, pose_quarter, pose_second;
|
|
Transform still_at;
|
|
std::uint64_t moved_at{};
|
|
std::uint64_t pose_arrival{};
|
|
std::array<PoseDelivery, max_players> pose_delivery;
|
|
CrowdLimits crowd; // how far the full and half rates reach for them in a crowd
|
|
std::vector<Member> 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;
|
|
ChatRate chat_rate;
|
|
ChatBudget admin_budget, throwdown_budget, party_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
|
|
// 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<std::uint64_t> 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<std::uint64_t> unmet;
|
|
std::uint64_t next_introduction{};
|
|
bool maps_sent{}; // this player has the server's map list (send_maps)
|
|
// The owner's own upload, and what the server shares of it.
|
|
ObjectState objects, shared;
|
|
std::uint64_t shared_from{};
|
|
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{};
|
|
std::uint64_t failing_since{}; // Steam has refused their object updates since
|
|
} object_delivery;
|
|
};
|
|
|
|
ServerConfig &config_;
|
|
SteamTransport &transport_;
|
|
Log log_;
|
|
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.
|
|
struct Vote {
|
|
VoteKind kind{};
|
|
std::uint64_t starter{}, target{}; // target: the player a kick vote is about
|
|
std::string value, label; // value: the map or time; label: "change the map to ..."
|
|
std::set<std::uint64_t> yes, no;
|
|
std::uint64_t ends{};
|
|
unsigned shown_yes{}, shown_no{}; // the tally last announced
|
|
};
|
|
std::optional<Vote> vote_;
|
|
bool vote_recount_{}; // a player left: recount in tick(), never while guests_ is being walked
|
|
std::map<std::uint64_t, std::uint64_t> vote_cooldowns_;
|
|
std::uint64_t map_since_{}; // rotation clock start: the last map change, or while nobody is on
|
|
bool rotation_warned_{}; // players were told the next map is a minute away
|
|
// Parties (server_party.cpp): the server owns them; each roster carries them to everyone.
|
|
PartyBook parties_;
|
|
std::uint64_t party_revision_{};
|
|
std::map<std::uint64_t, std::unique_ptr<Guest>> guests_;
|
|
std::set<std::uint64_t> kicked_;
|
|
JoinBackoff join_backoff_; // Steam IDs whose attempts to join keep failing
|
|
std::optional<PasswordKey> password_;
|
|
std::uint64_t id_{}, secret_{}, epoch_{}, map_{}, world_ = 1;
|
|
std::uint32_t sequence_{};
|
|
VoicePolicy voice_policy_;
|
|
std::uint32_t object_clears_{};
|
|
WorldLayerChoices layers_;
|
|
bool roster_dirty_ = true, running_{};
|
|
std::uint64_t bans_revision_ = 1; // bumped whenever the ban list or the admins change
|
|
std::uint64_t now_{}, last_roster_{}, last_world_state_{}, next_object_update_{}, travel_started_{};
|
|
std::uint64_t next_crowd_{}; // when the crowd limits are worked out again
|
|
std::uint64_t next_network_log_{}; // the log's once-a-minute line about the connections
|
|
// How the server's own loop is keeping up, for `net` and that line: passes of tick() in a
|
|
// minute, the time spent in them, the longest one and the longest wait between two. A long
|
|
// pass or gap is the server itself stalling, which every player feels at once.
|
|
struct Loop {
|
|
std::uint64_t since{}, passes{}, busy_us{}, longest_pass_us{}, longest_gap_us{};
|
|
} loop_, last_loop_, worst_loop_;
|
|
std::uint64_t pass_started_{};
|
|
Counted traffic_;
|
|
// What a pose costs to send as a difference from references of different ages, measured on
|
|
// every fourth pose that arrives (net). A nearer reference changes less, so it packs
|
|
// smaller: this says by how much, before the server is made to send them that way.
|
|
struct PoseSizes {
|
|
std::uint64_t samples{}, whole{}, last{}, quarter{}, second{}, sent{}, sent_bytes{};
|
|
} pose_sizes_;
|
|
void measure_pose(Guest &from, const Packet &packet);
|
|
std::uint64_t traffic_mark_{}, traffic_window_us_{}; // when `mark` was taken, and how long `last` covers
|
|
void meet_later(Guest &guest);
|
|
void introduce();
|
|
std::string loop_report() const;
|
|
std::string network_report(std::string_view player, bool console);
|
|
|
|
Guest *find(std::uint64_t id);
|
|
Packet packet(PacketKind kind, std::uint64_t now);
|
|
unsigned capacity() const { return config_.max_players + 1; }
|
|
std::string guest_name(const Guest &) const;
|
|
std::string player_name(std::string_view wanted, std::uint64_t id) const;
|
|
bool is_admin(std::uint64_t id) const;
|
|
bool is_banned(std::uint64_t id) const;
|
|
void save();
|
|
|
|
// `detail` is for the log alone: the player is told `reason`.
|
|
void drop(std::uint64_t id, const std::string &reason, const std::string &detail = {});
|
|
bool send_packet(Guest &, const Packet &, bool reliable, bool fresh, std::span<const std::uint8_t> raw = {},
|
|
std::span<const std::uint8_t> wire = {});
|
|
void send_required(Guest &, const std::vector<std::uint8_t> &bytes);
|
|
void broadcast(const Packet &, bool reliable, bool fresh, std::uint64_t except = 0);
|
|
void send_roster();
|
|
void send_world_state();
|
|
void send_chat(std::string_view text, Guest *only = nullptr);
|
|
void send_bans(Guest &admin);
|
|
void send_maps(Guest &admin);
|
|
void change_map(std::string_view map); // a level name, level path or destination
|
|
std::string wire_map_label() const;
|
|
bool same_map(std::string_view asset) const { return map_hash(map_destination(asset)) == map_; }
|
|
bool accept_data(Guest &source, const Packet &);
|
|
// `received_at`: the transport's arrival time for the message (TransportMessage::arrived).
|
|
void receive(std::uint64_t peer, std::span<const std::uint8_t> bytes, std::uint64_t received_at);
|
|
void receive_cosmetics();
|
|
void sync_objects();
|
|
void apply_layers();
|
|
// Chat commands and votes (server_votes.cpp).
|
|
void chat_command(Guest &, std::string_view line);
|
|
void start_vote(Guest &, VoteKind, std::string_view argument);
|
|
void cast_vote(Guest &, bool yes);
|
|
void check_vote(bool expired);
|
|
void cancel_vote(const std::string &why);
|
|
const VoteSetting &vote_setting(VoteKind) const;
|
|
std::uint8_t enabled_votes() const;
|
|
void reply(Guest &, std::string_view text);
|
|
Guest *match_player(std::string_view text);
|
|
void tick_rotation();
|
|
std::string pool_text() const; // the map pool, one map a line
|
|
std::string rotation_text() const; // the rotation's interval and next map
|
|
void resend_maps(); // after the pool or the admins change
|
|
// Parties (server_party.cpp).
|
|
void party_request(Guest &, PartyAction, std::uint64_t player);
|
|
void party_command(Guest &, std::string_view line); // "/party ..."
|
|
void party_chat(Guest &, std::string_view text); // "/p <text>": to the sender's party only
|
|
void party_left(std::uint64_t id, const std::string &name); // a player left the server
|
|
bool check_speed(Guest &, std::uint64_t sent); // a pose they sent at `sent` (their clock); false: kicked
|
|
void check_scoring(Guest &); // after their report, or a score_check change
|
|
void tick_parties();
|
|
void send_party(Guest &to, PartyAction, std::uint64_t player);
|
|
void party_notice(std::uint32_t party, std::string_view text, std::uint64_t except = 0);
|
|
std::string party_status(std::uint64_t id) const;
|
|
};
|
|
} // namespace dingosdk::server
|