28#include <unordered_set>
38constexpr int kDecorBase = 100;
39constexpr int kFloorMaxVariant = 32;
42constexpr int kE = 1 << 0;
43constexpr int kS = 1 << 1;
44constexpr int kW = 1 << 2;
45constexpr int kN = 1 << 3;
46constexpr int kSE = 1 << 4;
47constexpr int kSW = 1 << 5;
48constexpr int kNW = 1 << 6;
49constexpr int kNE = 1 << 7;
51bool isWalkable(uint32_t
c) {
62int clampInt(
int v,
int lo,
int hi) {
return std::max(lo, std::min(hi,
v)); }
64std::vector<std::string> assetPool(
const Params &
params,
const std::string &role,
65 const std::string &fallback) {
66 std::vector<std::string> result;
67 std::stringstream
input(
params.getString(
"assets." + role, fallback));
69 while (std::getline(
input, item,
',')) {
70 const auto first = item.find_first_not_of(
" \t");
71 const auto last = item.find_last_not_of(
" \t");
72 if (
first != std::string::npos) result.push_back(item.substr(
first, last -
first + 1));
77std::string pickAsset(
const std::vector<std::string> &pool, std::mt19937 &rng) {
78 if (pool.empty())
return {};
79 return pool[size_t(rng()) % pool.size()];
83void autotileWalls(Grid2D &out) {
84 const int w = out.getWidth();
85 const int h = out.getHeight();
86 for (
int y = 0;
y <
h; ++
y) {
87 for (
int x = 0;
x <
w; ++
x) {
90 if (
x + 1 <
w && isWalkable(uint32_t(out.getCell(
x + 1,
y))))
mask |= kE;
91 if (
y + 1 <
h && isWalkable(uint32_t(out.getCell(
x,
y + 1))))
mask |= kS;
92 if (
x - 1 >= 0 && isWalkable(uint32_t(out.getCell(
x - 1,
y))))
mask |= kW;
93 if (
y - 1 >= 0 && isWalkable(uint32_t(out.getCell(
x,
y - 1))))
mask |= kN;
94 if (
x + 1 <
w &&
y + 1 <
h && isWalkable(uint32_t(out.getCell(
x + 1,
y + 1))))
96 if (
x - 1 >= 0 &&
y + 1 <
h && isWalkable(uint32_t(out.getCell(
x - 1,
y + 1))))
98 if (
x - 1 >= 0 &&
y - 1 >= 0 && isWalkable(uint32_t(out.getCell(
x - 1,
y - 1))))
100 if (
x + 1 <
w &&
y - 1 >= 0 && isWalkable(uint32_t(out.getCell(
x + 1,
y - 1))))
102 out.setDetail(
x,
y,
mask);
108int floorVariant(
int x,
int y,
const std::string &
pattern,
int variants, uint32_t salt) {
109 const int v = std::max(1, variants);
112 return 1 + ((
x +
y) %
v);
116 return 1 + (int((
x * 73856093u +
y * 19349663u + salt) & 0x7FFFFFFFu) %
v);
120void carveRect(Grid2D &out,
const Rect &
r, uint32_t
semantic) {
121 for (
int y =
r.y;
y <
r.y +
r.h; ++
y)
125bool rectsOverlap(
const Rect &
a,
const Rect &
b,
int pad) {
130void carveCorridor(Grid2D &out,
int ax,
int ay,
int bx,
int by,
int width,
131 const std::string &style, std::mt19937 &rng) {
132 auto carve = [&](
int x,
int y) {
136 if (style ==
"diagonal") {
139 while (
x !=
bx ||
y !=
by) {
143 if (
x !=
bx &&
y !=
by && (rng() & 1u)) {
144 x += (
bx >
x) ? 1 : -1;
145 }
else if (
x !=
bx) {
146 x += (
bx >
x) ? 1 : -1;
148 y += (
by >
y) ? 1 : -1;
159 if (style ==
"straight") {
161 }
else if (rng() & 1u) {
166 const int x0 = std::min(
ax, cornerX);
167 const int x1 = std::max(
ax, cornerX);
168 for (
int x = x0;
x <= x1; ++
x)
170 carve(
x, cornerY +
oy);
172 const int y0 = std::min(cornerY,
by);
173 const int y1 = std::max(cornerY,
by);
174 for (
int y = y0;
y <= y1; ++
y)
176 carve(cornerX +
ox,
y);
179bool genRoguelike(
const Params &
params, Grid2D &out, std::string &
error) {
180 const int w =
params.getWidth();
181 const int h =
params.getHeight();
182 if (
w < 9 ||
h < 9) {
183 error =
"level.roguelike: size must be at least 9x9";
188 const int roomCount = clampInt(
params.getInt(
"roomCount", 9), 1, 256);
189 const int roomMin = clampInt(
params.getInt(
"roomMin", 4), 2,
w);
190 const int roomMax = clampInt(
params.getInt(
"roomMax", 8), roomMin,
w);
191 const int padding = clampInt(
params.getInt(
"padding", 1), 0, 4);
192 const int spacing = clampInt(
params.getInt(
"spacing", 2), 0, 8);
193 const int clusterGapMin = clampInt(
params.getInt(
"clusterGapMin",
spacing + 1), 1, 8);
194 const int clusterGapMax = clampInt(
params.getInt(
"clusterGapMax",
spacing + 3),
196 const int clusterBranchBias = clampInt(
params.getInt(
"clusterBranchBias", 0), 0, 4);
197 const int corridorW = clampInt(
params.getInt(
"corridorWidth", 1), 1, 3);
198 const int stairCount = clampInt(
params.getInt(
"stairCount", 1), 0, 4);
199 const int stairSideMask = clampInt(
params.getInt(
"stairSideMask", 15), 1, 15);
200 const std::string
layout =
params.getString(
"layoutStyle",
"grid");
201 const std::string style =
params.getString(
"corridorStyle",
"l");
202 const std::string connections =
params.getString(
"connectionStyle",
"sequential");
203 const std::string
pattern =
params.getString(
"floorPattern",
"brick");
204 const int variants = clampInt(
params.getInt(
"floorVariants", 4), 1, kFloorMaxVariant);
205 const float decorDensity = std::clamp(
params.getFloat(
"decorDensity", 0.05f), 0.f, 1.f);
206 const std::string decorSet =
params.getString(
"decorSet",
"mixed");
207 const bool doAutotile =
params.getInt(
"autotile", 1) != 0;
212 std::mt19937 rng(
seed);
217 const int cols = std::max(1,
int(std::sqrt(
double(roomCount))));
218 const int rows = std::max(1, (roomCount +
cols - 1) /
cols);
219 std::vector<Rect> rooms;
220 std::vector<size_t> roomParents;
221 std::uniform_int_distribution<int> dim(roomMin, roomMax);
222 std::uniform_int_distribution<int> jx(0, 0), jy(0, 0);
224 if (
layout ==
"clustered") {
225 const int firstW = std::min(dim(rng),
w - padding * 2);
226 const int firstH = std::min(dim(rng),
h - padding * 2);
227 rooms.push_back({(
w - firstW) / 2, (
h - firstH) / 2, firstW, firstH});
228 roomParents.push_back(0);
229 std::uniform_int_distribution<int> jitter(-1, 1);
230 std::uniform_int_distribution<int> clusterGap(clusterGapMin, clusterGapMax);
231 const int maxAttempts = roomCount * 160;
232 for (
int attempt = 0; attempt < maxAttempts && int(rooms.size()) < roomCount; ++attempt) {
233 size_t parentIndex = size_t(rng()) % rooms.size();
238 parentIndex = std::min(parentIndex,
size_t(rng()) % rooms.size());
239 const Rect &
parent = rooms[parentIndex];
240 const int rw = std::min(dim(rng),
w - padding * 2);
241 const int rh = std::min(dim(rng),
h - padding * 2);
242 const int gap = clusterGap(rng);
243 Rect cand{0, 0, rw, rh};
244 switch (rng() % 4u) {
247 cand.y =
parent.y +
parent.h / 2 - rh / 2 + jitter(rng);
251 cand.y =
parent.y +
parent.h / 2 - rh / 2 + jitter(rng);
254 cand.x =
parent.x +
parent.w / 2 - rw / 2 + jitter(rng);
258 cand.x =
parent.x +
parent.w / 2 - rw / 2 + jitter(rng);
262 if (cand.x < padding || cand.y < padding ||
263 cand.x + cand.w >
w - padding || cand.y + cand.h >
h - padding)
continue;
265 for (
const Rect &other : rooms) {
266 if (rectsOverlap(cand, other,
spacing)) {
ok =
false;
break; }
269 rooms.push_back(cand);
270 roomParents.push_back(parentIndex);
274 for (
int r = 0;
r <
rows; ++
r) {
275 for (
int c = 0;
c <
cols; ++
c) {
276 if (
int(rooms.size()) >= roomCount)
break;
277 const int sx = padding + int((
long(
c) * (
w - 2 * padding)) /
cols);
278 const int ex = padding + int((
long(
c + 1) * (
w - 2 * padding)) /
cols);
279 const int sy = padding + int((
long(
r) * (
h - 2 * padding)) /
rows);
280 const int ey = padding + int((
long(
r + 1) * (
h - 2 * padding)) /
rows);
281 const int slotW = std::max(roomMin, ex -
sx);
282 const int slotH = std::max(roomMin, ey -
sy);
283 if (slotW < roomMin || slotH < roomMin)
continue;
284 for (
int attempt = 0; attempt < 24; ++attempt) {
285 const int rw = std::min(dim(rng), slotW);
286 const int rh = std::min(dim(rng), slotH);
287 std::uniform_int_distribution<int>
ox(
sx,
sx + slotW - rw);
288 std::uniform_int_distribution<int>
oy(
sy,
sy + slotH - rh);
289 const Rect cand{
ox(rng),
oy(rng), rw, rh};
291 for (
const Rect &other : rooms) {
292 if (rectsOverlap(cand, other,
spacing)) {
ok =
false;
break; }
295 roomParents.push_back(rooms.empty() ? 0 : rooms.
size() - 1);
296 rooms.push_back(cand);
305 const int rw = std::max(roomMin,
w / 3);
306 const int rh = std::max(roomMin,
h / 3);
307 rooms.push_back({(
w - rw) / 2, (
h - rh) / 2, rw, rh});
308 roomParents.push_back(0);
312 for (
const Rect &
r : rooms) carveRect(out,
r, Semantic::
Floor);
316 for (
size_t i = 1; i < rooms.size(); ++i) {
318 if (connections ==
"growth" && i < roomParents.size()) {
320 }
else if (connections ==
"nearest") {
321 int bestDistance =
w +
h + 1;
322 const int bx = rooms[i].x + rooms[i].w / 2;
323 const int by = rooms[i].y + rooms[i].h / 2;
324 for (
size_t candidate = 0; candidate < i; ++candidate) {
325 const int ax = rooms[candidate].x + rooms[candidate].w / 2;
326 const int ay = rooms[candidate].y + rooms[candidate].h / 2;
336 const int bx = rooms[i].x + rooms[i].w / 2;
337 const int by = rooms[i].y + rooms[i].h / 2;
338 carveCorridor(out,
ax,
ay,
bx,
by, corridorW, style, rng);
342 if (doAutotile) autotileWalls(out);
345 const uint32_t salt =
seed * 2654435761u;
346 for (
int y = 0;
y <
h; ++
y) {
347 for (
int x = 0;
x <
w; ++
x) {
348 const uint32_t
c = uint32_t(out.getCell(
x,
y));
350 out.setDetail(
x,
y, floorVariant(
x,
y,
pattern, variants, salt));
358 std::vector<std::pair<int, int>> floorCells;
359 floorCells.reserve(
size_t(
w *
h));
360 for (
int y = 1;
y <
h - 1; ++
y)
361 for (
int x = 1;
x <
w - 1; ++
x)
362 if (isWalkable(uint32_t(out.getCell(
x,
y)))) floorCells.emplace_back(
x,
y);
364 std::vector<std::pair<int, int>> decorTiles;
365 if (!floorCells.empty() && decorDensity > 0.f) {
366 const size_t count = size_t(
float(floorCells.size()) * decorDensity);
367 std::uniform_int_distribution<size_t> pick(0, floorCells.size() - 1);
368 std::uniform_int_distribution<int>
kind(0, 2);
369 for (
size_t i = 0; i <
count; ++i) {
370 const auto [
dx,
dy] = floorCells[pick(rng)];
371 if (out.getDetail(
dx,
dy) >= kDecorBase)
continue;
373 bool nearMarker =
false;
374 for (
int k = 0; k < out.getObjectCount(); ++k) {
375 if (
int(out.getObjectX(k)) ==
dx &&
int(out.getObjectY(k)) ==
dy) {
380 if (nearMarker)
continue;
381 out.setDetail(
dx,
dy, kDecorBase +
kind(rng));
382 decorTiles.emplace_back(
dx,
dy);
389 const auto containers = assetPool(
params,
"container",
"container");
390 const auto treasure = assetPool(
params,
"treasure",
"treasure");
392 const auto tables = assetPool(
params,
"table",
"table");
393 const auto seating = assetPool(
params,
"seating",
"seat");
394 const auto beds = assetPool(
params,
"bed",
"bed");
395 const auto shelves = assetPool(
params,
"shelf",
"shelf");
396 const auto lights = assetPool(
params,
"light",
"light");
397 const auto wallLights = assetPool(
params,
"wallLight",
398 params.getString(
"assets.light",
"light"));
399 const auto wallShelves = assetPool(
params,
"wallShelf",
400 params.getString(
"assets.shelf",
"shelf"));
401 const auto banners = assetPool(
params,
"banner",
"banner");
402 const auto weapons = assetPool(
params,
"weapon",
"weapon_display");
403 const auto traps = assetPool(
params,
"trap",
"trap");
404 const auto food = assetPool(
params,
"food",
"food");
405 const auto tavern = assetPool(
params,
"tavern",
"tavern_prop");
406 const auto clutter = assetPool(
params,
"clutter",
"clutter");
407 const auto stairsAssets = assetPool(
params,
"stairs",
"stairs");
408 const float propDensity = std::clamp(
params.getFloat(
"propDensity", 0.16f), 0.f, 1.f);
409 const float corridorLightDensity =
410 std::clamp(
params.getFloat(
"corridorLightDensity", 0.035f), 0.f, 0.25f);
412 int objectSerial = 0;
413 int minimumRoomProps = std::numeric_limits<int>::max();
414 auto cellKey = [
w](
int x,
int y) {
return y *
w +
x; };
415 auto place = [&](
const std::string &role,
const std::vector<std::string> &pool,
double x,
double y,
416 float rotation,
int flags,
float ow = 1.f,
float oh = 1.f) {
417 const int cellX = clampInt(
int(std::round(
x)), 0,
w - 1);
418 const int cellY = clampInt(
int(std::round(
y)), 0,
h - 1);
419 if (pool.empty() || !isWalkable(uint32_t(out.getCell(cellX, cellY))))
return false;
420 if ((flags & 1) &&
occupied.count(cellKey(cellX, cellY)))
return false;
421 out.addAssetObject(role + std::to_string(objectSerial++), role, pickAsset(pool, rng),
422 static_cast<float>(
x),
static_cast<float>(
y), ow, oh,
rotation, flags);
423 if (flags & 1)
occupied.insert(cellKey(cellX, cellY));
427 if (decorSet !=
"none") {
428 for (
size_t roomIndex = 0; roomIndex < rooms.size(); ++roomIndex) {
429 const Rect &
r = rooms[roomIndex];
430 if (
r.w < 3 ||
r.h < 3)
continue;
433 const int cx =
r.x +
r.w / 2,
cy =
r.y +
r.h / 2;
437 const int theme = roomIndex == 0 ? 2 : int((roomIndex +
seed) % 7);
438 static constexpr const char *kThemeNames[] = {
439 "storage",
"quarters",
"dining",
"armory",
"treasury",
"shrine",
"tavern"};
440 out.addAssetObject(
"room" + std::to_string(roomIndex),
"room",
441 kThemeNames[
theme],
float(
r.x),
float(
r.y),
442 float(
r.w),
float(
r.h), 0.f, 32);
443 const int propsBeforeRoom = objectSerial;
446 place(
"light", wallLights,
left,
cy, 90.f, 2 | 4);
447 if (
r.w >= 6) place(
"light", wallLights,
right,
cy, 270.f, 2 | 4);
448 if ((rng() % 3u) != 0) place(
"banner", banners,
left,
cy - 1, 90.f, 2);
449 if (propDensity >= 0.45f &&
r.w >= 5 && (roomIndex % 2) == 0)
450 place(
"banner", banners,
cx + (
r.w >= 7 ? 1 : 0),
bottom, 0.f, 2);
451 if (propDensity >= 0.6f &&
r.w >= 6 && (roomIndex % 2) != 0)
452 place(
"light", wallLights,
cx,
bottom, 0.f, 2 | 4);
454 if (decorSet ==
"pillars") {
459 if (decorSet ==
"treasure" ||
theme == 4) {
460 place(
"container", containers,
right,
cy, 270.f, 1);
461 place(
"treasure", treasure,
right - 1,
cy, 0.f, 16);
462 place(
"treasure", treasure,
right - 1.35f,
cy + 0.45f, 35.f, 16);
463 place(
"treasure", treasure,
right - 0.65f,
cy - 0.45f, 320.f, 16);
466 }
else if (
theme == 0) {
467 place(
"container", containers,
right,
top + 1, 270.f, 1);
468 place(
"container", containers,
right,
bottom - 1, 270.f, 1);
469 place(
"container", containers,
right - 0.75f,
top + 1.35f, 245.f, 16);
470 place(
"container", containers,
right - 0.55f,
bottom - 1.25f, 300.f, 16);
471 place(
"shelf", wallShelves,
cx,
bottom, 0.f, 1 | 2);
472 if (
r.h >= 6) place(
"shelf", wallShelves,
left,
cy + 1, 90.f, 1 | 2);
473 if (
r.w >= 6) place(
"container", containers,
left + 2,
bottom, 0.f, 1);
474 place(
"clutter", clutter,
left + 1,
bottom, 0.f, 16);
475 }
else if (
theme == 1) {
476 place(
"bed", beds,
right,
cy, 270.f, 1, 1.f, 2.f);
477 if (
r.h >= 6) place(
"bed", beds,
right,
cy - 2, 270.f, 1, 1.f, 2.f);
478 place(
"container", containers,
right - 1,
top, 180.f, 1);
480 place(
"seating", seating,
left + 1,
bottom - 1, 45.f, 1);
481 }
else if (
theme == 2) {
482 place(
"table", tables,
cx,
cy, (rng() & 1u) ? 0.f : 90.f, 1, 2.f, 1.f);
483 place(
"seating", seating,
cx - 1,
cy, 270.f, 1);
484 place(
"seating", seating,
cx + 1,
cy, 90.f, 1);
485 place(
"seating", seating,
cx,
cy - 1, 0.f, 1);
486 place(
"seating", seating,
cx,
cy + 1, 180.f, 1);
487 place(
"food", food,
cx,
cy, 0.f, 16);
488 place(
"food", food,
cx - 0.38f,
cy + 0.18f, 25.f, 16);
489 place(
"food", food,
cx + 0.38f,
cy - 0.18f, 205.f, 16);
491 place(
"table", tables,
cx - 2,
cy, 0.f, 1, 2.f, 1.f);
492 place(
"seating", seating,
cx - 2,
cy - 1, 0.f, 1);
493 place(
"seating", seating,
cx - 2,
cy + 1, 180.f, 1);
494 place(
"food", food,
cx - 2,
cy, 70.f, 16);
496 }
else if (
theme == 3) {
497 place(
"weapon", weapons,
left,
cy, 90.f, 2);
498 place(
"weapon", weapons,
cx,
bottom, 0.f, 2);
501 place(
"container", containers,
right,
top + 1, 270.f, 1);
502 if ((rng() & 1u) != 0) place(
"trap", traps,
cx - 1,
cy, 0.f, 8);
503 }
else if (
theme == 5) {
504 place(
"table", tables,
cx,
cy, 0.f, 1, 2.f, 1.f);
505 place(
"light",
lights,
cx - 0.45f,
cy, 0.f, 4);
506 place(
"light",
lights,
cx + 0.45f,
cy, 0.f, 4);
507 place(
"food", food,
cx,
cy, 0.f, 16);
508 place(
"seating", seating,
cx,
bottom - 1, 180.f, 1);
510 place(
"seating", seating,
cx - 2,
bottom - 1, 180.f, 1);
511 place(
"seating", seating,
cx + 2,
bottom - 1, 180.f, 1);
514 place(
"tavern", tavern,
right,
cy, 270.f, 1);
515 place(
"table", tables,
cx,
cy, 0.f, 1, 2.f, 1.f);
516 place(
"seating", seating,
cx - 1,
cy, 270.f, 1);
517 place(
"seating", seating,
cx + 1,
cy, 90.f, 1);
518 place(
"seating", seating,
cx,
cy + 1, 180.f, 1);
519 place(
"food", food,
cx,
cy, 0.f, 16);
520 place(
"food", food,
cx - 0.4f,
cy, 20.f, 16);
521 place(
"food", food,
cx + 0.4f,
cy, 340.f, 16);
522 place(
"container", containers,
right,
bottom - 1, 270.f, 1);
523 place(
"tavern", tavern,
right - 0.7f,
bottom - 1.2f, 250.f, 16);
530 place(
"container", containers,
right,
bottom - 1, 270.f, 1);
531 place(
"container", containers,
right - 0.65f,
bottom - 1.3f, 245.f, 16);
532 if (
r.w >= 6 &&
theme != 2)
533 place(
"seating", seating,
left + 1,
top + 1, 45.f, 1);
538 const int clutterAttempts = clampInt(
int(std::ceil(propDensity * 1.5f)), 0, 2);
539 const std::pair<int, int> clutterSpots[] = {
541 for (
int i = 0; i < clutterAttempts; ++i) {
542 const auto &spot = clutterSpots[(size_t(i) + roomIndex) % 4];
543 place(
"clutter", clutter, spot.first, spot.second,
544 float((i +
int(roomIndex)) % 4) * 90.f, 16);
550 if (propDensity >= 0.35f) {
553 const int roomArea =
r.w *
r.h;
554 const int targetProps = clampInt(
555 4 +
int(std::ceil(propDensity *
float(roomArea) * 0.28f)), 4, 16);
556 const std::pair<int, int> fillerSpots[] = {
560 for (
int attempt = 0;
561 attempt < 24 && objectSerial - propsBeforeRoom < targetProps; ++attempt) {
562 const auto &spot = fillerSpots[(size_t(attempt) + roomIndex) % 6];
563 if ((attempt & 1) == 0)
564 place(
"container", containers, spot.first, spot.second,
565 float((attempt +
int(roomIndex)) % 4) * 90.f, 1);
567 place(
"container", containers,
float(spot.first) + 0.35f,
568 float(spot.second) - 0.25f,
569 float((attempt * 37) % 360), 16);
572 minimumRoomProps = std::min(minimumRoomProps, objectSerial - propsBeforeRoom);
578 const uint32_t lightThreshold = uint32_t(corridorLightDensity * 1000.f);
579 for (
int y = 1;
y <
h - 1; ++
y) {
580 for (
int x = 1;
x <
w - 1; ++
x) {
582 const uint32_t
hash = uint32_t(
x) * 73856093u ^ uint32_t(
y) * 19349663u ^
seed;
583 if ((
hash % 1000u) >= lightThreshold)
continue;
585 place(
"light", wallLights,
float(
x),
float(
y), 90.f, 2 | 4);
587 place(
"light", wallLights,
float(
x),
float(
y), 270.f, 2 | 4);
589 place(
"light", wallLights,
float(
x),
float(
y), 180.f, 2 | 4);
591 place(
"light", wallLights,
float(
x),
float(
y), 0.f, 2 | 4);
600 std::vector<StairCandidate> stairCandidates;
601 for (
const Rect &
r : rooms) {
602 for (
int x =
r.x + 1;
x <
r.x +
r.w - 1; ++
x) {
603 if ((stairSideMask & 1) && uint32_t(out.getCell(
x,
r.y - 1)) ==
Semantic::Wall)
604 stairCandidates.push_back({
x,
r.y, 180.f, 1});
605 if ((stairSideMask & 2) && uint32_t(out.getCell(
x,
r.y +
r.h)) ==
Semantic::Wall)
606 stairCandidates.push_back({
x,
r.y +
r.h - 1, 0.f, 2});
608 for (
int y =
r.y + 1;
y <
r.y +
r.h - 1; ++
y) {
609 if ((stairSideMask & 4) && uint32_t(out.getCell(
r.x - 1,
y)) ==
Semantic::Wall)
610 stairCandidates.push_back({
r.x,
y, 270.f, 4});
611 if ((stairSideMask & 8) && uint32_t(out.getCell(
r.x +
r.w,
y)) ==
Semantic::Wall)
612 stairCandidates.push_back({
r.x +
r.w - 1,
y, 90.f, 8});
615 std::vector<size_t> selectedStairs;
616 for (
int entrance = 0;
617 entrance < stairCount && selectedStairs.size() < stairCandidates.size(); ++entrance) {
618 size_t best = size_t(
seed) % stairCandidates.size();
619 int bestDistance = -1;
620 if (!selectedStairs.empty()) {
621 for (
size_t candidate = 0; candidate < stairCandidates.size(); ++candidate) {
622 bool duplicate =
false;
624 for (
size_t chosen : selectedStairs) {
625 if (stairCandidates[candidate].
x == stairCandidates[chosen].
x &&
626 stairCandidates[candidate].
y == stairCandidates[chosen].
y) {
630 nearest = std::min(nearest,
631 std::abs(stairCandidates[candidate].
x - stairCandidates[chosen].
x) +
632 std::abs(stairCandidates[candidate].
y - stairCandidates[chosen].
y));
634 if (!duplicate && nearest > bestDistance) {
635 bestDistance = nearest;
639 if (bestDistance < 0)
break;
641 selectedStairs.push_back(
best);
642 const StairCandidate &stairs = stairCandidates[
best];
643 out.addAssetObject(
"stairs" + std::to_string(entrance),
"stairs",
644 pickAsset(stairsAssets, rng),
float(stairs.x),
float(stairs.y),
645 1.f, 1.f, stairs.rotation, 64);
646 occupied.insert(cellKey(stairs.x, stairs.y));
649 std::vector<std::pair<int, int>> walkable;
650 for (
int y = 0;
y <
h; ++
y)
651 for (
int x = 0;
x <
w; ++
x)
652 if (isWalkable(uint32_t(out.getCell(
x,
y))) && !
occupied.count(cellKey(
x,
y)))
653 walkable.emplace_back(
x,
y);
654 if (!walkable.empty()) {
655 auto pick = [&](uint32_t
s) {
656 return walkable[(
seed * 1664525u +
s * 1013904223u) % walkable.size()];
659 out.addObjectAt(
"spawn",
"spawn",
float(
a.first),
float(
a.second));
663 out.setMeta(
"algorithm",
"level.roguelike");
664 out.setMeta(
"seed", std::to_string(
seed));
665 out.setMeta(
"rooms", std::to_string(rooms.size()));
666 out.setMeta(
"floorPattern",
pattern);
667 out.setMeta(
"decorTiles", std::to_string(decorTiles.size()));
668 out.setMeta(
"corridorStyle", style);
669 out.setMeta(
"layoutStyle",
layout);
670 out.setMeta(
"connectionStyle", connections);
671 out.setMeta(
"assetPack",
params.getString(
"assetPack",
"semantic-default"));
672 out.setMeta(
"placedProps", std::to_string(objectSerial));
673 out.setMeta(
"minimumRoomProps",
674 std::to_string(minimumRoomProps == std::numeric_limits<int>::max()
675 ? 0 : minimumRoomProps));
676 out.setMeta(
"stairs", std::to_string(selectedStairs.size()));
679 static const char *architectureRoles[] = {
680 "wall",
"wallCorner",
"wallJunction",
"wallDoor",
"wallWindow",
"wallHalf",
681 "wallBroken",
"wallScaffold",
"floor",
"floorBroken",
"floorDirt",
"floorWood",
682 "floorGrate",
"floorFoundation",
"ceiling",
"stairs",
"stairsRail",
"door",
"barrier"};
683 for (
const char *role : architectureRoles) {
684 const std::string
key = std::string(
"assets.") + role;
693 auto descriptor = GeneratorDescriptor::grid(
"level.roguelike",
"Roguelike Level",
"Dungeon", 9, 9);
694 descriptor.params.push_back(ParamDescriptor::integer(
"roomCount",
"Room Count", 9, 1, 256));
695 descriptor.params.push_back(ParamDescriptor::integer(
"roomMin",
"Minimum Room Size", 4, 2, 128));
696 descriptor.params.push_back(ParamDescriptor::integer(
"roomMax",
"Maximum Room Size", 8, 2, 256));
697 descriptor.params.push_back(ParamDescriptor::integer(
"padding",
"Room Padding", 1, 0, 4));
698 descriptor.params.push_back(ParamDescriptor::integer(
"spacing",
"Room Spacing", 2, 0, 8));
699 descriptor.params.push_back(ParamDescriptor::integer(
700 "clusterGapMin",
"Cluster Minimum Gap", 2, 1, 8));
701 descriptor.params.push_back(ParamDescriptor::integer(
702 "clusterGapMax",
"Cluster Maximum Gap", 4, 1, 12));
703 descriptor.params.push_back(ParamDescriptor::integer(
704 "clusterBranchBias",
"Cluster Branch Bias", 0, 0, 4));
705 descriptor.params.push_back(ParamDescriptor::integer(
"corridorWidth",
"Corridor Width", 1, 1, 3));
706 descriptor.params.push_back(ParamDescriptor::integer(
"stairCount",
"Stair Count", 1, 0, 4));
707 descriptor.params.push_back(ParamDescriptor::integer(
708 "stairSideMask",
"Stair Side Mask", 15, 1, 15));
709 descriptor.params.push_back(ParamDescriptor::choice(
"layoutStyle",
"Room Layout",
"grid",
710 {
"grid",
"clustered"}));
711 descriptor.params.push_back(ParamDescriptor::choice(
"connectionStyle",
"Room Connections",
713 {
"sequential",
"nearest",
"growth"}));
714 descriptor.params.push_back(ParamDescriptor::choice(
"corridorStyle",
"Corridor Style",
"l",
715 {
"l",
"straight",
"diagonal"}));
716 descriptor.params.push_back(ParamDescriptor::choice(
"floorPattern",
"Floor Pattern",
"brick",
717 {
"brick",
"checker",
"plank",
"plain",
"cobble"}));
718 descriptor.params.push_back(ParamDescriptor::integer(
"floorVariants",
"Floor Variants", 4, 1, 15));
719 descriptor.params.push_back(ParamDescriptor::floating(
"decorDensity",
"Decoration Density", 0.05f, 0.f,
721 descriptor.params.push_back(ParamDescriptor::choice(
"decorSet",
"Decoration Set",
"mixed",
722 {
"mixed",
"pillars",
"treasure",
"none"}));
723 descriptor.params.push_back(ParamDescriptor::floating(
"propDensity",
"Prop Density", 0.16f,
725 descriptor.params.push_back(ParamDescriptor::floating(
"corridorLightDensity",
726 "Corridor Light Density", 0.035f,
727 0.f, 0.25f, 0.005f));
728 descriptor.params.push_back(ParamDescriptor::boolean(
"autotile",
"Autotile",
true));
734 return grid.getWidth() > 0 && grid.getHeight() > 0;
738 if (grid.getWidth() <= 0 || grid.getHeight() <= 0)
741 "procgen.gridGraph"));
742 constexpr int dx[] = {0, 1, 0, -1, 1, 1, -1, -1};
743 constexpr int dy[] = {-1, 0, 1, 0, -1, 1, 1, -1};
744 for (
int y = 0;
y < grid.getHeight(); ++
y) {
745 for (
int x = 0;
x < grid.getWidth(); ++
x) {
747 if (uint32_t(grid.getCell(
x,
y)) != Semantic::Empty)
751 grid.setDetail(
x,
y,
mask);
758 std::random_device rd;
759 const uint32_t
v = rd();
760 return v == 0 ? 1u :
v;
std::array< std::uint8_t, 32 > hash
std::vector< eve::ProcgenProbeDesc > lights
std::array< float, 4 > rotation
RoadLaneDirection direction
std::map< Cell, int > best
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
Registry for executable generators and their reflection metadata.
void registerAlgorithm(const std::string &id, GeneratorFn fn)
Register an algorithm without metadata for backward compatibility.
Intermediate 2D generation result. cells store semantic ids (see Semantic.h), not tile GIDs — convert...
std::vector< ParamSpec > params
constexpr uint32_t Corridor
double sample(const Heightmap &map, double u, double v)
Sample.
void registerRoguelikeGenerator(GeneratorRegistry ®istry)
Register the "level.roguelike" algorithm (idempotent).
eve::Result< void > autotileOccupiedGridInPlace(Grid2D &grid)
Write an eight-neighbour occupancy mask into every non-empty cell's detail value.
bool autotileGridInPlace(Grid2D &grid)
Post-process any generated Grid2D: fill each wall cell's detail with an 8-bit neighbour mask describi...
uint32_t randomSeedValue()
Produce a fresh seed suitable for regenerating a level (never 0).