Brandling Safari: LAN safari-zone creature catching on the HD-2D engine
- Authoritative 30 Hz server (ENet, SQLite): accounts, characters, wild AI, tall grass / water / wandering spawns, fishing, captures, follower chains, clock + weather, LAN beacon - Client: title / join / character creator / play screens, prediction + reconciliation, interpolated players and parties, HD-2D UI (dialogue with Professor Henry, HUD, collection) - Shared deterministic zone generator, movement, protocol, roster, appearance - Paper-doll character art pipeline (PixelLab base walk + per-item animation edits) - Brandling roster from the company list (logo colors, jev types), Blender safari props - Headless test bot
This commit is contained in:
219
server/Game.h
Normal file
219
server/Game.h
Normal file
@@ -0,0 +1,219 @@
|
||||
#pragma once
|
||||
// Authoritative game simulation: sessions/accounts, player movement, wild
|
||||
// creatures, fishing, catching, followers, Professor Henry, chat, world clock.
|
||||
|
||||
#include "Appearance.h"
|
||||
#include "Database.h"
|
||||
#include "Movement.h"
|
||||
#include "Net.h"
|
||||
#include "Protocol.h"
|
||||
#include "Rng.h"
|
||||
#include "Roster.h"
|
||||
#include "Spawns.h"
|
||||
#include "Zone.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace bs {
|
||||
|
||||
struct GameConfig {
|
||||
std::string serverName = "Brandling Safari";
|
||||
uint32_t seed = 1;
|
||||
std::string zoneId = "meadow";
|
||||
float dayMinutes = 30.0f;
|
||||
float hour = -1.0f; // < 0: resume the saved clock (default 9:00)
|
||||
int weather = -1; // < 0: resume the saved weather
|
||||
};
|
||||
|
||||
// Professor Henry always has this entity id; dynamic entities start above it.
|
||||
constexpr uint32_t kHenryEntityId = 1;
|
||||
|
||||
enum class LineState { None, Waiting, Biting };
|
||||
|
||||
struct Player {
|
||||
uint32_t peer = 0;
|
||||
uint32_t entity = 0; // 0 until the character is spawned
|
||||
bool helloDone = false;
|
||||
bool hasCharacter = false;
|
||||
bool spawned = false;
|
||||
PlayerRow row; // persistent fields (id, token, name, look, capsules, intro)
|
||||
MoveState move;
|
||||
uint32_t lastSeq = 0;
|
||||
float inputBudget = 2.0f;
|
||||
int idleTicks = 0; // ticks without an applied input (stops the walk anim)
|
||||
|
||||
std::vector<OwnedCreature> creatures;
|
||||
std::set<uint16_t> seen, caught;
|
||||
|
||||
float throwTimer = 0.0f;
|
||||
LineState line = LineState::None;
|
||||
glm::vec2 bobber{0.0f}, castPos{0.0f};
|
||||
float lineTimer = 0.0f, fishCooldown = 0.0f;
|
||||
|
||||
float grassDist = 0.0f;
|
||||
float rustleCooldown = 0.0f; // misses shake the grass at most about once a second
|
||||
std::deque<glm::vec2> trail; // newest first
|
||||
float introTimer = -1.0f; // > 0: henry_intro dialogue pending
|
||||
bool introOffered = false; // henry_intro was sent and not answered yet
|
||||
double lastChat = -10.0;
|
||||
bool posDirty = false;
|
||||
};
|
||||
|
||||
enum class CMode { Wander, Emerge, Alert, Capturing, Flee, Hooked, Swim, Follow };
|
||||
enum class COrigin { Wanderer, Grass, Swimmer, Fished, Console, Caught };
|
||||
|
||||
struct Creature {
|
||||
uint32_t id = 0;
|
||||
uint16_t species = 0;
|
||||
EntityKind kind = EntityKind::Wild;
|
||||
COrigin origin = COrigin::Wanderer;
|
||||
CMode mode = CMode::Wander;
|
||||
Anim anim = Anim::Idle;
|
||||
glm::vec2 pos{0.0f};
|
||||
float y = 0.0f;
|
||||
float yaw = 0.0f;
|
||||
uint32_t owner = 0; // follower owner / player hooking, capturing or scaring it
|
||||
uint8_t order = 0;
|
||||
uint32_t uid = 0; // follower: owned creature uid
|
||||
bool dead = false; // removed at the end of the tick
|
||||
uint32_t emergedFor = 0; // grass spawn: player entity it popped up for (per-player cap)
|
||||
|
||||
float timer = 0.0f; // time left in the current mode / pause
|
||||
bool walking = false;
|
||||
glm::vec2 target{0.0f};
|
||||
float lifetime = 120.0f; // despawn once this runs out and nobody is within 12 units
|
||||
bool hookBonus = false; // Alert after being fished out
|
||||
int failedThrows = 0;
|
||||
std::set<uint32_t> seenBy; // player entities that already had it registered as seen
|
||||
|
||||
// capture in flight
|
||||
float captureTimer = 0.0f;
|
||||
bool captureSuccess = false;
|
||||
int wobbles = 0;
|
||||
CMode resumeMode = CMode::Wander;
|
||||
|
||||
// hook arc
|
||||
glm::vec2 arcFrom{0.0f}, arcTo{0.0f};
|
||||
float arcFromY = 0.0f;
|
||||
};
|
||||
|
||||
class Game {
|
||||
public:
|
||||
Game(net::Host& host, Database& db, Zone& zone, const Roster& roster, const CharacterManifest& manifest,
|
||||
GameConfig cfg);
|
||||
|
||||
void onConnect(uint32_t peer);
|
||||
void onDisconnect(uint32_t peer);
|
||||
void onReceive(uint32_t peer, const std::vector<uint8_t>& data);
|
||||
void tick();
|
||||
void saveAll();
|
||||
// Console command; returns false for "quit".
|
||||
bool console(const std::string& line);
|
||||
|
||||
int playerCount() const;
|
||||
const std::string& serverName() const { return cfg_.serverName; }
|
||||
|
||||
private:
|
||||
// --- sessions
|
||||
Player* byPeer(uint32_t peer);
|
||||
Player* byEntity(uint32_t entity);
|
||||
void handleHello(Player& p, Reader& r);
|
||||
void handleCreate(Player& p, Reader& r);
|
||||
void handleInput(Player& p, Reader& r);
|
||||
void handleAction(Player& p, Reader& r);
|
||||
void handleChat(Player& p, Reader& r);
|
||||
void handleDialogueDone(Player& p, Reader& r);
|
||||
void spawnPlayer(Player& p);
|
||||
void despawnPlayer(Player& p); // save + PlayerLeft + remove followers
|
||||
void reject(uint32_t peer, const std::string& reason);
|
||||
|
||||
// --- messaging
|
||||
void send(const Player& p, const Writer& w, bool reliable = true);
|
||||
void broadcast(const Writer& w, bool reliable = true);
|
||||
void sendProfile(Player& p);
|
||||
void sendEvent(const GameEvent& e);
|
||||
void sendToast(Player& p, const std::string& text);
|
||||
void sendDialogue(Player& p, const std::string& id);
|
||||
PlayerInfoMsg playerInfo(const Player& p) const;
|
||||
void sendSnapshots();
|
||||
|
||||
// --- persistence
|
||||
void savePlayer(Player& p);
|
||||
void saveWorld();
|
||||
|
||||
// --- players
|
||||
void applyInput(Player& p, const InputCmd& c);
|
||||
void updatePlayer(Player& p, float dt);
|
||||
void cancelLine(Player& p);
|
||||
void noteSeen(Player& p, uint16_t species);
|
||||
glm::vec3 pos3(glm::vec2 p) const { return {p.x, zone_.groundHeight(p), p.y}; }
|
||||
Anim playerAnim(const Player& p) const;
|
||||
|
||||
// --- creatures
|
||||
Creature& addCreature(uint16_t species, glm::vec2 pos, COrigin origin);
|
||||
void removeCreature(uint32_t id);
|
||||
void announceAppeared(const Creature& c);
|
||||
void updateCreatures(float dt);
|
||||
void updateWild(Creature& c, float dt);
|
||||
void updateFollowers(Player& p, float dt);
|
||||
void wander(Creature& c, float dt, float speed);
|
||||
bool pickWanderTarget(Creature& c);
|
||||
void faceNearestPlayer(Creature& c);
|
||||
float nearestPlayerDist(glm::vec2 p, Player** who = nullptr);
|
||||
int emergedCount(const Player& p) const;
|
||||
void maintainPopulation(float dt);
|
||||
bool spawnWanderer();
|
||||
bool spawnSwimmer(bool announce);
|
||||
void grassEncounters(Player& p);
|
||||
void resolveCapture(Creature& c);
|
||||
void syncFollowers(Player& p);
|
||||
Creature* spawnForConsole(uint16_t species);
|
||||
|
||||
// --- actions
|
||||
void actionThrow(Player& p, uint32_t target);
|
||||
void actionCast(Player& p, glm::vec2 dir);
|
||||
void actionReel(Player& p);
|
||||
void actionInteract(Player& p, uint32_t target);
|
||||
void actionToggleParty(Player& p, uint32_t uid);
|
||||
void finishIntro(Player& p);
|
||||
bool landingSpot(glm::vec2 bobber, glm::vec2 player, glm::vec2& out) const;
|
||||
|
||||
// --- world
|
||||
void updateWorld(float dt);
|
||||
void updateHenry(float dt);
|
||||
void setWeather(Weather w);
|
||||
|
||||
net::Host& host_;
|
||||
Database& db_;
|
||||
Zone& zone_;
|
||||
const Roster& roster_;
|
||||
const CharacterManifest& manifest_;
|
||||
GameConfig cfg_;
|
||||
Rng rng_;
|
||||
SpawnTables tables_;
|
||||
|
||||
std::map<uint32_t, std::unique_ptr<Player>> players_; // by peer
|
||||
std::map<uint32_t, Creature> creatures_; // wild + followers by entity id
|
||||
uint32_t nextEntity_ = kHenryEntityId + 1;
|
||||
uint32_t tick_ = 0;
|
||||
double time_ = 0.0; // seconds since start
|
||||
|
||||
float hour_ = 9.0f;
|
||||
Weather weather_ = Weather::Clear;
|
||||
float weatherTimer_ = 300.0f;
|
||||
float weatherBlend_ = 1.0f;
|
||||
float saveTimer_ = 10.0f;
|
||||
|
||||
float henryYaw_ = 0.0f;
|
||||
float wanderSpawnTimer_ = 0.0f;
|
||||
float swimmerTimer_ = 10.0f; // next surface/dive
|
||||
float swimmerRespawn_ = -1.0f; // > 0: a dived swimmer resurfaces elsewhere
|
||||
};
|
||||
|
||||
} // namespace bs
|
||||
Reference in New Issue
Block a user