- 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
220 lines
7.3 KiB
C++
220 lines
7.3 KiB
C++
#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
|