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:
121
shared/Zone.h
Normal file
121
shared/Zone.h
Normal file
@@ -0,0 +1,121 @@
|
||||
#pragma once
|
||||
// Deterministic safari zone layout. The server and every client call
|
||||
// Zone::generate(id, seed) and get the exact same terrain, props, water,
|
||||
// tall grass and landmarks, so only the zone id + seed travel over the wire.
|
||||
// Everything that matters for gameplay (collision, heights, water, tall
|
||||
// grass, NPC and spawn points) lives here; purely visual decoration (grass
|
||||
// tufts, flowers, the tall-grass sprites themselves) is added by the client.
|
||||
|
||||
#include "Rng.h"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace bs {
|
||||
|
||||
// Terrain materials (client maps them to texture layers).
|
||||
enum ZoneMat : uint8_t { MatGrass = 0, MatMeadow = 1, MatPath = 2, MatSand = 3, MatStone = 4, MatRidge = 5, MatPlaza = 6 };
|
||||
|
||||
// One placed 3D model. `radius` > 0 makes it a round obstacle.
|
||||
struct ZoneProp {
|
||||
std::string model; // model name in scenery.glb / safari.glb
|
||||
glm::vec3 pos{0.0f};
|
||||
float yaw = 0.0f;
|
||||
glm::vec3 scale{1.0f};
|
||||
float tint = 1.0f;
|
||||
float radius = 0.0f;
|
||||
};
|
||||
|
||||
// Thin straight obstacle (fences, tent walls) as a capsule from a to b.
|
||||
struct ZoneWall {
|
||||
glm::vec2 a, b;
|
||||
float halfWidth = 0.15f;
|
||||
};
|
||||
|
||||
// Walkable wooden deck over water.
|
||||
struct ZoneDock {
|
||||
glm::vec2 min, max;
|
||||
float deckY = 0.05f;
|
||||
};
|
||||
|
||||
struct ZonePatch {
|
||||
glm::vec2 c;
|
||||
float r;
|
||||
};
|
||||
|
||||
class Zone {
|
||||
public:
|
||||
static constexpr float kWaterLevel = -0.2f;
|
||||
static constexpr float kLevelHeight = 0.5f;
|
||||
|
||||
// Known ids: "meadow" (the grassy safari zone). Returns false for unknown ids.
|
||||
bool generate(const std::string& id, uint32_t seed);
|
||||
|
||||
const std::string& id() const { return id_; }
|
||||
uint32_t seed() const { return seed_; }
|
||||
int width() const { return w_; }
|
||||
int depth() const { return d_; }
|
||||
bool inBounds(int x, int z) const { return x >= 0 && z >= 0 && x < w_ && z < d_; }
|
||||
int level(int x, int z) const;
|
||||
uint8_t material(int x, int z) const;
|
||||
bool tallGrassTile(int x, int z) const;
|
||||
|
||||
// Same rule as hd2d::Terrain::heightAt (flat tile tops).
|
||||
float terrainHeight(float x, float z) const;
|
||||
float groundHeight(glm::vec2 p) const; // dock deck or terrain
|
||||
bool onDock(glm::vec2 p) const;
|
||||
bool isWater(glm::vec2 p) const; // open water (not under a dock)
|
||||
bool isDeepWater(glm::vec2 p) const; // basin, at least 1 level below the shelf
|
||||
bool inTallGrass(glm::vec2 p) const;
|
||||
bool walkable(glm::vec2 from, glm::vec2 to, float radius = 0.28f) const;
|
||||
|
||||
// Where a line cast from `from` along `dir` lands: first open water point
|
||||
// 1.6 .. 4.5 units ahead. False when there is no water in range.
|
||||
bool fishingTarget(glm::vec2 from, glm::vec2 dir, glm::vec2& bobber) const;
|
||||
|
||||
bool randomTallGrassNear(Rng& rng, glm::vec2 c, float rmin, float rmax, glm::vec2& out) const;
|
||||
bool randomWater(Rng& rng, glm::vec2& out, bool deep = true) const;
|
||||
bool randomWaterNear(Rng& rng, glm::vec2 c, float rmax, glm::vec2& out) const;
|
||||
// Open grass / meadow for wandering creatures (away from the entrance plaza).
|
||||
bool randomWanderSpot(Rng& rng, glm::vec2& out) const;
|
||||
// True when a creature may stand / walk here (land creatures stay out of water).
|
||||
bool creatureWalkable(glm::vec2 p, bool water) const;
|
||||
|
||||
std::vector<ZoneProp> props;
|
||||
std::vector<ZoneWall> walls;
|
||||
std::vector<ZoneDock> docks;
|
||||
std::vector<ZonePatch> tallGrassPatches;
|
||||
glm::vec2 spawn{0.0f}; // where new players appear
|
||||
float spawnYaw = 0.0f;
|
||||
glm::vec2 henry{0.0f}; // Professor Henry's post
|
||||
float henryYaw = 0.0f;
|
||||
glm::vec2 plaza{0.0f}; // entrance plaza center (no wild spawns nearby)
|
||||
glm::vec2 waterMin{0.0f}, waterMax{0.0f}; // bounds of all water tiles (+ margin)
|
||||
|
||||
private:
|
||||
void buildMeadow(uint32_t seed);
|
||||
void setTile(int x, int z, int level, uint8_t mat);
|
||||
void paintPath(const std::vector<glm::vec2>& pts, float halfWidth, bool clearGrass = true);
|
||||
bool blocked(glm::vec2 p, float r) const; // overlaps a prop or wall
|
||||
bool clearForProp(glm::vec2 p, float r) const;
|
||||
void addProp(const std::string& model, glm::vec2 p, float yaw, float scale, float radius, float tint = 1.0f,
|
||||
float yOffset = 0.0f);
|
||||
void addFence(glm::vec2 a, glm::vec2 b);
|
||||
|
||||
std::string id_;
|
||||
uint32_t seed_ = 0;
|
||||
int w_ = 0, d_ = 0;
|
||||
std::vector<int> levels_;
|
||||
std::vector<uint8_t> mats_;
|
||||
std::vector<uint8_t> grass_;
|
||||
};
|
||||
|
||||
// 0 = north (-z), pi/2 = east (+x).
|
||||
inline float yawFromDir(glm::vec2 d) { return std::atan2(d.x, -d.y); }
|
||||
inline glm::vec2 dirFromYaw(float yaw) { return {std::sin(yaw), -std::cos(yaw)}; }
|
||||
|
||||
} // namespace bs
|
||||
Reference in New Issue
Block a user