feat: extend builder
This commit is contained in:
1 parent
86b7017214
commit
ef58d96008
1 file changed
+173
+173
@@ -336,6 +336,179 @@ function Builder.build(depth, rng, options)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- A companion's bonding charm is the rarest of the depth-gated placements
|
||||||
|
-- and, unlike the others, drawn last so it can never perturb the rng
|
||||||
|
-- sequence any earlier system (traps, shrines, the shop, the amulet) relies
|
||||||
|
-- on for its own placement.
|
||||||
|
if depth >= 3 and rng:chance(1, 5) then
|
||||||
|
local x, y = pickFree(reachable, rng, freeFloor)
|
||||||
|
if x then
|
||||||
|
local key = Companions.order[rng:range(1, #Companions.order)]
|
||||||
|
floorItems[#floorItems + 1] = { x = x, y = y, item = Items.charm(key) }
|
||||||
|
mark(x, y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Rarer still, and drawn last of all for the same rng-ordering reason as
|
||||||
|
-- the charm above: a named artifact with a fixed, always-on mechanic.
|
||||||
|
if depth >= 5 and rng:chance(1, 6) then
|
||||||
|
local x, y = pickFree(reachable, rng, freeFloor)
|
||||||
|
if x then
|
||||||
|
local key = Artifacts.order[rng:range(1, #Artifacts.order)]
|
||||||
|
floorItems[#floorItems + 1] = { x = x, y = y, item = Artifacts.create(key) }
|
||||||
|
mark(x, y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A trap kit lets the player plant one of these same effects
|
||||||
|
-- themselves (see Game:useItem's KIT branch and Game:checkTrapFor, which
|
||||||
|
-- springs a planted trap on a monster exactly as a builder-placed one
|
||||||
|
-- springs on the player). Drawn last of all so it can never perturb any
|
||||||
|
-- earlier system's rng sequence, the charm and artifact included.
|
||||||
|
if depth >= 2 and rng:chance(1, 4) then
|
||||||
|
local x, y = pickFree(reachable, rng, freeFloor)
|
||||||
|
if x then
|
||||||
|
floorItems[#floorItems + 1] = { x = x, y = y, item = Items.kit(trapEffects[rng:range(1, #trapEffects)]) }
|
||||||
|
mark(x, y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A named boss rarer still guards the deepest floors, drawn dead last of
|
||||||
|
-- all for the same rng-ordering reason as the kit above: it can never
|
||||||
|
-- perturb any earlier system's placement, artifacts included.
|
||||||
|
if depth >= 6 and rng:chance(1, 5) then
|
||||||
|
local eligible = Bosses.forDepth(depth)
|
||||||
|
if #eligible > 0 then
|
||||||
|
local x, y = pickFree(reachable, rng, freeFloor)
|
||||||
|
if x then
|
||||||
|
local spec = eligible[rng:range(1, #eligible)]
|
||||||
|
entities[#entities + 1] = Bosses.create(spec, #entities + 1, x, y)
|
||||||
|
mark(x, y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A bounty board rarer than a trap kit but commoner than an artifact,
|
||||||
|
-- offering a single fixed pact the moment the player steps onto it (see
|
||||||
|
-- Game:tryAcceptQuest). Drawn dead last of all for the same rng-ordering
|
||||||
|
-- reason as the boss above: it can never perturb any earlier system's
|
||||||
|
-- placement, the boss included.
|
||||||
|
local questBoards = {}
|
||||||
|
if depth >= 2 and rng:chance(1, 4) then
|
||||||
|
local x, y = pickFree(reachable, rng, freeFloor)
|
||||||
|
if x then
|
||||||
|
questBoards[#questBoards + 1] = {
|
||||||
|
x = x, y = y, claimed = false,
|
||||||
|
quest = Quests.generate(rng, depth, Builder.maxDepth(options)),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Herbs dot every floor from the first, foraging material rather than a
|
||||||
|
-- depth-gated reward. Drawn dead last of all, after the quest board, so
|
||||||
|
-- they can never perturb any earlier system's rng sequence.
|
||||||
|
local herbCount = rng:range(2, 4)
|
||||||
|
for _ = 1, herbCount do
|
||||||
|
local x, y = pickFree(reachable, rng, freeFloor)
|
||||||
|
if x then
|
||||||
|
local key = Alchemy.order[rng:range(1, #Alchemy.order)]
|
||||||
|
floorItems[#floorItems + 1] = { x = x, y = y, item = Alchemy.herb(key) }
|
||||||
|
mark(x, y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A cauldron to brew those herbs at, rarer than the herbs themselves and
|
||||||
|
-- gated to depth 2+ so the entrance floor is foraging-only. Drawn last of
|
||||||
|
-- all for the same rng-ordering reason as the herbs above.
|
||||||
|
local cauldrons = {}
|
||||||
|
if depth >= 2 and rng:chance(1, 4) then
|
||||||
|
local x, y = pickFree(reachable, rng, freeFloor)
|
||||||
|
if x then
|
||||||
|
cauldrons[#cauldrons + 1] = { x = x, y = y }
|
||||||
|
mark(x, y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Volatile terrain rarer than a cauldron, gated to depth 4+ so the entry
|
||||||
|
-- floors are always safe: a lava pool of two to four adjoining cells that
|
||||||
|
-- burns anyone standing on it (see Game:checkTerrainFor), a single chasm
|
||||||
|
-- pit that drops the player to the floor below and swallows a monster
|
||||||
|
-- whole (capped to floors that still have one to fall onto), and a patch
|
||||||
|
-- of ice that sends a traveller sliding. Drawn dead last of all for the
|
||||||
|
-- same rng-ordering reason as the cauldron above.
|
||||||
|
if depth >= 4 and rng:chance(1, 4) then
|
||||||
|
local x, y = pickFree(reachable, rng, freeFloor)
|
||||||
|
if x then
|
||||||
|
map:set(x, y, Tiles.LAVA)
|
||||||
|
mark(x, y)
|
||||||
|
for _ = 1, rng:range(1, 3) do
|
||||||
|
local dir = NEIGHBOURS[rng:range(1, #NEIGHBOURS)]
|
||||||
|
local nx, ny = x + dir[1], y + dir[2]
|
||||||
|
if freeFloor(nx, ny) then
|
||||||
|
map:set(nx, ny, Tiles.LAVA)
|
||||||
|
mark(nx, ny)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if depth >= 4 and depth < Builder.maxDepth(options) and rng:chance(1, 5) then
|
||||||
|
local x, y = pickFree(reachable, rng, freeFloor)
|
||||||
|
if x then
|
||||||
|
map:set(x, y, Tiles.CHASM)
|
||||||
|
mark(x, y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if depth >= 4 and rng:chance(1, 4) then
|
||||||
|
local x, y = pickFree(reachable, rng, freeFloor)
|
||||||
|
if x then
|
||||||
|
map:set(x, y, Tiles.ICE)
|
||||||
|
mark(x, y)
|
||||||
|
for _ = 1, rng:range(1, 4) do
|
||||||
|
local dir = NEIGHBOURS[rng:range(1, #NEIGHBOURS)]
|
||||||
|
local nx, ny = x + dir[1], y + dir[2]
|
||||||
|
if freeFloor(nx, ny) then
|
||||||
|
map:set(nx, ny, Tiles.ICE)
|
||||||
|
mark(nx, ny)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Elite promotion rolls last of all, dead after the ice above: it only
|
||||||
|
-- edits stats already-placed entities carry, never a position, so it
|
||||||
|
-- cannot desync reachability - but rng-ordering discipline still applies,
|
||||||
|
-- since it draws from the same stream every earlier placement in this
|
||||||
|
-- floor already relied on.
|
||||||
|
for _, entity in ipairs(entities) do
|
||||||
|
Elites.maybePromote(rng, entity, depth)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A vault seals a treasure behind a lever puzzle instead of a key: a door
|
||||||
|
-- set on an unused doorway pinch point that only swings open once every
|
||||||
|
-- lever scattered on the side already reachable without it has been
|
||||||
|
-- pulled (see vaults.lua). Rarer than a boss and drawn dead last of all so
|
||||||
|
-- it can never perturb any earlier system's rng sequence, the boss and
|
||||||
|
-- quest board included. Falls back to an ordinary closed door whenever the
|
||||||
|
-- floor's geometry cannot host a genuine puzzle (no free doorway, no room
|
||||||
|
-- for its levers, or no cell the door actually gates), so a vault never
|
||||||
|
-- seals content behind an unsolvable or pointless door.
|
||||||
|
local vaultLevers, vaultDoors = {}, {}
|
||||||
|
if depth >= 5 and rng:chance(1, 6) then
|
||||||
|
local doorCandidates = doorwayCandidates(map)
|
||||||
|
for i = #doorCandidates, 1, -1 do
|
||||||
|
if taken(doorCandidates[i].x, doorCandidates[i].y) then
|
||||||
|
table.remove(doorCandidates, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #doorCandidates > 0 then
|
||||||
|
local cell = doorCandidates[rng:range(1, #doorCandidates)]
|
||||||
|
map:set(cell.x, cell.y, Tiles.VAULT_DOOR)
|
||||||
|
mark(cell.x, cell.y)
|
||||||
|
local vaultId = "vault" .. depth
|
||||||
|
local openSide = reachSet(map, upX, upY, false, false)
|
||||||
|
local leverCount = rng:range(2, 3)
|
||||||
return Level.new({
|
return Level.new({
|
||||||
depth = depth,
|
depth = depth,
|
||||||
generator = generator,
|
generator = generator,
|
||||||
|
|||||||
Reference in new issue
Block a user