feat(mapgen): add connectivity repair and prefab vaults
This commit is contained in:
1 parent
9076c6c729
commit
de801793e1
2 files changed
+166
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
Reference in new issue
Block a user