feat: build out builder

This commit is contained in:
dualfroz committed 2026-09-02 15:53:52 +02:00
1 parent 364ea34db4
commit 72e2969146
1 file changed
+131
+131
View File
@@ -855,14 +855,145 @@ function Builder.build(depth, rng, options)
-- can never end up sealed unreachably deep inside solid rock no digger -- can never end up sealed unreachably deep inside solid rock no digger
-- could ever find. Its monster and loot markers (see gen/prefab.lua) draw -- could ever find. Its monster and loot markers (see gen/prefab.lua) draw
-- from the same eligible roster and item tables every earlier pass in this -- from the same eligible roster and item tables every earlier pass in this
-- floor already used. Drawn dead last of all before the grave below -
-- which must stay the true last word (see its own comment) - so this can
-- never perturb any earlier system's rng sequence either.
local prefabVaults = {}
if depth >= 3 and rng:chance(1, 5) then
local vault = Prefab.vaults[rng:range(1, #Prefab.vaults)]
local w, h = Prefab.size(vault)
local vaultReach = reachSet(map, upX, upY, false)
for _ = 1, 40 do
local ox = rng:range(2, map.width - w)
local oy = rng:range(2, map.height - h)
if Prefab.fits(map, vault, ox, oy) then
local untouched = true
for y = oy, oy + h - 1 do
for x = ox, ox + w - 1 do
if map:get(x, y) ~= Tiles.WALL then untouched = false end
end
end
local bordersReachable = false
for y = oy - 1, oy + h do
for x = ox - 1, ox + w do
local onPerimeter = x < ox or x > ox + w - 1 or y < oy or y > oy + h - 1
if onPerimeter and map:inBounds(x, y) and vaultReach[map:index(x, y)] then
bordersReachable = true
end
end
end
if untouched and bordersReachable then
local stamp = Prefab.stamp(map, vault, ox, oy)
for _, spot in ipairs(stamp.treasure) do
floorItems[#floorItems + 1] = { x = spot.x, y = spot.y, item = Vaults.treasure(rng, depth) }
mark(spot.x, spot.y)
end
for _, spot in ipairs(stamp.loot) do
floorItems[#floorItems + 1] = { x = spot.x, y = spot.y, item = rollItem(rng, depth) }
mark(spot.x, spot.y)
end
for _, spot in ipairs(stamp.monsters) do
local spec = eligible[rng:range(1, #eligible)]
local monster = Monsters.create(spec, #entities + 1, spot.x, spot.y, depth)
monster.awareness = Stealth.DORMANT
entities[#entities + 1] = monster
mark(spot.x, spot.y)
end
prefabVaults[#prefabVaults + 1] = { name = stamp.name, x = ox, y = oy, w = w, h = h }
break
end
end
end
end
-- A net turns up from depth five on, rarer than a miner's pick, drawn dead
-- last of all - after even the prefab vault pass above - so it can never
-- perturb any earlier system's rng sequence: the one way a run carries the
-- means to subdue a battered monster alive instead of finishing it off
-- (see captives.lua), rather than a strictly worse copy of an ordinary
-- weapon. Gated this deep so it can never turn up in the fixed shallow
-- worlds the win-condition tests build with a low --maxDepth.
if depth >= 5 and rng:chance(1, 6) then
local x, y = pickFree(reachable, rng, freeFloor)
if x then
floorItems[#floorItems + 1] = { x = x, y = y, item = Items.net() }
mark(x, y)
end
end
-- A rift shard turns up from depth four on, rarer than a net, drawn dead
-- last of all - after the net above - so it can never perturb any earlier
-- system's rng sequence: fuel for the one crossing a rift (below) actually
-- costs (see branches.lua), found a couple of floors ahead of where a rift
-- can itself first appear so a run always has a real chance to be carrying
-- one the day it matters.
if depth >= 4 and rng:chance(1, 6) then
local x, y = pickFree(reachable, rng, freeFloor)
if x then
floorItems[#floorItems + 1] = { x = x, y = y, item = Items.riftstone() }
mark(x, y)
end
end
-- A rift tears open rarer still, only from depth six on, drawn dead last
-- of all - after the shard above - so it can never perturb any earlier
-- system's rng sequence: a shimmering, unstable seam in the floor a
-- carried rift shard alone can stabilize into a crossing onto a short,
-- harsher pocket floor (see branches.lua and Game:enterRift). Plain
-- position-keyed level data exactly like a shrine or a node, never a tile
-- of its own, so it needs no new entry in tiles.lua.
local rifts = {}
if depth >= 6 and rng:chance(1, 8) then
local x, y = pickFree(reachable, rng, freeFloor)
if x then
rifts[#rifts + 1] = { x = x, y = y }
mark(x, y)
end
end
-- A previous run's grave turns up rarer than a pick, dead last of all -
-- after the lockpick, chest, secret door, biome, gate and net pass above -
-- so it can never perturb any earlier system's rng sequence: cross-run
-- bones (see graves.lua), rolled only when the caller actually supplies
-- any - a run without --meta, or a fresh meta with no recorded deaths
-- yet, makes no extra rng draw here at all, so its floors stay
-- byte-identical to a build with no graves system in play.
if options and options.graves and #options.graves > 0 then
local eligibleGraves = Graves.eligibleFor(options.graves, depth)
if #eligibleGraves > 0 and rng:chance(1, 5) then
local x, y = pickFree(reachable, rng, freeFloor)
if x then
local grave = eligibleGraves[rng:range(1, #eligibleGraves)]
floorItems[#floorItems + 1] = { x = x, y = y, item = Graves.floorItem(grave) }
mark(x, y)
end
end
end
return Level.new({ return Level.new({
depth = depth, depth = depth,
generator = generator, generator = generator,
weather = weather,
biome = biome.key,
map = map, map = map,
entities = entities, entities = entities,
floorItems = floorItems, floorItems = floorItems,
traps = traps, traps = traps,
lights = lights, lights = lights,
hazards = hazards,
shrines = shrines,
questBoards = questBoards,
cauldrons = cauldrons,
nodes = nodes,
chests = chests,
vaultLevers = vaultLevers,
vaultDoors = vaultDoors,
secretDoors = secretDoors,
blocks = blocks,
plates = plates,
gates = gates,
prefabVaults = prefabVaults,
rifts = rifts,
dark = dark, dark = dark,
explored = {}, explored = {},
upX = upX, upY = upY, upX = upX, upY = upY,