Initial commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shweetandClaude Opus 5.5 committed 2026-11-08 01:30:33 -05:00
commit 2007fb7edc
200 files changed
+27710

No files matched your search

+224
View File
@@ -0,0 +1,224 @@
#pragma once
#include "bot.h"
#include "input.h"
#include "match.h"
#include "rollback.h"
#include "trinket.h"
#include "weapon.h"
#include <map>
#include <set>
#include <string>
#include <vector>
struct Game;
constexpr int INPUT_RING = 1024;
constexpr int SNAPSHOT_RING = 64;
// A member's connection has no ping to show (it's this screen's own, hosting).
constexpr float PING_NONE = -2;
// A player's setup as they join online: their name, the colour they'd like (-1 for any), and their sandbox slots,
// loadouts, levels and stacking, which their fighter starts with. Plain data, sent as it sits in memory.
struct Profile
{
char name[PLAYER_NAME_MAX + 1] = {};
int color = -1;
int weaponSlots = SLOTS_DEFAULT;
int trinketSlots = SLOTS_DEFAULT;
int weaponLoadout[SLOT_MAX] = {};
int trinketLoadout[SLOT_MAX] = {};
int weaponSlotLevels[SLOT_MAX] = {};
int trinketSlotLevels[SLOT_MAX] = {};
int weaponItemLevels[WEAPON_COUNT] = {};
int trinketItemLevels[TRINKET_COUNT] = {};
};
enum SessionEventType
{
EVENT_JOIN,
EVENT_LEAVE,
// A member queues a game in the lobby's sandbox, with its rules, or stops queueing one.
EVENT_QUEUE,
EVENT_UNQUEUE
};
// A change to who's playing, at a set tick, so every machine makes it at the same point in the game (and again when
// rolling back over it). From is, for a join, the tick the newcomer's copy of the game starts at.
struct SessionEvent
{
int tick = 0;
int type = EVENT_JOIN;
int member = -1;
int from = 0;
Profile profile;
Rules rules;
};
// The queued game being played, a second game beside the lobby's sandbox (see Session): its state between ticks
// (while it isn't swapped into the game), its bots' map of it, which fighter is this screen's in it and the devices
// that go with that, and the tick it started (each play of it, a fresh one).
struct Arena
{
Snapshot state;
Bot bot;
int devices[MAX_FIGHTERS] = {};
int local = 0;
int serial = -1;
int graphSerial = -1;
bool isActive = false;
};
// One member's controls by tick, in a ring that keeps the last INPUT_RING ticks. They're needed from start (when the
// member's fighter appears) until until (when it goes). Contiguous is the last tick up to which every tick from start
// on is known, so up to which the member's play is certain. Used is what a tick was
// simulated with when the member's controls had to be guessed, to spot a wrong guess when the real ones come.
struct InputLog
{
Controls controls[INPUT_RING];
int ticks[INPUT_RING];
Controls used[INPUT_RING];
int usedTicks[INPUT_RING];
int start = 0;
int until = INT32_MAX;
int contiguous = -1;
int latest = -1;
InputLog();
void restart(int from);
bool has(int tick) const;
bool put(int tick, const Controls& controls);
Controls guess(int tick) const;
};
// What this screen knows of another member: the last tick of this screen's inputs they've confirmed having, the first
// one they need, whether to send them inputs at all yet, and the tick they last said they were on.
struct SessionPeer
{
int ack = -1;
int sendFrom = 0;
bool isSending = false;
int remoteTick = 0;
};
// Online play as one game on every machine: rollback netcode. Each member's controls go to everyone, a few ticks
// ahead of when they're used (the input delay); a member whose controls for a tick haven't arrived yet is guessed to
// keep doing what they last did, and when the real ones turn out different, the game rewinds to that tick and plays
// forward again (silently). A machine that gets too far ahead of what it knows, or of the others, waits.
//
// The lobby's host decides who comes and goes, and when: a newcomer gets the game's state as of a tick everyone's
// controls are certain for, fast-forwards from there, and appears at a tick a moment ahead, on every machine at once.
// Someone leaving is taken out at the tick after their last known controls. Checksums of certain ticks are compared,
// to catch machines drifting apart.
//
// Lobby members can queue a game in the sandbox (see Match::queue_open). Once it's ready, it starts as a second game,
// the arena, beside the sandbox: both step every tick on every machine, each with their own fighters, the players
// in the arena standing faded in the sandbox meanwhile. This screen shows one of the two (the arena while it plays in
// it, or watches it): that one is swapped into the game around this screen's own work, which otherwise always sees
// the sandbox.
class Session
{
public:
bool isActive = false;
bool isJoined = false;
bool isDesynced = false;
// Set when what this screen shows changes between the sandbox and the arena, for the camera to jump.
bool isViewChanged = false;
int tick = 0;
int delay = 2;
int rollbacks = 0;
int stalls = 0;
void host_start(Game& game);
void join_start(Game& game);
void stop(Game& game);
void update(Game& game, Controls local);
bool is_wanted(Game& game);
Profile profile_get(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);
void unqueue_send(Game& game);
bool is_queued(Game& game);
bool view_enter(Game& game);
void view_leave(Game& game);
bool is_arena_on();
bool is_playing_arena(Game& game);
bool is_watching();
void watch_start(Game& game);
void watch_stop(Game& game);
void arena_leave(Game& game);
const Arena& arena_get();
std::string arena_names_get(Game& game);
float ping_get(Game& game, int member);
private:
std::map<int, InputLog> logs;
std::map<int, SessionPeer> peers;
std::vector<SessionEvent> events;
std::vector<int> departed;
Snapshot snapshots[SNAPSHOT_RING];
Snapshot arenaSnapshots[SNAPSHOT_RING];
int arenaSerials[SNAPSHOT_RING];
Arena arena;
// Whether this screen watches the arena (not being in it), whether the arena's swapped into the game now, which
// one it showed last (to start afresh on a change), and the chat lines already said for things in the game (which
// replays would otherwise say again).
bool isWatching = false;
bool isSwapped = false;
int shownSerial = -1;
std::set<std::string> announced;
int snapshotTicks[SNAPSHOT_RING];
Uint32 checksums[SNAPSHOT_RING];
std::map<int, Uint32> remoteChecksums;
int checksumSent = -1;
int ownNext = 0;
int ownStart = 0;
int rollbackTo = -1;
bool isRequested = false;
// Every member's chosen name (cleaned), kept current by their renames, and the colour this screen last asked for.
std::map<int, std::string> names;
int sentColor = -1;
// Names can change only every RENAME_WAIT, so a player can't dodge being kicked by renaming: when this screen's can
// next, whether it's told its pending one is waiting, and when each other member's last rename came.
Uint64 nextRename = 0;
bool isRenameWaiting = false;
std::map<int, Uint64> renamedAt;
void packets_take(Game& game);
void packet_handle(Game& game, int from, const std::string& bytes, bool isReliable);
void members_watch(Game& game);
void join_grant(Game& game, int member, const Profile& profile);
void leave_decide(Game& game, int member);
void event_add(Game& game, const SessionEvent& event);
void event_send(Game& game, int to, const SessionEvent& event, int inputsFrom, int inputsTo);
void inputs_send(Game& game);
void step(Game& game, bool isLive);
void events_apply(Game& game, int at);
bool is_needed(int member, int at);
int confirmed_get(Game& game);
void checksums_exchange(Game& game);
void local_find(Game& game);
void broadcast(Game& game, const std::string& bytes);
void rename_send(Game& game, int to, bool isQuiet);
void profile_watch(Game& game);
void controls_give(Game& game, bool isArena);
void arena_swap(Game& game);
void arena_start(Game& game, int queuer);
void arena_build(Game& game, const Rules& rules, const int* members, int count);
void arena_after(Game& game);
void arena_end(Game& game);
void arena_locals_find(Game& game);
void arena_save(Game& game, int slot);
void arena_restore(Game& game, int slot);
Uint32 checksum_full(Game& game);
std::string state_full_save(Game& game);
bool state_full_load(Game& game, const std::string& bytes);
void announce(Game& game, const std::string& key, const std::string& text);
void queue_decide(Game& game, int member, bool isOpen, const Rules& rules);
};