Split the simulation from presentation into an SDL-free library
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>
This commit is contained in:
1 parent
2007fb7edc
commit
465f75f6d7
38 files changed
+2225
-1965
No files matched your search
+103
@@ -0,0 +1,103 @@
|
||||
#include "sim.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
constexpr Uint32 FNV_OFFSET = 2166136261u;
|
||||
constexpr Uint32 FNV_PRIME = 16777619u;
|
||||
|
||||
// Sets up a game from scratch: the sandbox world, the bot graph, fighters and the seeded generator.
|
||||
void Sim::init(Uint32 seed)
|
||||
{
|
||||
world.sandbox_load();
|
||||
bot.init(world);
|
||||
for (int i = 0; i < MAX_FIGHTERS; i++)
|
||||
fighters[i].init(*this, i);
|
||||
rng.state = seed;
|
||||
}
|
||||
|
||||
void Sim::tick()
|
||||
{
|
||||
match.update(*this);
|
||||
if (match.state != MATCH_COUNTDOWN)
|
||||
bot.update(*this);
|
||||
for (int i = 0; i < fighterCount && match.state != MATCH_COUNTDOWN; i++)
|
||||
fighters[i].update(*this);
|
||||
weapons.update(*this);
|
||||
world.update(*this);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
int next = std::clamp(level + step, 0, LEVEL_COUNT - 1);
|
||||
if (next == level)
|
||||
return;
|
||||
sim.cues->level_step(next > level);
|
||||
level = next;
|
||||
}
|
||||
|
||||
Snapshot Sim::snapshot_save()
|
||||
{
|
||||
Snapshot snapshot = {.match = match,
|
||||
.weapons = weapons,
|
||||
.world = world,
|
||||
.rng = rng,
|
||||
.fighterCount = fighterCount};
|
||||
std::copy(std::begin(fighters), std::end(fighters), snapshot.fighters);
|
||||
std::copy(std::begin(bot.states), std::end(bot.states), snapshot.bots);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
void Sim::snapshot_load(Snapshot& snapshot)
|
||||
{
|
||||
std::copy(std::begin(snapshot.fighters), std::end(snapshot.fighters), fighters);
|
||||
std::copy(std::begin(snapshot.bots), std::end(snapshot.bots), bot.states);
|
||||
fighterCount = snapshot.fighterCount;
|
||||
weapons = snapshot.weapons;
|
||||
world = snapshot.world;
|
||||
rng = snapshot.rng;
|
||||
match = snapshot.match;
|
||||
}
|
||||
|
||||
static void hash_add(Uint32& hash, float value)
|
||||
{
|
||||
Uint32 bits;
|
||||
std::memcpy(&bits, &value, sizeof(bits));
|
||||
hash = (hash ^ bits) * FNV_PRIME;
|
||||
}
|
||||
|
||||
// A fingerprint of the simulation state that matters for a desync: the fighters, every attack, the dummies, the
|
||||
// match and the random generator.
|
||||
Uint32 Sim::checksum_get()
|
||||
{
|
||||
Uint32 hash = FNV_OFFSET;
|
||||
hash_add(hash, (float)match.state + match.timer);
|
||||
for (int i = 0; i < fighterCount; i++)
|
||||
{
|
||||
Fighter& fighter = fighters[i];
|
||||
for (float value : {fighter.body.box.x, fighter.body.box.y, fighter.body.velocity.x, fighter.body.velocity.y,
|
||||
fighter.aim.x, fighter.aim.y, fighter.charge, fighter.health, fighter.hitstop})
|
||||
hash_add(hash, value);
|
||||
for (float cooldown : fighter.cooldowns)
|
||||
hash_add(hash, cooldown);
|
||||
// What the menu's edits change: loadouts, levels, slot counts, stacking and PvP.
|
||||
for (int slot = 0; slot < SLOT_MAX; slot++)
|
||||
for (int value : {fighter.weaponLoadout[slot], fighter.trinketLoadout[slot], fighter.weaponSlotLevels[slot],
|
||||
fighter.trinketSlotLevels[slot]})
|
||||
hash_add(hash, (float)value);
|
||||
for (int level : fighter.weaponItemLevels)
|
||||
hash_add(hash, (float)level);
|
||||
for (int level : fighter.trinketItemLevels)
|
||||
hash_add(hash, (float)level);
|
||||
for (int value : {fighter.weaponSlots, fighter.trinketSlots, (int)fighter.isPvp})
|
||||
hash_add(hash, (float)value);
|
||||
}
|
||||
for (Attack& attack : weapons.attacks)
|
||||
for (float value : {attack.position.x, attack.position.y, attack.velocity.x, attack.velocity.y, attack.angle,
|
||||
attack.time, (float)attack.hitsLeft})
|
||||
hash_add(hash, value);
|
||||
for (Dummy& dummy : world.dummies)
|
||||
for (float value : {dummy.flash, dummy.wobble, dummy.wobbleSpeed})
|
||||
hash_add(hash, value);
|
||||
return (hash ^ rng.state) * FNV_PRIME;
|
||||
}
|
||||
Reference in new issue
Block a user