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

+229
View File
@@ -0,0 +1,229 @@
#pragma once
#include "fighter.h"
#include "protocol.h"
struct Game;
constexpr int BEST_OF_MAX = 9;
constexpr int PLAYERS_MAX = 4;
constexpr int PLAYERS_MIN = 2;
constexpr int TIMER_MAX = 10;
// A queued game's area: room per player, a margin each end, its height, how finely the ground's checked for one, and
// how far below its floor feet still count as in it.
constexpr float QUEUE_SLOT_WIDTH = 24;
constexpr float QUEUE_MARGIN = 6;
constexpr float QUEUE_HEIGHT = 40;
constexpr float QUEUE_PROBE_STEP = 4;
constexpr float QUEUE_FEET_SLACK = 3;
constexpr int QUEUE_START_TICKS = 60;
constexpr int BASE_HEALTH_MAX = 10000;
constexpr int BASE_HEALTH_MIN = 10;
constexpr int PERIOD_MAX = 10;
constexpr int PERIOD_MIN = 2;
constexpr float MULTIPLIER_MAX = 5;
constexpr float COUNTDOWN_TIME = 1;
constexpr float READY_TIME = 15;
constexpr float CHOICE_SLOT_TIME = 5;
enum MatchMode
{
MODE_DEATHMATCH,
MODE_RACE
};
enum MatchState
{
MATCH_PRACTICE,
MATCH_COUNTDOWN,
MATCH_FIGHT,
MATCH_ROUND_OVER,
MATCH_OVER
};
// When a match hands out items: once at its start, every round, or every period rounds.
enum PickTiming
{
PICK_FIRST_ROUND,
PICK_EVERY_ROUND,
PICK_PERIOD
};
// How a match hands out one kind of item: how many slots, dealt at random or chosen in the menu, on which rounds, the
// level they start at (1 to 3), whether they gain one each round, whether every fighter mirrors one loadout, whether
// a loadout can hold the same item more than once (copies always stack; see Fighter).
struct ItemRules
{
int period = 2;
int slots = SLOTS_DEFAULT;
int startLevel = 1;
int timing = PICK_FIRST_ROUND;
bool isChoice = true;
bool isDuplicates = false;
bool isLevelUp = true;
bool isMirror = false;
bool operator==(const ItemRules&) const = default;
};
// A match's settings, picked in the menu's Play tab. A deathmatch is best of an odd number of rounds, won by taking
// more than half of them. Health is the first round's, multiplied by the multiplier for each round after. Timer is a
// round's limit in minutes (0 for none). Single player fills every other slot with bots, and bot fill only decides it
// in multiplayer.
struct Rules
{
ItemRules trinkets;
ItemRules weapons;
float health = 250;
float healthMultiplier = 2;
int bestOf = 3;
int mode = MODE_DEATHMATCH;
int players = PLAYERS_MIN;
int timer = 0;
bool isBotFill = false;
bool operator==(const Rules&) const = default;
int wins_get() const
{
return bestOf / 2 + 1;
}
};
// A change a player makes to their own fighter from the menu, sent through its controls so that every machine applies
// it on the same tick (and rollback replays it). Item puts an item (or EDIT_EMPTY) in a slot, and is the only kind a
// match takes, as a Choice pick; the rest are the sandbox's: one copy's level, an item's level (with every copy of it
// equipped), the slot counts (index weapons, value trinkets), PvP on or off, and online, the colour (if no one else has
// it).
enum EditType
{
EDIT_ITEM,
EDIT_SLOT_LEVEL,
EDIT_ITEM_LEVEL,
EDIT_SLOTS,
EDIT_PVP,
EDIT_COLOR,
// Online, leaving a queued game (forfeiting it if it's still on).
EDIT_LEAVE
};
struct Edit
{
int type = EDIT_ITEM;
bool isTrinket = false;
int index = 0;
int value = 0;
};
constexpr int EDIT_EMPTY = 31;
constexpr int EDIT_FIELD_BITS = 5;
constexpr int EDIT_FIELD_MASK = (1 << EDIT_FIELD_BITS) - 1;
constexpr int EDIT_KIND_SHIFT = EDIT_FIELD_BITS * 2;
constexpr int EDIT_TYPE_SHIFT = EDIT_KIND_SHIFT + 1;
// An edit packed into one number for a fighter's controls (-1 is none).
inline int edit_encode(Edit edit)
{
return (edit.value & EDIT_FIELD_MASK) | (edit.index & EDIT_FIELD_MASK) << EDIT_FIELD_BITS |
edit.isTrinket << EDIT_KIND_SHIFT | edit.type << EDIT_TYPE_SHIFT;
}
inline Edit edit_decode(int code)
{
return {.type = code >> EDIT_TYPE_SHIFT,
.isTrinket = (code >> EDIT_KIND_SHIFT & 1) != 0,
.index = code >> EDIT_FIELD_BITS & EDIT_FIELD_MASK,
.value = code & EDIT_FIELD_MASK};
}
// Putting an item in a slot (or emptying it, for an item below 0).
inline int pick_encode(bool isTrinket, int slot, int item)
{
return edit_encode(
{.type = EDIT_ITEM, .isTrinket = isTrinket, .index = slot, .value = item < 0 ? EDIT_EMPTY : item});
}
// The default rules for a mode: a best-of-three deathmatch for two, or a three minute race for four.
Rules rules_get(int mode);
Rules rules_clean(Rules rules);
// Online, a game queued in the lobby's sandbox by one of its fighters: its rules, and the area on the ground where
// whoever wants to play it gathers (the queuer standing at its left).
struct Queue
{
Rules rules;
Rect area;
// How many ticks it's been ready to start for (it starts after QUEUE_START_TICKS).
int readyTicks = 0;
bool isActive = false;
};
// Best-of-three deathmatch, or a single race, played by the rules. Practice is the sandbox with the dummy and the menu.
// Each round opens with a long countdown to ready up in (cut short once every fighter is ready), at the round's level
// and health. Countdown is how long it ran from: longer on rounds where fighters choose items. On the rounds items are
// handed out, they're dealt at random, or chosen in the menu during the countdown with any slots left empty dealt at
// random when it ends. A deathmatch round goes to the last fighter standing, or when the timer runs out to whoever has
// the most health left. A drawn round is replayed. A race is won by the first over the finish line (finishers leave the
// course), ending once everyone is over, a while after the winner, or when the timer runs out (then the one furthest
// along wins if nobody finished). Forfeiting, or Escape after a match, goes back to the sandbox with the loadout and
// levels from before it. Enter after a match plays again.
class Match
{
public:
Rules rules = rules_get(MODE_DEATHMATCH);
MatchState state = MATCH_PRACTICE;
float clock = 0;
float countdown = READY_TIME;
float finishTimes[MAX_FIGHTERS] = {};
float timer = 0;
int round = 0;
int winner = -1;
int wins[MAX_FIGHTERS] = {};
int colors[MAX_FIGHTERS] = {0, 1, 2, 3};
// Online, which lobby member plays each fighter (-1 for none) and their name, kept with the game so every machine
// (and a player joining) agrees who's who as fighters come and go.
int owners[MAX_FIGHTERS] = {-1, -1, -1, -1};
char names[MAX_FIGHTERS][PLAYER_NAME_MAX + 1] = {};
bool isReady[MAX_FIGHTERS] = {};
// Online, in the lobby's sandbox: each fighter's queued game, which queue's area it's standing in (-1 for none),
// whether it's readied up there, and whether it's away playing a queued game (faded, and left alone meanwhile).
// In a queued game, whether each fighter has left it (out for good), and whether that was before it was over.
Queue queues[MAX_FIGHTERS];
int queuedIn[MAX_FIGHTERS] = {-1, -1, -1, -1};
bool isQueueReady[MAX_FIGHTERS] = {};
bool isAway[MAX_FIGHTERS] = {};
bool isGone[MAX_FIGHTERS] = {};
bool isForfeited[MAX_FIGHTERS] = {};
// After a queued game, how many ticks everyone's been set to play it again for (see Session).
int againTicks = 0;
bool isTrinketPick = false;
bool isWeaponPick = false;
Fighter sandbox;
void update(Game& game);
void start(Game& game);
void colors_assign(Game& game);
void sandbox_return(Game& game);
void sandbox_join(Game& game, int device);
void sandbox_leave(Game& game, int fighter);
bool is_racing();
float health_get();
bool is_final_round(Game& game);
float time_limit_get();
void queue_open(Game& game, int fighter, const Rules& rules);
int queue_members_get(Game& game, int queuer, int* members);
bool is_queue_full(Game& game, int queuer);
int queue_startable_get(Game& game);
bool is_queue_set(Game& game, int queuer);
void queue_slots_shift(int from, int count);
int humans_left_get(Game& game);
private:
void round_start(Game& game, bool isFirst);
void fight_start(Game& game);
void edit_take(Game& game, Fighter& fighter);
void mirror_copy(Game& game, bool isTrinket);
void deathmatch_update(Game& game);
void race_update(Game& game);
void queues_update(Game& game);
void leaves_take(Game& game);
};