Sim holds one whole game (tick, snapshots, checksum) and talks to the screen only through Cues, which the client's Scene plays and the server ignores. All drawing moved to scene.cpp. Match takes this screen's wishes as request flags instead of reading the menu, input and settings. The sim library builds without SDL and the server links it, ready to run games itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
236 lines
8.6 KiB
C++
236 lines
8.6 KiB
C++
#pragma once
|
|
|
|
#include "fighter.h"
|
|
#include "protocol.h"
|
|
|
|
struct Sim;
|
|
|
|
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;
|
|
// Offline, what this screen asks of the next tick: the menu's Play, leaving the results for the sandbox, and the
|
|
// colour its player picked. Online, members' wishes go through the session instead.
|
|
int colorWanted = COLOR_UNASSIGNED;
|
|
bool isOnline = false;
|
|
bool isPlayRequested = false;
|
|
bool isSandboxRequested = false;
|
|
Fighter sandbox;
|
|
|
|
void update(Sim& sim);
|
|
void start(Sim& sim);
|
|
void colors_assign(Sim& sim);
|
|
void sandbox_return(Sim& sim);
|
|
int sandbox_join(Sim& sim);
|
|
void sandbox_leave(Sim& sim, int fighter);
|
|
bool is_racing();
|
|
float health_get();
|
|
bool is_final_round(Sim& sim);
|
|
float time_limit_get();
|
|
void queue_open(Sim& sim, int fighter, const Rules& rules);
|
|
int queue_members_get(Sim& sim, int queuer, int* members);
|
|
bool is_queue_full(Sim& sim, int queuer);
|
|
int queue_startable_get(Sim& sim);
|
|
bool is_queue_set(Sim& sim, int queuer);
|
|
void queue_slots_shift(int from, int count);
|
|
int humans_left_get(Sim& sim);
|
|
|
|
private:
|
|
void round_start(Sim& sim, bool isFirst);
|
|
void fight_start(Sim& sim);
|
|
void edit_take(Sim& sim, Fighter& fighter);
|
|
void mirror_copy(Sim& sim, bool isTrinket);
|
|
void deathmatch_update(Sim& sim);
|
|
void race_update(Sim& sim);
|
|
void queues_update(Sim& sim);
|
|
void leaves_take(Sim& sim);
|
|
};
|