Files
brandling-safari/client/World.h
Gabe 238cd9c6c3 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
2026-10-02 15:20:27 -05:00

194 lines
6.5 KiB
C++

#pragma once
// Client view of a safari zone: builds the HD-2D diorama from the shared
// Zone layout, predicts the local player, interpolates every networked
// entity, and turns server events into effects (grass bursts, splashes,
// capsule throws, fishing bobbers).
#include "CharacterSprite.h"
#include "Common.h"
#include "Movement.h"
#include "NetClient.h"
#include "Roster.h"
#include "Zone.h"
#include "hd2d/Billboard.h"
#include "hd2d/Camera.h"
#include "hd2d/DayCycle.h"
#include "hd2d/GroundOcclusion.h"
#include "hd2d/Model.h"
#include "hd2d/Particles.h"
#include "hd2d/Scene.h"
#include "hd2d/SpriteAtlas.h"
#include "hd2d/Terrain.h"
#include "hd2d/Water.h"
#include "hd2d/Weather.h"
#include <deque>
#include <map>
#include <memory>
#include <unordered_map>
#include <vector>
namespace bs {
struct FrameInput {
glm::vec2 move{0.0f}; // camera-relative: x = screen right, y = screen up
bool run = false;
};
class World {
public:
bool init(); // static assets (models, textures, character art, roster); once per process
bool buildZone(const std::string& id, uint32_t seed);
const Zone& zone() const { return zone_; }
bool zoneBuilt() const { return built_; }
// Online: consume snapshots/events and predict the local player.
void update(float dt, float time, const FrameInput& in, NetClient& net, const hd2d::Camera& cam);
// Offline backdrop (title screen): just animate the scene at a fixed hour.
void updateBackdrop(float dt, float time, float hour, Weather w);
void reset(); // forget all entities (on disconnect)
// Gameplay queries for the HUD / input handling.
glm::vec3 localPos() const { return localRender_; }
float localYaw() const { return predicted_.yaw; }
bool localSpawned() const { return localSpawned_; }
uint32_t targetCreature() const { return target_; }
uint32_t nearbyNpc() const; // Henry when in talking range, else 0
bool canFish(glm::vec2& bobber) const;
bool lineOut() const; // the local player's fishing line is in the water
bool biting() const { return biteTimer_ > 0.0f; }
float hour() const { return hour_; }
Weather weatherState() const { return weatherState_; }
// Character preview / creator.
const CharacterArt& characterArt() const { return charArt_; }
const CharacterManifest& manifest() const { return manifest_; }
const Roster& roster() const { return roster_; }
// Screen-space labels (players, Henry, the targeted creature).
struct Label {
glm::vec3 world;
std::string text;
uint32_t accent;
bool chat = false;
uint32_t entity = 0; // players: App fills the name from NetClient::players
};
std::vector<Label> labels() const;
void showChatBubble(uint32_t entity, const std::string& text);
hd2d::Scene scene;
hd2d::Weather weather;
hd2d::DayCycle day;
private:
struct Snap {
float tick;
EntityState s;
};
struct Visual {
uint32_t id = 0;
EntityKind kind = EntityKind::Wild;
uint16_t species = 0;
uint32_t owner = 0;
std::deque<Snap> history;
glm::vec3 pos{0.0f}, lastPos{0.0f};
float yaw = 0.0f;
Anim anim = Anim::Idle;
float walkPhase = 0.0f, speed = 0.0f;
float flip = 0.0f, flipTarget = 0.0f, turnAnim = 0.0f; // paper-turn when flipping
float born = -1.0f, lastSeen = 0.0f, flash = 0.0f, hide = 0.0f;
int atlasSlot = -1;
std::string chat;
float chatAge = 1e9f;
};
struct PlayerArt {
Appearance look;
std::unique_ptr<hd2d::BillboardBatch> batch;
};
struct Capsule {
uint32_t thrower = 0, target = 0;
glm::vec3 from, to;
float start = 0.0f, flight = 0.5f;
int result = -1; // -1 pending, 0 fail, 1 success
float resultAt = 0.0f;
};
struct Bobber {
uint32_t player = 0;
glm::vec3 pos;
float bite = -1.0f; // time of the last bite
};
void buildTerrain();
void buildProps();
void buildDecor();
void applySnapshots(NetClient& net);
void handleEvent(const GameEvent& e, NetClient& net);
void predict(float dt, const FrameInput& in, NetClient& net, const hd2d::Camera& cam);
void sampleEntities(float dt);
void updateDayWeather(float dt);
void updateSprites(float dt, const hd2d::Camera& cam, NetClient* net);
void updateLights();
int creatureSlot(uint16_t species);
hd2d::BillboardBatch* playerBatch(uint32_t entity, const NetClient& net);
void drawCharacter(hd2d::BillboardBatch& batch, glm::vec3 pos, float yaw, bool moving, bool running,
float phase, const hd2d::Camera& cam, int anchorRow, float alpha = 1.0f);
float groundAt(glm::vec2 p) const { return zone_.groundHeight(p); }
// static assets
hd2d::ModelLibrary scenery_, safari_;
CharacterManifest manifest_;
CharacterArt charArt_;
Roster roster_;
hd2d::Image henryImage_;
bool assetsLoaded_ = false;
// zone
Zone zone_;
bool built_ = false;
hd2d::Terrain terrain_;
hd2d::Water water_;
std::unique_ptr<hd2d::ParticleSystem> particles_;
hd2d::GroundOcclusion groundAO_;
std::map<std::string, std::unique_ptr<hd2d::ModelBatch>> models_;
hd2d::BillboardBatch tallGrass_, tufts_, flowers_;
std::vector<hd2d::PointLight> lampLights_;
// entities
std::unordered_map<uint32_t, Visual> visuals_;
std::unordered_map<uint32_t, PlayerArt> playerArt_;
hd2d::BillboardBatch henry_;
hd2d::SpriteAtlas creatureAtlas_;
hd2d::BillboardBatch creatures_;
std::unordered_map<uint16_t, int> speciesSlot_;
std::unordered_map<int, float> slotUsed_; // LRU stamp
hd2d::SpriteAtlas fxAtlas_;
hd2d::BillboardBatch fx_;
int fxCapsule_ = -1, fxBobber_ = -1, fxDot_ = -1;
std::vector<Capsule> capsules_;
std::vector<Bobber> bobbers_;
// local prediction
uint32_t localId_ = 0;
bool localSpawned_ = false;
MoveState predicted_, prevPredicted_;
std::deque<InputCmd> pending_;
uint32_t inputSeq_ = 0;
float tickAccum_ = 0.0f;
glm::vec2 correction_{0.0f};
glm::vec3 localRender_{0.0f};
float localPhase_ = 0.0f;
Anim localServerAnim_ = Anim::Idle;
float serverTick_ = 0.0f;
bool haveTick_ = false;
uint32_t target_ = 0;
float biteTimer_ = 0.0f;
float stepTimer_ = 0.0f;
float time_ = 0.0f;
float hour_ = 12.0f, hourShown_ = -1.0f;
Weather weatherState_ = Weather::Clear;
};
} // namespace bs