- 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
194 lines
6.5 KiB
C++
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
|