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