#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 #include #include #include #include #include #include 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 creatures; std::set 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 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 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& 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> players_; // by peer std::map 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