feat(mapgen): add connectivity repair and prefab vaults

This commit is contained in:
dualfroz committed 2026-08-24 12:25:00 +02:00
1 parent 9076c6c729
commit de801793e1
2 files changed
+166

No files matched your search

+79
View File
@@ -0,0 +1,79 @@
-- Guarantees every walkable cell is reachable. Labels connected components with
-- a deterministic flood fill, then tunnels each secondary component into the
-- largest one along the shortest Manhattan link. Returns the cells of the final
-- reachable region in index order.
local Carve = require("gen.carve")
local Connect = {}
local NEIGHBOURS = { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } }
local function label(map)
local component = {}
local components = {}
for y = 1, map.height do
for x = 1, map.width do
local idx = map:index(x, y)
if map:isWalkable(x, y) and not component[idx] then
local cells = {}
local stack = { { x, y } }
component[idx] = #components + 1
while #stack > 0 do
local cell = stack[#stack]
stack[#stack] = nil
cells[#cells + 1] = { x = cell[1], y = cell[2] }
for _, step in ipairs(NEIGHBOURS) do
local nx, ny = cell[1] + step[1], cell[2] + step[2]
if map:inBounds(nx, ny) and map:isWalkable(nx, ny) then
local nIdx = map:index(nx, ny)
if not component[nIdx] then
component[nIdx] = #components + 1
stack[#stack + 1] = { nx, ny }
end
end
end
end
table.sort(cells, function(a, b)
return map:index(a.x, a.y) < map:index(b.x, b.y)
end)
components[#components + 1] = cells
end
end
end
return components
end
local function nearestPair(from, to, map)
local best, bx, by, tx, ty = math.huge
for _, a in ipairs(from) do
for _, b in ipairs(to) do
local dist = math.abs(a.x - b.x) + math.abs(a.y - b.y)
if dist < best then
best, bx, by, tx, ty = dist, a.x, a.y, b.x, b.y
end
end
end
return bx, by, tx, ty
end
function Connect.ensure(map, rng)
local components = label(map)
if #components == 0 then return {} end
table.sort(components, function(a, b)
if #a ~= #b then return #a > #b end
return map:index(a[1].x, a[1].y) < map:index(b[1].x, b[1].y)
end)
local main = components[1]
for i = 2, #components do
local ax, ay, bx, by = nearestPair(components[i], main, map)
Carve.corridor(map, ax, ay, bx, by, rng)
end
local reachable = label(map)[1] or main
table.sort(reachable, function(a, b)
return map:index(a.x, a.y) < map:index(b.x, b.y)
end)
return reachable
end
return Connect
+87
View File
@@ -0,0 +1,87 @@
-- Hand-authored vaults stamped onto a generated level. Each vault is an ASCII
-- template; markers flag where the dungeon layer should later drop treasure or
-- guardians. Stamping is deterministic given a fixed anchor.
local Tiles = require("tiles")
local Prefab = {}
Prefab.vaults = {
{
name = "treasureCell",
rows = {
"#########",
"#.......#",
"#.#####.#",
"#.#*M*#.#",
"#.#*L*#+#",
"#.#####.#",
"#.......#",
"#########",
},
},
{
name = "guardPost",
rows = {
"#######",
"#M...M#",
"#..*..#",
"#M...M#",
"#######",
},
},
{
name = "flooded",
rows = {
"#########",
"#~~...~~#",
"#~..*..~#",
"#..MLM..#",
"#~..*..~#",
"#~~...~~#",
"#########",
},
},
}
local glyphFor = {
["#"] = Tiles.WALL,
["."] = Tiles.FLOOR,
["+"] = Tiles.DOOR_CLOSED,
["~"] = Tiles.WATER,
["*"] = Tiles.FLOOR,
["M"] = Tiles.FLOOR,
["L"] = Tiles.FLOOR,
}
function Prefab.size(vault)
return #vault.rows[1], #vault.rows
end
-- Returns true when a vault fits entirely inside the map interior with a wall
-- gap to the border, false otherwise. No cell content is inspected here; the
-- caller decides overlap policy.
function Prefab.fits(map, vault, ox, oy)
local w, h = Prefab.size(vault)
return ox >= 2 and oy >= 2 and ox + w - 1 <= map.width - 1 and oy + h - 1 <= map.height - 1
end
-- Stamps a vault at (ox, oy) and returns lists of treasure and monster anchors
-- in map coordinates so the dungeon layer can populate them.
function Prefab.stamp(map, vault, ox, oy)
local treasure, monsters, loot = {}, {}, {}
for row = 1, #vault.rows do
local line = vault.rows[row]
for col = 1, #line do
local mark = line:sub(col, col)
local x, y = ox + col - 1, oy + row - 1
map:set(x, y, glyphFor[mark] or Tiles.FLOOR)
if mark == "*" then treasure[#treasure + 1] = { x = x, y = y } end
if mark == "M" then monsters[#monsters + 1] = { x = x, y = y } end
if mark == "L" then loot[#loot + 1] = { x = x, y = y } end
end
end
return { name = vault.name, treasure = treasure, monsters = monsters, loot = loot }
end
return Prefab