#pragma once #include "input.h" #include "sim.h" #include #include struct Game; constexpr int SESSION_RING = 64; // A member's connection has no ping to show. constexpr float PING_NONE = -2; // One tick of the room as this screen played it: the state as it started (and its checksum), and once the tick's // events were in, who played each fighter and the controls each was given. struct SessionTick { Snapshot state; Controls controls[MAX_FIGHTERS]; int owners[MAX_FIGHTERS]; Uint32 checksum = 0; int tick = -1; }; // The server's frame for a tick: every fighter's controls as it played them, after the events before them. struct SessionFrame { Controls controls[MAX_FIGHTERS]; int count = 0; int events = 0; }; // Online play: this screen runs the room it's in (the lobby's sandbox, or a game queued in it), which the server runs // too (see server/server.cpp). It plays ahead of the server, its own controls sent for the ticks it plays and everyone // else's guessed to keep doing what they last did. The server's frames say what everyone really did: when one differs // from what was played, the game rewinds to that tick and plays forward again (silently). It keeps its lead over the // server by playing a tick more, or one less, now and then, as the server says how early its controls come. class Session { public: bool isActive = false; bool isJoined = false; bool isDesynced = false; // Set when the room changes, for the camera to jump. bool isViewChanged = false; int rollbacks = 0; int stalls = 0; void update(Game& game, Controls local); void stop(Game& game); bool is_wanted(Game& game); bool is_watching(); bool is_playing_arena(Game& game); bool is_queued(Game& game); void arena_leave(Game& game); int fighter_get(Game& game, int member); std::string base_name_get(Game& game, int member); std::string name_get(Game& game, int member); void chat_send(Game& game, const std::string& text); void typing_send(Game& game, bool isTyping); void kick(Game& game, const std::string& name, const std::string& reason); int rename_wait_get(); void queue_send(Game& game, const Rules& rules, bool isOpen = true); float ping_get(Game& game, int member); private: SessionTick ticks[SESSION_RING]; Controls own[SESSION_RING]; std::map frames; // Each room's events by number, kept from before its state comes in case they come first. std::map> events; std::map checksums; std::map held; // Every member's chosen name (cleaned), kept current by their renames, and the colour this screen last asked for. std::map names; int room = 0; // The next tick to play, the first not yet confirmed, and the earliest that has to be played again. int tick = 0; int confirmed = 0; int rewindTo = -1; // How early (in ticks) this screen's controls last reached the server, and when its pace last changed. int lead = 0; int paced = 0; int sentColor = -1; // When a newcomer gives up waiting for the WebRTC link, and whether its fighter has been brought in. Uint64 linkDeadline = 0; bool isProfileSent = false; // With SAB_NET_STATS set, how it's keeping up goes to the log every few seconds. bool isStatsLogged = false; // Names can change only every RENAME_WAIT, so a player can't dodge being kicked by renaming: when this screen's can // next, and whether it's told its pending one is waiting. Uint64 nextRename = 0; bool isRenameWaiting = false; void start(Game& game); void packets_take(Game& game); void packet_handle(Game& game, const std::string& bytes); void state_load(Game& game, PacketReader& reader, int serial, int at); void frames_take(PacketReader& reader); void confirm(Game& game); void rewind(Game& game); void step(Game& game, bool isLive); void inputs_send(Game& game); void local_find(Game& game); void profile_watch(Game& game); void text_send(Game& game, PacketType type, const std::string& text); };