- 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
488 lines
20 KiB
C++
488 lines
20 KiB
C++
#include "Zone.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
namespace bs {
|
|
|
|
namespace {
|
|
|
|
constexpr float kPi = 3.14159265f;
|
|
|
|
// Distance from a wobbly blob's center, normalized so the edge sits at `radius`.
|
|
float blob(glm::vec2 p, glm::vec2 c, float radius, float seed) {
|
|
glm::vec2 d = p - c;
|
|
float a = std::atan2(d.y, d.x);
|
|
float wobble = 1.0f + 0.16f * std::sin(a * 3.0f + seed) + 0.08f * std::sin(a * 5.0f + seed * 2.3f);
|
|
return glm::length(d) / (radius * wobble);
|
|
}
|
|
|
|
float ridgeLow(float x) { return 13.0f + 2.2f * std::sin(x * 0.19f) + 1.3f * std::sin(x * 0.47f + 1.1f); }
|
|
float ridgeHigh(float x) { return 6.5f + 1.6f * std::sin(x * 0.15f + 2.0f) + 1.0f * std::sin(x * 0.58f); }
|
|
|
|
float segDist(glm::vec2 p, glm::vec2 a, glm::vec2 b) {
|
|
glm::vec2 ab = b - a;
|
|
float t = glm::clamp(glm::dot(p - a, ab) / std::max(glm::dot(ab, ab), 1e-6f), 0.0f, 1.0f);
|
|
return glm::length(p - (a + ab * t));
|
|
}
|
|
|
|
// Meadow landmarks (tile coordinates, +z = south).
|
|
constexpr int kW = 96, kD = 88;
|
|
const glm::vec2 kLake{24.0f, 46.0f};
|
|
constexpr float kLakeR = 10.5f;
|
|
const glm::vec2 kPond{76.0f, 64.0f};
|
|
constexpr float kPondR = 4.4f;
|
|
const glm::vec2 kPlateau{80.0f, 30.0f};
|
|
constexpr float kPlateauR = 13.0f;
|
|
const glm::vec2 kPlazaMin{40.0f, 70.0f}, kPlazaMax{56.0f, 82.0f};
|
|
const glm::vec2 kGate{48.0f, 85.4f};
|
|
const glm::vec2 kTent{41.0f, 72.6f};
|
|
const ZonePatch kPatches[] = {{{58, 67}, 5.5f}, {{32, 64}, 5.0f}, {{41, 35}, 4.0f}, {{62, 46}, 4.5f},
|
|
{{83, 27}, 5.0f}, {{30, 10}, 4.0f}, {{13, 60}, 3.8f}, {{69, 75}, 4.0f}};
|
|
// 10 units: four 2-unit "dock" segments plus a "dock_end" (models from tools/blender_safari.py);
|
|
// the east end rests on the sand so it is reachable on foot.
|
|
const ZoneDock kDock{{27.6f, 45.2f}, {37.6f, 46.8f}, 0.05f};
|
|
|
|
bool inPlaza(glm::vec2 p, float margin) {
|
|
return p.x > kPlazaMin.x - margin && p.x < kPlazaMax.x + margin && p.y > kPlazaMin.y - margin &&
|
|
p.y < kPlazaMax.y + margin;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool Zone::generate(const std::string& id, uint32_t seed) {
|
|
if (id != "meadow") return false;
|
|
id_ = id;
|
|
seed_ = seed;
|
|
props.clear();
|
|
walls.clear();
|
|
docks.clear();
|
|
tallGrassPatches.clear();
|
|
buildMeadow(seed);
|
|
return true;
|
|
}
|
|
|
|
int Zone::level(int x, int z) const {
|
|
x = std::clamp(x, 0, w_ - 1);
|
|
z = std::clamp(z, 0, d_ - 1);
|
|
return levels_[size_t(z) * w_ + x];
|
|
}
|
|
|
|
uint8_t Zone::material(int x, int z) const {
|
|
x = std::clamp(x, 0, w_ - 1);
|
|
z = std::clamp(z, 0, d_ - 1);
|
|
return mats_[size_t(z) * w_ + x];
|
|
}
|
|
|
|
bool Zone::tallGrassTile(int x, int z) const { return inBounds(x, z) && grass_[size_t(z) * w_ + x]; }
|
|
|
|
void Zone::setTile(int x, int z, int lvl, uint8_t mat) {
|
|
if (!inBounds(x, z)) return;
|
|
levels_[size_t(z) * w_ + x] = lvl;
|
|
mats_[size_t(z) * w_ + x] = mat;
|
|
}
|
|
|
|
float Zone::terrainHeight(float x, float z) const {
|
|
return float(level(int(std::floor(x)), int(std::floor(z)))) * kLevelHeight;
|
|
}
|
|
|
|
bool Zone::onDock(glm::vec2 p) const {
|
|
for (const ZoneDock& d : docks)
|
|
if (p.x >= d.min.x && p.x <= d.max.x && p.y >= d.min.y && p.y <= d.max.y) return true;
|
|
return false;
|
|
}
|
|
|
|
float Zone::groundHeight(glm::vec2 p) const {
|
|
for (const ZoneDock& d : docks)
|
|
if (p.x >= d.min.x && p.x <= d.max.x && p.y >= d.min.y && p.y <= d.max.y) return d.deckY;
|
|
return terrainHeight(p.x, p.y);
|
|
}
|
|
|
|
bool Zone::isWater(glm::vec2 p) const {
|
|
int x = int(std::floor(p.x)), z = int(std::floor(p.y));
|
|
return inBounds(x, z) && level(x, z) < 0 && !onDock(p);
|
|
}
|
|
|
|
bool Zone::isDeepWater(glm::vec2 p) const {
|
|
int x = int(std::floor(p.x)), z = int(std::floor(p.y));
|
|
return inBounds(x, z) && level(x, z) <= -2 && !onDock(p);
|
|
}
|
|
|
|
bool Zone::inTallGrass(glm::vec2 p) const { return tallGrassTile(int(std::floor(p.x)), int(std::floor(p.y))); }
|
|
|
|
bool Zone::blocked(glm::vec2 p, float r) const {
|
|
for (const ZoneProp& o : props)
|
|
if (o.radius > 0.0f && glm::length(glm::vec2(o.pos.x, o.pos.z) - p) < o.radius + r) return true;
|
|
for (const ZoneWall& w : walls)
|
|
if (segDist(p, w.a, w.b) < w.halfWidth + r) return true;
|
|
return false;
|
|
}
|
|
|
|
bool Zone::walkable(glm::vec2 from, glm::vec2 to, float radius) const {
|
|
if (to.x < 1.0f || to.y < 1.0f || to.x > float(w_ - 1) || to.y > float(d_ - 1)) return false;
|
|
bool dockTo = onDock(to), dockFrom = onDock(from);
|
|
if (!dockTo) {
|
|
int fl = level(int(std::floor(from.x)), int(std::floor(from.y)));
|
|
int tl = level(int(std::floor(to.x)), int(std::floor(to.y)));
|
|
if (tl < 0) return false; // water
|
|
if (!dockFrom && (tl > fl + 1 || tl < fl - 2)) return false; // climb one level, drop at most two
|
|
}
|
|
// Only block motion that goes deeper into an obstacle, so nobody gets stuck.
|
|
for (const ZoneProp& o : props) {
|
|
if (o.radius <= 0.0f) continue;
|
|
glm::vec2 c(o.pos.x, o.pos.z);
|
|
float r = o.radius + radius;
|
|
float dt = glm::length(c - to);
|
|
if (dt < r && dt < glm::length(c - from)) return false;
|
|
}
|
|
for (const ZoneWall& w : walls) {
|
|
float r = w.halfWidth + radius;
|
|
float dt = segDist(to, w.a, w.b);
|
|
if (dt < r && dt < segDist(from, w.a, w.b)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Zone::creatureWalkable(glm::vec2 p, bool water) const {
|
|
if (p.x < 1.5f || p.y < 1.5f || p.x > float(w_) - 1.5f || p.y > float(d_) - 1.5f) return false;
|
|
if (water) return isWater(p);
|
|
if (isWater(p) || onDock(p)) return false;
|
|
return !blocked(p, 0.3f);
|
|
}
|
|
|
|
bool Zone::fishingTarget(glm::vec2 from, glm::vec2 dir, glm::vec2& bobber) const {
|
|
if (glm::length(dir) < 1e-3f || isWater(from)) return false;
|
|
dir = glm::normalize(dir);
|
|
for (float t = 1.6f; t <= 4.5f; t += 0.2f) {
|
|
glm::vec2 p = from + dir * t;
|
|
if (isWater(p)) {
|
|
// land a bit beyond the shoreline so the bobber sits in open water
|
|
glm::vec2 q = p + dir * 0.6f;
|
|
bobber = isWater(q) ? q : p;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Zone::randomTallGrassNear(Rng& rng, glm::vec2 c, float rmin, float rmax, glm::vec2& out) const {
|
|
for (int i = 0; i < 40; ++i) {
|
|
float a = rng.range(0.0f, 2.0f * kPi), r = rng.range(rmin, rmax);
|
|
glm::vec2 p = c + glm::vec2(std::cos(a), std::sin(a)) * r;
|
|
if (inTallGrass(p) && creatureWalkable(p, false)) {
|
|
out = p;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Zone::randomWater(Rng& rng, glm::vec2& out, bool deep) const {
|
|
for (int i = 0; i < 60; ++i) {
|
|
glm::vec2 p(rng.range(waterMin.x, waterMax.x), rng.range(waterMin.y, waterMax.y));
|
|
if (deep ? isDeepWater(p) : isWater(p)) {
|
|
out = p;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Zone::randomWaterNear(Rng& rng, glm::vec2 c, float rmax, glm::vec2& out) const {
|
|
for (int i = 0; i < 40; ++i) {
|
|
float a = rng.range(0.0f, 2.0f * kPi), r = rng.range(0.5f, rmax);
|
|
glm::vec2 p = c + glm::vec2(std::cos(a), std::sin(a)) * r;
|
|
if (isWater(p)) {
|
|
out = p;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Zone::randomWanderSpot(Rng& rng, glm::vec2& out) const {
|
|
for (int i = 0; i < 80; ++i) {
|
|
glm::vec2 p(rng.range(3.0f, float(w_) - 3.0f), rng.range(3.0f, float(d_) - 3.0f));
|
|
int x = int(std::floor(p.x)), z = int(std::floor(p.y));
|
|
uint8_t m = material(x, z);
|
|
if (m != MatGrass && m != MatMeadow) continue;
|
|
if (inPlaza(p, 7.0f) || !creatureWalkable(p, false)) continue;
|
|
out = p;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ------------------------------------------------------------------ meadow
|
|
|
|
void Zone::paintPath(const std::vector<glm::vec2>& pts, float halfWidth, bool clearGrass) {
|
|
for (size_t i = 0; i + 1 < pts.size(); ++i) {
|
|
glm::vec2 a = pts[i], b = pts[i + 1];
|
|
int steps = std::max(1, int(glm::length(b - a) * 4.0f));
|
|
for (int s = 0; s <= steps; ++s) {
|
|
glm::vec2 q = glm::mix(a, b, float(s) / float(steps));
|
|
int r = int(std::ceil(halfWidth)) + 1;
|
|
for (int dz = -r; dz <= r; ++dz)
|
|
for (int dx = -r; dx <= r; ++dx) {
|
|
int x = int(std::floor(q.x)) + dx, z = int(std::floor(q.y)) + dz;
|
|
if (!inBounds(x, z) || level(x, z) < 0) continue;
|
|
if (glm::length(glm::vec2(float(x) + 0.5f, float(z) + 0.5f) - q) > halfWidth) continue;
|
|
if (material(x, z) != MatPlaza) mats_[size_t(z) * w_ + x] = MatPath;
|
|
if (clearGrass) grass_[size_t(z) * w_ + x] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Zone::clearForProp(glm::vec2 p, float r) const {
|
|
int x = int(std::floor(p.x)), z = int(std::floor(p.y));
|
|
if (!inBounds(x, z) || level(x, z) < 0) return false;
|
|
uint8_t m = material(x, z);
|
|
if (m == MatPath || m == MatPlaza || m == MatStone || m == MatSand) return false;
|
|
if (grass_[size_t(z) * w_ + x]) return false;
|
|
if (onDock(p) || inPlaza(p, 2.5f)) return false;
|
|
if (glm::length(p - kTent) < 3.5f || glm::length(p - kGate) < 3.0f) return false;
|
|
if (p.x > kDock.min.x - 1.0f && p.x < kDock.max.x + 2.5f && p.y > kDock.min.y - 1.5f && p.y < kDock.max.y + 1.5f)
|
|
return false;
|
|
return !blocked(p, r);
|
|
}
|
|
|
|
void Zone::addProp(const std::string& model, glm::vec2 p, float yaw, float scale, float radius, float tint,
|
|
float yOffset) {
|
|
ZoneProp o;
|
|
o.model = model;
|
|
o.pos = glm::vec3(p.x, groundHeight(p) + yOffset, p.y);
|
|
o.yaw = yaw;
|
|
o.scale = glm::vec3(scale);
|
|
o.tint = tint;
|
|
o.radius = radius;
|
|
props.push_back(o);
|
|
}
|
|
|
|
void Zone::addFence(glm::vec2 a, glm::vec2 b) {
|
|
glm::vec2 d = b - a;
|
|
float len = glm::length(d);
|
|
int n = std::max(1, int(std::round(len / 2.0f)));
|
|
glm::vec2 step = d / float(n);
|
|
// fence model: 2 units along +X. yaw rotates +X toward the segment direction (mesh.vert convention).
|
|
float yaw = std::atan2(-step.y, step.x);
|
|
for (int i = 0; i < n; ++i) {
|
|
glm::vec2 c = a + step * (float(i) + 0.5f);
|
|
ZoneProp o;
|
|
o.model = "fence";
|
|
o.pos = glm::vec3(c.x, groundHeight(c), c.y);
|
|
o.yaw = yaw;
|
|
o.scale = glm::vec3(glm::length(step) / 2.0f, 1.0f, 1.0f);
|
|
props.push_back(o);
|
|
}
|
|
walls.push_back({a, b, 0.12f});
|
|
}
|
|
|
|
void Zone::buildMeadow(uint32_t seed) {
|
|
w_ = kW;
|
|
d_ = kD;
|
|
levels_.assign(size_t(w_) * d_, 0);
|
|
mats_.assign(size_t(w_) * d_, MatGrass);
|
|
grass_.assign(size_t(w_) * d_, 0);
|
|
Rng rng(seed ^ 0x5AFA21u);
|
|
|
|
// --- terrain: north ridge, east plateau, lake, pond, plaza
|
|
for (int z = 0; z < d_; ++z)
|
|
for (int x = 0; x < w_; ++x) {
|
|
glm::vec2 p(float(x) + 0.5f, float(z) + 0.5f);
|
|
int lvl = 0;
|
|
uint8_t mat = MatGrass;
|
|
if (p.y < ridgeLow(p.x)) lvl = 2, mat = MatRidge;
|
|
if (p.y < ridgeHigh(p.x)) lvl = 4, mat = MatRidge;
|
|
if (lvl == 0 && blob(p, kPlateau, kPlateauR, 1.9f) < 1.0f) lvl = 2, mat = MatRidge;
|
|
if (blob(p, {15.0f, 74.0f}, 4.2f, 0.3f) < 1.0f) lvl = 1; // small hill by the west fence
|
|
|
|
float lake = blob(p, kLake, kLakeR, 0.7f);
|
|
if (lake < 1.16f) lvl = 0, mat = MatSand;
|
|
if (lake < 1.0f) lvl = -1;
|
|
if (lake < 0.68f) lvl = -3;
|
|
float pond = blob(p, kPond, kPondR, 2.4f);
|
|
if (pond < 1.22f) lvl = 0, mat = MatSand;
|
|
if (pond < 1.0f) lvl = -1;
|
|
if (pond < 0.6f) lvl = -2;
|
|
|
|
if (inPlaza(p, 0.0f)) {
|
|
float cx = std::max({kPlazaMin.x - p.x, p.x - kPlazaMax.x, 0.0f});
|
|
float cz = std::max({kPlazaMin.y - p.y, p.y - kPlazaMax.y, 0.0f});
|
|
(void)cx, (void)cz;
|
|
// round the corners
|
|
glm::vec2 c = glm::clamp(p, kPlazaMin + 2.5f, kPlazaMax - 2.5f);
|
|
if (glm::length(p - c) < 2.5f) lvl = 0, mat = MatPlaza;
|
|
}
|
|
setTile(x, z, lvl, mat);
|
|
}
|
|
|
|
// --- tall grass patches (Meadow material under them)
|
|
for (const ZonePatch& tg : kPatches) {
|
|
tallGrassPatches.push_back(tg);
|
|
for (int z = 0; z < d_; ++z)
|
|
for (int x = 0; x < w_; ++x) {
|
|
glm::vec2 p(float(x) + 0.5f, float(z) + 0.5f);
|
|
if (blob(p, tg.c, tg.r, tg.c.x * 0.37f) >= 1.0f) continue;
|
|
if (level(x, z) < 0 || material(x, z) == MatSand || material(x, z) == MatPlaza) continue;
|
|
mats_[size_t(z) * w_ + x] = MatMeadow;
|
|
grass_[size_t(z) * w_ + x] = 1;
|
|
}
|
|
}
|
|
|
|
// --- paths from the gate through the plaza to every area
|
|
const glm::vec2 cross{48.0f, 60.0f};
|
|
paintPath({{48, 87}, {48, 76}, cross}, 1.35f);
|
|
paintPath({cross, {41, 54}, {37, 48.5f}, {36, 46}}, 1.25f);
|
|
paintPath({cross, {50, 46}, {52, 30}, {52, 2}}, 1.3f);
|
|
paintPath({cross, {60, 56}, {67, 49}, {72, 41}, {78, 33}, {84, 30}}, 1.25f);
|
|
paintPath({{36, 46}, {34, 52}, {33, 58}, {38, 66}, {44, 70}}, 1.0f); // lake loop back to the plaza
|
|
|
|
// Stairs: a path tile next to a path tile two levels higher becomes a one-level step.
|
|
for (int pass = 0; pass < 3; ++pass) {
|
|
std::vector<std::pair<int, int>> raise;
|
|
for (int z = 0; z < d_; ++z)
|
|
for (int x = 0; x < w_; ++x) {
|
|
if (material(x, z) != MatPath && material(x, z) != MatStone) continue;
|
|
int l = level(x, z);
|
|
const int nx[4] = {1, -1, 0, 0}, nz[4] = {0, 0, 1, -1};
|
|
for (int k = 0; k < 4; ++k) {
|
|
int xx = x + nx[k], zz = z + nz[k];
|
|
if (!inBounds(xx, zz)) continue;
|
|
uint8_t m = material(xx, zz);
|
|
if ((m == MatPath || m == MatStone) && level(xx, zz) >= l + 2) {
|
|
raise.push_back({x, z});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (auto [x, z] : raise) setTile(x, z, level(x, z) + 1, MatStone);
|
|
}
|
|
|
|
// --- landmarks
|
|
docks.push_back(kDock);
|
|
plaza = (kPlazaMin + kPlazaMax) * 0.5f;
|
|
henry = {44.6f, 73.4f};
|
|
spawn = {48.2f, 76.6f};
|
|
henryYaw = yawFromDir(spawn - henry);
|
|
spawnYaw = yawFromDir(henry - spawn);
|
|
|
|
// dock segments (2 units along +X each) from the shore out over the lake
|
|
for (float x = kDock.max.x - 1.0f; x > kDock.min.x + 2.5f; x -= 2.0f) {
|
|
ZoneProp o;
|
|
o.model = "dock";
|
|
o.pos = glm::vec3(x, kDock.deckY, (kDock.min.y + kDock.max.y) * 0.5f);
|
|
props.push_back(o);
|
|
}
|
|
{
|
|
ZoneProp o;
|
|
o.model = "dock_end";
|
|
o.pos = glm::vec3(kDock.min.x + 1.0f, kDock.deckY, (kDock.min.y + kDock.max.y) * 0.5f);
|
|
o.yaw = 3.14159265f; // the end board (+X in the model) faces out over the lake
|
|
props.push_back(o);
|
|
}
|
|
|
|
addProp("gate", kGate, 0.0f, 1.0f, 0.0f);
|
|
props.back().radius = 0.0f;
|
|
// gate pillars collide (the arch itself is overhead)
|
|
walls.push_back({kGate + glm::vec2(-1.95f, 0.0f), kGate + glm::vec2(-1.45f, 0.0f), 0.3f});
|
|
walls.push_back({kGate + glm::vec2(1.45f, 0.0f), kGate + glm::vec2(1.95f, 0.0f), 0.3f});
|
|
{ // door (+Z in the model) faces the plaza; mesh yaw maps local +Z to (sin yaw, cos yaw)
|
|
glm::vec2 d = plaza - kTent;
|
|
addProp("tent", kTent, std::atan2(d.x, d.y), 1.0f, 1.55f);
|
|
}
|
|
for (glm::vec2 p : {glm::vec2{42.6f, 80.6f}, {53.4f, 80.6f}, {53.4f, 71.4f}, {46.1f, 64.0f}, {50.0f, 50.0f},
|
|
{37.8f, 49.6f}, {66.2f, 51.6f}, {53.6f, 36.0f}, {75.6f, 37.6f}})
|
|
addProp("lamp", p, 0.0f, 1.0f, 0.22f);
|
|
addProp("signpost", {50.9f, 62.2f}, -0.4f, 1.0f, 0.2f);
|
|
addProp("signpost", {39.4f, 52.2f}, 0.8f, 1.0f, 0.2f);
|
|
addProp("signpost", {70.6f, 46.6f}, -0.9f, 1.0f, 0.2f);
|
|
addProp("crate", {38.9f, 74.9f}, 0.3f, 1.0f, 0.45f);
|
|
addProp("crate", {38.6f, 76.1f}, 1.1f, 0.85f, 0.4f, 0.9f);
|
|
addProp("barrel", {39.9f, 76.3f}, 0.0f, 1.0f, 0.35f);
|
|
addProp("hay_bale", {55.0f, 75.6f}, 0.2f, 1.0f, 0.6f);
|
|
addProp("hay_bale", {55.6f, 77.0f}, 1.4f, 1.0f, 0.6f, 0.92f);
|
|
addProp("log_bench", {52.6f, 73.2f}, 0.0f, 1.0f, 0.5f);
|
|
addProp("log_bench", {36.4f, 40.6f}, 1.57f, 1.0f, 0.5f);
|
|
addProp("crate", {38.6f, 47.6f}, 0.6f, 0.8f, 0.4f);
|
|
|
|
// fences along the southern border, either side of the gate
|
|
addFence({33.0f, 85.4f}, {45.9f, 85.4f});
|
|
addFence({50.1f, 85.4f}, {63.0f, 85.4f});
|
|
// paddock fence west of the main path
|
|
addFence({44.6f, 68.6f}, {44.6f, 62.6f});
|
|
|
|
// --- trees: dense border forest, groves on the ridge, sparse elsewhere
|
|
const char* oaks[] = {"oak_a", "oak_b"};
|
|
const char* pines[] = {"pine_a", "pine_b"};
|
|
for (int z = 0; z < d_; ++z)
|
|
for (int x = 0; x < w_; ++x) {
|
|
int edge = std::min({x, z, w_ - 1 - x, d_ - 1 - z});
|
|
float chance = edge < 5 ? 0.5f - float(edge) * 0.07f : (level(x, z) >= 4 ? 0.16f : 0.014f);
|
|
if (level(x, z) == 2 && material(x, z) == MatRidge) chance = std::max(chance, 0.03f);
|
|
if (!rng.chance(chance)) continue;
|
|
glm::vec2 p(float(x) + rng.range(0.2f, 0.8f), float(z) + rng.range(0.2f, 0.8f));
|
|
if (std::abs(p.x - kGate.x) < 4.0f && p.y > 80.0f) continue;
|
|
float scale = rng.range(0.8f, 1.12f);
|
|
if (!clearForProp(p, 1.0f * scale)) continue;
|
|
if (blob(p, kLake, kLakeR, 0.7f) < 1.4f || blob(p, kPond, kPondR, 2.4f) < 1.5f) continue;
|
|
bool pine = rng.chance(level(x, z) >= 2 || edge < 5 ? 0.55f : 0.3f);
|
|
const char* model = pine ? pines[rng.range(0, 1)] : oaks[rng.range(0, 1)];
|
|
ZoneProp o;
|
|
o.model = model;
|
|
o.pos = glm::vec3(p.x, terrainHeight(p.x, p.y), p.y);
|
|
o.yaw = rng.range(0.0f, 6.2832f);
|
|
o.scale = glm::vec3(scale, scale * rng.range(0.92f, 1.08f), scale);
|
|
o.tint = rng.range(0.86f, 1.08f);
|
|
o.radius = 0.45f * scale;
|
|
props.push_back(o);
|
|
}
|
|
|
|
// --- rocks (gathering around water and the plateau edge) and bushes
|
|
for (int z = 1; z < d_ - 1; ++z)
|
|
for (int x = 1; x < w_ - 1; ++x) {
|
|
glm::vec2 p(float(x) + 0.5f + rng.range(-0.2f, 0.2f), float(z) + 0.5f + rng.range(-0.2f, 0.2f));
|
|
float lake = blob(p, kLake, kLakeR, 0.7f), pond = blob(p, kPond, kPondR, 2.4f);
|
|
bool shore = (lake > 1.16f && lake < 1.45f) || (pond > 1.22f && pond < 1.55f);
|
|
bool cliff = level(x, z) >= 2 && (level(x + 1, z) < level(x, z) || level(x, z + 1) < level(x, z));
|
|
float r = rng.uniform();
|
|
float s = rng.range(0.8f, 1.3f);
|
|
if (r < (shore ? 0.07f : cliff ? 0.06f : 0.006f)) {
|
|
if (!clearForProp(p, 0.6f * s)) continue;
|
|
ZoneProp o;
|
|
o.model = rng.chance(0.5f) ? "rock_a" : "rock_b";
|
|
o.pos = glm::vec3(p.x, terrainHeight(p.x, p.y) - 0.02f, p.y);
|
|
o.yaw = rng.range(0.0f, 6.2832f);
|
|
o.scale = glm::vec3(s, s * rng.range(0.8f, 1.2f), s);
|
|
o.tint = rng.range(0.86f, 1.08f);
|
|
o.radius = 0.42f * s;
|
|
props.push_back(o);
|
|
} else if (r < (shore ? 0.12f : 0.022f)) {
|
|
float bs = rng.range(0.8f, 1.25f);
|
|
if (!clearForProp(p, 0.65f * bs)) continue;
|
|
ZoneProp o;
|
|
o.model = rng.chance(0.5f) ? "bush_a" : "bush_b";
|
|
o.pos = glm::vec3(p.x, terrainHeight(p.x, p.y) - 0.03f, p.y);
|
|
o.yaw = rng.range(0.0f, 6.2832f);
|
|
o.scale = glm::vec3(bs);
|
|
o.tint = rng.range(0.86f, 1.08f);
|
|
o.radius = 0.5f * bs;
|
|
props.push_back(o);
|
|
}
|
|
}
|
|
|
|
// --- water bounds for the water plane and spawns
|
|
waterMin = glm::vec2(float(w_), float(d_));
|
|
waterMax = glm::vec2(0.0f);
|
|
for (int z = 0; z < d_; ++z)
|
|
for (int x = 0; x < w_; ++x)
|
|
if (level(x, z) < 0) {
|
|
waterMin = glm::min(waterMin, glm::vec2(float(x), float(z)));
|
|
waterMax = glm::max(waterMax, glm::vec2(float(x + 1), float(z + 1)));
|
|
}
|
|
waterMin -= glm::vec2(1.0f);
|
|
waterMax += glm::vec2(1.0f);
|
|
}
|
|
|
|
} // namespace bs
|