Share online membership and game setup with the server
Profile, member join/leave and building a fresh game move into the sim library, and the packet reader and writer into protocol.h, so the server can run lobbies exactly as clients do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
465f75f6d7
commit
9613e85706
7 files changed
+181
-105
No files matched your search
@@ -1,6 +1,7 @@
|
|||||||
#include "match.h"
|
#include "match.h"
|
||||||
#include "sim.h"
|
#include "sim.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
constexpr Burst FINISH_BURST = {.gravity = 200, .life = 1, .size = 3, .speed = 240, .count = 40};
|
constexpr Burst FINISH_BURST = {.gravity = 200, .life = 1, .size = 3, .speed = 240, .count = 40};
|
||||||
constexpr float RACE_GRACE = 15;
|
constexpr float RACE_GRACE = 15;
|
||||||
@@ -746,3 +747,80 @@ void Match::leaves_take(Sim& sim)
|
|||||||
std::fill(std::begin(isReady), std::end(isReady), false);
|
std::fill(std::begin(isReady), std::end(isReady), false);
|
||||||
sim.cues->sound_play(SOUND_FINISH);
|
sim.cues->sound_play(SOUND_FINISH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A fighter's loadout, slots and levels as a profile, to bring it back as it was (a name and colour are added by whoever
|
||||||
|
// knows them).
|
||||||
|
Profile profile_get(Fighter& fighter)
|
||||||
|
{
|
||||||
|
Profile profile;
|
||||||
|
profile.weaponSlots = fighter.weaponSlots;
|
||||||
|
profile.trinketSlots = fighter.trinketSlots;
|
||||||
|
std::copy(std::begin(fighter.weaponLoadout), std::end(fighter.weaponLoadout), profile.weaponLoadout);
|
||||||
|
std::copy(std::begin(fighter.trinketLoadout), std::end(fighter.trinketLoadout), profile.trinketLoadout);
|
||||||
|
std::copy(std::begin(fighter.weaponSlotLevels), std::end(fighter.weaponSlotLevels), profile.weaponSlotLevels);
|
||||||
|
std::copy(std::begin(fighter.trinketSlotLevels), std::end(fighter.trinketSlotLevels), profile.trinketSlotLevels);
|
||||||
|
std::copy(std::begin(fighter.weaponItemLevels), std::end(fighter.weaponItemLevels), profile.weaponItemLevels);
|
||||||
|
std::copy(std::begin(fighter.trinketItemLevels), std::end(fighter.trinketItemLevels), profile.trinketItemLevels);
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Online, a member's fighter appears, set up from their profile, in the colour they asked for if it's free. Returns
|
||||||
|
// it, or -1 when the game is full.
|
||||||
|
int Match::member_join(Sim& sim, int member, const Profile& profile)
|
||||||
|
{
|
||||||
|
if (sim.fighterCount >= MAX_FIGHTERS)
|
||||||
|
return -1;
|
||||||
|
int index = sim.fighterCount++;
|
||||||
|
Fighter& fighter = sim.fighters[index];
|
||||||
|
fighter = Fighter{};
|
||||||
|
fighter.init(sim, index);
|
||||||
|
fighter.slots_set(std::clamp(profile.weaponSlots, 1, SLOT_MAX), std::clamp(profile.trinketSlots, 1, SLOT_MAX));
|
||||||
|
std::copy(std::begin(profile.weaponLoadout), std::end(profile.weaponLoadout), fighter.weaponLoadout);
|
||||||
|
std::copy(std::begin(profile.trinketLoadout), std::end(profile.trinketLoadout), fighter.trinketLoadout);
|
||||||
|
std::copy(std::begin(profile.weaponSlotLevels), std::end(profile.weaponSlotLevels), fighter.weaponSlotLevels);
|
||||||
|
std::copy(std::begin(profile.trinketSlotLevels), std::end(profile.trinketSlotLevels), fighter.trinketSlotLevels);
|
||||||
|
std::copy(std::begin(profile.weaponItemLevels), std::end(profile.weaponItemLevels), fighter.weaponItemLevels);
|
||||||
|
std::copy(std::begin(profile.trinketItemLevels), std::end(profile.trinketItemLevels), fighter.trinketItemLevels);
|
||||||
|
owners[index] = member;
|
||||||
|
std::memcpy(names[index], profile.name, sizeof(names[index]));
|
||||||
|
names[index][PLAYER_NAME_MAX] = '\0';
|
||||||
|
bool isTaken[COLOR_COUNT] = {};
|
||||||
|
for (int i = 0; i < index; i++)
|
||||||
|
isTaken[colors[i]] = true;
|
||||||
|
bool isFree = profile.color >= 0 && profile.color < COLOR_COUNT && !isTaken[profile.color];
|
||||||
|
colors[index] = isFree ? profile.color : (std::find(isTaken, isTaken + COLOR_COUNT, false) - isTaken) % COLOR_COUNT;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Online, a member's fighter goes with its attacks, and the fighters after it move down a place.
|
||||||
|
void Match::member_leave(Sim& sim, int member)
|
||||||
|
{
|
||||||
|
int index = fighter_find(sim, member);
|
||||||
|
if (index < 0)
|
||||||
|
return;
|
||||||
|
std::vector<Attack>& attacks = sim.weapons.attacks;
|
||||||
|
std::erase_if(attacks, [&](Attack& attack) { return attack.owner == index; });
|
||||||
|
for (Attack& attack : attacks)
|
||||||
|
attack.owner -= attack.owner > index;
|
||||||
|
for (int i = index; i + 1 < sim.fighterCount; i++)
|
||||||
|
{
|
||||||
|
sim.fighters[i] = sim.fighters[i + 1];
|
||||||
|
sim.fighters[i].index = i;
|
||||||
|
sim.bot.states[i] = sim.bot.states[i + 1];
|
||||||
|
owners[i] = owners[i + 1];
|
||||||
|
colors[i] = colors[i + 1];
|
||||||
|
std::memcpy(names[i], names[i + 1], sizeof(names[i]));
|
||||||
|
}
|
||||||
|
queue_slots_shift(index, sim.fighterCount);
|
||||||
|
sim.fighterCount--;
|
||||||
|
owners[sim.fighterCount] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Which fighter a member plays, or -1 for none.
|
||||||
|
int Match::fighter_find(Sim& sim, int member)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < sim.fighterCount; i++)
|
||||||
|
if (owners[i] == member)
|
||||||
|
return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
+20
@@ -142,8 +142,25 @@ inline int pick_encode(bool isTrinket, int slot, int item)
|
|||||||
{.type = EDIT_ITEM, .isTrinket = isTrinket, .index = slot, .value = item < 0 ? EDIT_EMPTY : item});
|
{.type = EDIT_ITEM, .isTrinket = isTrinket, .index = slot, .value = item < 0 ? EDIT_EMPTY : item});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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] = {};
|
||||||
|
};
|
||||||
|
|
||||||
// The default rules for a mode: a best-of-three deathmatch for two, or a three minute race for four.
|
// 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_get(int mode);
|
||||||
|
Profile profile_get(Fighter& fighter);
|
||||||
Rules rules_clean(Rules rules);
|
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
|
// Online, a game queued in the lobby's sandbox by one of its fighters: its rules, and the area on the ground where
|
||||||
@@ -221,6 +238,9 @@ public:
|
|||||||
int queue_startable_get(Sim& sim);
|
int queue_startable_get(Sim& sim);
|
||||||
bool is_queue_set(Sim& sim, int queuer);
|
bool is_queue_set(Sim& sim, int queuer);
|
||||||
void queue_slots_shift(int from, int count);
|
void queue_slots_shift(int from, int count);
|
||||||
|
int member_join(Sim& sim, int member, const Profile& profile);
|
||||||
|
void member_leave(Sim& sim, int member);
|
||||||
|
int fighter_find(Sim& sim, int member);
|
||||||
int humans_left_get(Sim& sim);
|
int humans_left_get(Sim& sim);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -22,8 +22,10 @@
|
|||||||
//
|
//
|
||||||
// A signal's payload is either a description {sdp, kind} or a candidate {candidate, mid}.
|
// A signal's payload is either a description {sdp, kind} or a candidate {candidate, mid}.
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
constexpr int PROTOCOL_VERSION = 2;
|
constexpr int PROTOCOL_VERSION = 2;
|
||||||
constexpr int LOBBY_MEMBERS_MAX = 4;
|
constexpr int LOBBY_MEMBERS_MAX = 4;
|
||||||
@@ -36,6 +38,35 @@ constexpr int KICK_REASON_MAX = 60;
|
|||||||
constexpr const char SERVER_URL_DEFAULT[] = "wss://shweetz.net/smack-a-brother/ws";
|
constexpr const char SERVER_URL_DEFAULT[] = "wss://shweetz.net/smack-a-brother/ws";
|
||||||
constexpr int SERVER_URL_LENGTH = 128;
|
constexpr int SERVER_URL_LENGTH = 128;
|
||||||
|
|
||||||
|
// Packets are plain values one after another, as they sit in memory (every platform agrees on these layouts).
|
||||||
|
struct PacketWriter
|
||||||
|
{
|
||||||
|
std::string bytes;
|
||||||
|
|
||||||
|
template <class T> PacketWriter& put(const T& value)
|
||||||
|
{
|
||||||
|
static_assert(std::is_trivially_copyable_v<T>);
|
||||||
|
bytes.append(reinterpret_cast<const char*>(&value), sizeof(T));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PacketReader
|
||||||
|
{
|
||||||
|
const std::string& bytes;
|
||||||
|
size_t at = 0;
|
||||||
|
|
||||||
|
template <class T> bool get(T& value)
|
||||||
|
{
|
||||||
|
static_assert(std::is_trivially_copyable_v<T>);
|
||||||
|
if (at + sizeof(T) > bytes.size())
|
||||||
|
return false;
|
||||||
|
std::memcpy(static_cast<void*>(&value), bytes.data() + at, sizeof(T));
|
||||||
|
at += sizeof(T);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Text from elsewhere (another player, or the server) made safe to show: only printable ASCII (what the game's font
|
// Text from elsewhere (another player, or the server) made safe to show: only printable ASCII (what the game's font
|
||||||
// has; anything else, emoji included, is dropped), cut to max characters, with spaces at its ends trimmed.
|
// has; anything else, emoji included, is dropped), cut to max characters, with spaces at its ends trimmed.
|
||||||
inline std::string text_clean(std::string_view raw, size_t max)
|
inline std::string text_clean(std::string_view raw, size_t max)
|
||||||
|
|||||||
+4
-89
@@ -35,35 +35,6 @@ enum PacketType : Uint8
|
|||||||
PACKET_QUEUE
|
PACKET_QUEUE
|
||||||
};
|
};
|
||||||
|
|
||||||
// Packets are plain values one after another, as they sit in memory (every platform agrees on these layouts).
|
|
||||||
struct PacketWriter
|
|
||||||
{
|
|
||||||
std::string bytes;
|
|
||||||
|
|
||||||
template <class T> PacketWriter& put(const T& value)
|
|
||||||
{
|
|
||||||
static_assert(std::is_trivially_copyable_v<T>);
|
|
||||||
bytes.append(reinterpret_cast<const char*>(&value), sizeof(T));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PacketReader
|
|
||||||
{
|
|
||||||
const std::string& bytes;
|
|
||||||
size_t at = 0;
|
|
||||||
|
|
||||||
template <class T> bool get(T& value)
|
|
||||||
{
|
|
||||||
static_assert(std::is_trivially_copyable_v<T>);
|
|
||||||
if (at + sizeof(T) > bytes.size())
|
|
||||||
return false;
|
|
||||||
std::memcpy(static_cast<void*>(&value), bytes.data() + at, sizeof(T));
|
|
||||||
at += sizeof(T);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Field by field, since the padding between them is whatever was in memory.
|
// Field by field, since the padding between them is whatever was in memory.
|
||||||
static bool controls_same(const Controls& a, const Controls& b)
|
static bool controls_same(const Controls& a, const Controls& b)
|
||||||
{
|
{
|
||||||
@@ -125,18 +96,9 @@ Controls InputLog::guess(int at) const
|
|||||||
// This screen's player as they join: name, colour, and the sandbox fighter they're playing now.
|
// This screen's player as they join: name, colour, and the sandbox fighter they're playing now.
|
||||||
Profile Session::profile_get(Game& game)
|
Profile Session::profile_get(Game& game)
|
||||||
{
|
{
|
||||||
Profile profile;
|
Profile profile = ::profile_get(game.local_get());
|
||||||
Fighter& fighter = game.local_get();
|
|
||||||
SDL_strlcpy(profile.name, game.settings.name, sizeof(profile.name));
|
SDL_strlcpy(profile.name, game.settings.name, sizeof(profile.name));
|
||||||
profile.color = game.settings.color_get();
|
profile.color = game.settings.color_get();
|
||||||
profile.weaponSlots = fighter.weaponSlots;
|
|
||||||
profile.trinketSlots = fighter.trinketSlots;
|
|
||||||
std::copy(std::begin(fighter.weaponLoadout), std::end(fighter.weaponLoadout), profile.weaponLoadout);
|
|
||||||
std::copy(std::begin(fighter.trinketLoadout), std::end(fighter.trinketLoadout), profile.trinketLoadout);
|
|
||||||
std::copy(std::begin(fighter.weaponSlotLevels), std::end(fighter.weaponSlotLevels), profile.weaponSlotLevels);
|
|
||||||
std::copy(std::begin(fighter.trinketSlotLevels), std::end(fighter.trinketSlotLevels), profile.trinketSlotLevels);
|
|
||||||
std::copy(std::begin(fighter.weaponItemLevels), std::end(fighter.weaponItemLevels), profile.weaponItemLevels);
|
|
||||||
std::copy(std::begin(fighter.trinketItemLevels), std::end(fighter.trinketItemLevels), profile.trinketItemLevels);
|
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -449,57 +411,10 @@ void Session::events_apply(Game& game, int at)
|
|||||||
{
|
{
|
||||||
if (event.tick != at)
|
if (event.tick != at)
|
||||||
continue;
|
continue;
|
||||||
if (event.type == EVENT_JOIN && game.sim.fighterCount < MAX_FIGHTERS)
|
if (event.type == EVENT_JOIN)
|
||||||
{
|
match.member_join(game.sim, event.member, event.profile);
|
||||||
int index = game.sim.fighterCount++;
|
|
||||||
Fighter& fighter = game.sim.fighters[index];
|
|
||||||
fighter = Fighter{};
|
|
||||||
fighter.init(game.sim, index);
|
|
||||||
const Profile& profile = event.profile;
|
|
||||||
fighter.slots_set(std::clamp(profile.weaponSlots, 1, SLOT_MAX),
|
|
||||||
std::clamp(profile.trinketSlots, 1, SLOT_MAX));
|
|
||||||
std::copy(std::begin(profile.weaponLoadout), std::end(profile.weaponLoadout), fighter.weaponLoadout);
|
|
||||||
std::copy(std::begin(profile.trinketLoadout), std::end(profile.trinketLoadout), fighter.trinketLoadout);
|
|
||||||
std::copy(std::begin(profile.weaponSlotLevels), std::end(profile.weaponSlotLevels),
|
|
||||||
fighter.weaponSlotLevels);
|
|
||||||
std::copy(std::begin(profile.trinketSlotLevels), std::end(profile.trinketSlotLevels),
|
|
||||||
fighter.trinketSlotLevels);
|
|
||||||
std::copy(std::begin(profile.weaponItemLevels), std::end(profile.weaponItemLevels),
|
|
||||||
fighter.weaponItemLevels);
|
|
||||||
std::copy(std::begin(profile.trinketItemLevels), std::end(profile.trinketItemLevels),
|
|
||||||
fighter.trinketItemLevels);
|
|
||||||
match.owners[index] = event.member;
|
|
||||||
SDL_strlcpy(match.names[index], profile.name, sizeof(match.names[index]));
|
|
||||||
bool isTaken[COLOR_COUNT] = {};
|
|
||||||
for (int i = 0; i < index; i++)
|
|
||||||
isTaken[match.colors[i]] = true;
|
|
||||||
int color = profile.color >= 0 && profile.color < COLOR_COUNT && !isTaken[profile.color]
|
|
||||||
? profile.color
|
|
||||||
: std::find(isTaken, isTaken + COLOR_COUNT, false) - isTaken;
|
|
||||||
match.colors[index] = color % COLOR_COUNT;
|
|
||||||
}
|
|
||||||
if (event.type == EVENT_LEAVE)
|
if (event.type == EVENT_LEAVE)
|
||||||
{
|
match.member_leave(game.sim, event.member);
|
||||||
int index = std::find(match.owners, match.owners + game.sim.fighterCount, event.member) - match.owners;
|
|
||||||
if (index >= game.sim.fighterCount)
|
|
||||||
continue;
|
|
||||||
std::vector<Attack>& attacks = game.sim.weapons.attacks;
|
|
||||||
std::erase_if(attacks, [&](Attack& attack) { return attack.owner == index; });
|
|
||||||
for (Attack& attack : attacks)
|
|
||||||
attack.owner -= attack.owner > index;
|
|
||||||
for (int i = index; i + 1 < game.sim.fighterCount; i++)
|
|
||||||
{
|
|
||||||
game.sim.fighters[i] = game.sim.fighters[i + 1];
|
|
||||||
game.sim.fighters[i].index = i;
|
|
||||||
game.sim.bot.states[i] = game.sim.bot.states[i + 1];
|
|
||||||
match.owners[i] = match.owners[i + 1];
|
|
||||||
match.colors[i] = match.colors[i + 1];
|
|
||||||
SDL_memcpy(match.names[i], match.names[i + 1], sizeof(match.names[i]));
|
|
||||||
}
|
|
||||||
match.queue_slots_shift(index, game.sim.fighterCount);
|
|
||||||
game.sim.fighterCount--;
|
|
||||||
match.owners[game.sim.fighterCount] = -1;
|
|
||||||
}
|
|
||||||
if (event.type == EVENT_QUEUE || event.type == EVENT_UNQUEUE)
|
if (event.type == EVENT_QUEUE || event.type == EVENT_UNQUEUE)
|
||||||
{
|
{
|
||||||
int index = std::find(match.owners, match.owners + game.sim.fighterCount, event.member) - match.owners;
|
int index = std::find(match.owners, match.owners + game.sim.fighterCount, event.member) - match.owners;
|
||||||
|
|||||||
@@ -18,22 +18,6 @@ constexpr int SNAPSHOT_RING = 64;
|
|||||||
// A member's connection has no ping to show (it's this screen's own, hosting).
|
// A member's connection has no ping to show (it's this screen's own, hosting).
|
||||||
constexpr float PING_NONE = -2;
|
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
|
enum SessionEventType
|
||||||
{
|
{
|
||||||
EVENT_JOIN,
|
EVENT_JOIN,
|
||||||
|
|||||||
+47
@@ -26,6 +26,53 @@ void Sim::tick()
|
|||||||
world.update(*this);
|
world.update(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A fresh online game for some of a lobby's fighters (by index) to play by the rules, bots filling the rest when the
|
||||||
|
// rules say to. Their members, names and colours come along, and it starts from the seed, so everyone builds the same
|
||||||
|
// one. The bots keep their difficulty.
|
||||||
|
void Sim::game_start(Match& lobby, const int* members, int count, const Rules& rules, Uint32 seed)
|
||||||
|
{
|
||||||
|
int owners[MAX_FIGHTERS];
|
||||||
|
int colors[MAX_FIGHTERS];
|
||||||
|
char names[MAX_FIGHTERS][PLAYER_NAME_MAX + 1];
|
||||||
|
for (int k = 0; k < count; k++)
|
||||||
|
{
|
||||||
|
owners[k] = lobby.owners[members[k]];
|
||||||
|
colors[k] = lobby.colors[members[k]];
|
||||||
|
std::memcpy(names[k], lobby.names[members[k]], sizeof(names[k]));
|
||||||
|
}
|
||||||
|
int players = rules.isBotFill ? std::max(rules.players, count) : count;
|
||||||
|
int level = bot.level;
|
||||||
|
bot = Bot{};
|
||||||
|
bot.level = level;
|
||||||
|
weapons = Weapons{};
|
||||||
|
world = World{};
|
||||||
|
match = Match{};
|
||||||
|
match.rules = rules;
|
||||||
|
match.rules.players = players;
|
||||||
|
match.isOnline = true;
|
||||||
|
rng.state = seed ? seed : RNG_SEED;
|
||||||
|
fighterCount = players;
|
||||||
|
|
||||||
|
bool isTaken[COLOR_COUNT] = {};
|
||||||
|
for (int k = 0; k < count; k++)
|
||||||
|
isTaken[colors[k] % COLOR_COUNT] = true;
|
||||||
|
for (int k = 0; k < players; k++)
|
||||||
|
{
|
||||||
|
fighters[k] = Fighter{};
|
||||||
|
fighters[k].init(*this, k);
|
||||||
|
match.owners[k] = k < count ? owners[k] : -1;
|
||||||
|
if (k < count)
|
||||||
|
{
|
||||||
|
match.colors[k] = colors[k];
|
||||||
|
std::memcpy(match.names[k], names[k], sizeof(match.names[k]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match.colors[k] = std::find(isTaken, isTaken + COLOR_COUNT, false) - isTaken;
|
||||||
|
isTaken[match.colors[k]] = true;
|
||||||
|
}
|
||||||
|
match.start(*this);
|
||||||
|
}
|
||||||
|
|
||||||
// Moves a level by a step within the levels there are, cueing whether it went up or down.
|
// Moves a level by a step within the levels there are, cueing whether it went up or down.
|
||||||
void level_step(Sim& sim, int& level, int step)
|
void level_step(Sim& sim, int& level, int step)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ struct Sim
|
|||||||
|
|
||||||
void init(Uint32 seed);
|
void init(Uint32 seed);
|
||||||
void tick();
|
void tick();
|
||||||
|
void game_start(Match& lobby, const int* members, int count, const Rules& rules, Uint32 seed);
|
||||||
Snapshot snapshot_save();
|
Snapshot snapshot_save();
|
||||||
void snapshot_load(Snapshot& snapshot);
|
void snapshot_load(Snapshot& snapshot);
|
||||||
Uint32 checksum_get();
|
Uint32 checksum_get();
|
||||||
|
|||||||
Reference in new issue
Block a user