114 lines
4.4 KiB
Lua
114 lines
4.4 KiB
Lua
-- Pushable blocks: a block occupies a floor cell and blocks movement and
|
|
-- pathing exactly like a wall, but shoving it (see Game:movePlayer) relocates
|
|
-- it one cell in the push direction instead of just refusing the move, the
|
|
-- same "walk into it to interact" shape a closed door or a vault lever
|
|
-- already has. A block shoved into lava solidifies into rubble and one
|
|
-- shoved into a chasm fills the gap into plain floor - both permanently
|
|
-- consume the block, since it has nothing left to rest on once the hazard
|
|
-- it corrected is gone. Shoved onto a pressure plate instead, the block
|
|
-- survives and holds its linked gate open for as long as it stays put (see
|
|
-- Blocks.updateGates) - unlike vaults.lua's lever, a gate reseals the
|
|
-- instant the weight holding it open moves off the plate again.
|
|
|
|
local Tiles = require("tiles")
|
|
local Items = require("items")
|
|
local Affixes = require("affixes")
|
|
|
|
local Blocks = {}
|
|
|
|
local treasureWeapons = {
|
|
{ name = "gate-forged blade", power = 7 },
|
|
{ name = "gate-forged maul", power = 8 },
|
|
}
|
|
local treasureArmors = {
|
|
{ name = "gate-forged plate", power = 6 },
|
|
}
|
|
|
|
-- A guaranteed affix on a base already well above anything the floor's own
|
|
-- loot table can roll at this depth, mirroring vaults.lua's own Vaults.
|
|
-- treasure but with names that never claim to come from a vault.
|
|
function Blocks.treasure(rng, depth)
|
|
if rng:chance(1, 2) then
|
|
local base = treasureWeapons[rng:range(1, #treasureWeapons)]
|
|
local item = Items.weapon(base.name, base.power + depth // 3)
|
|
return Affixes.decorate(item, Affixes.weapon[rng:range(1, #Affixes.weapon)])
|
|
end
|
|
local base = treasureArmors[rng:range(1, #treasureArmors)]
|
|
local item = Items.armor(base.name, base.power + depth // 3)
|
|
return Affixes.decorate(item, Affixes.armor[rng:range(1, #Affixes.armor)])
|
|
end
|
|
|
|
-- Whether a cell a block might be shoved into can actually receive it: in
|
|
-- bounds, not a wall, a door of any kind, or a gate, and not already host to
|
|
-- another block or a living entity. Lava and chasm cells count as
|
|
-- receiving even though Blocks.push special-cases what happens to them
|
|
-- next, rather than refusing the shove outright.
|
|
function Blocks.canReceive(level, x, y)
|
|
if not level.map:inBounds(x, y) then return false end
|
|
local tile = level.map:get(x, y)
|
|
if tile == Tiles.WALL or Tiles.isDoor(tile) or Tiles.isVaultDoor(tile) or Tiles.isGate(tile) then
|
|
return false
|
|
end
|
|
if level:blockAt(x, y) or level:entityAt(x, y) then return false end
|
|
return true
|
|
end
|
|
|
|
local function remove(level, block)
|
|
for i, b in ipairs(level.blocks) do
|
|
if b == block then
|
|
table.remove(level.blocks, i)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Shoves `block` by (dx, dy) if the far cell can receive it. Returns
|
|
-- (true, message) on a successful shove, resolving whatever lava or chasm
|
|
-- it lands in along the way (Blocks.remove), or (false) when the far cell
|
|
-- refuses it, exactly like bumping into a wall.
|
|
function Blocks.push(level, block, dx, dy)
|
|
local nx, ny = block.x + dx, block.y + dy
|
|
if not Blocks.canReceive(level, nx, ny) then return false end
|
|
local tile = level.map:get(nx, ny)
|
|
if tile == Tiles.LAVA then
|
|
level.map:set(nx, ny, Tiles.RUBBLE)
|
|
remove(level, block)
|
|
return true, "the block hisses into the lava and cools into rubble"
|
|
end
|
|
if tile == Tiles.CHASM then
|
|
level.map:set(nx, ny, Tiles.FLOOR)
|
|
remove(level, block)
|
|
return true, "the block tumbles into the chasm and fills it"
|
|
end
|
|
block.x, block.y = nx, ny
|
|
return true, "you shove the block"
|
|
end
|
|
|
|
-- Whether any plate linked to `gate` currently carries a block's weight.
|
|
local function gateHeld(level, gate)
|
|
for _, plate in ipairs(level.plates) do
|
|
if plate.gateX == gate.x and plate.gateY == gate.y and level:blockAt(plate.x, plate.y) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Re-evaluates every gate on the level against its own plates' current
|
|
-- weight, flipping both its tile glyph and its `open` field the instant a
|
|
-- plate gains or loses a block. Called after any block shove (Game:
|
|
-- movePlayer) so a gate reacts immediately rather than waiting for a full
|
|
-- turn tick, the same way Vaults.pull's door flip is immediate on the pull
|
|
-- that completes it.
|
|
function Blocks.updateGates(level)
|
|
for _, gate in ipairs(level.gates) do
|
|
local held = gateHeld(level, gate)
|
|
if held ~= gate.open then
|
|
gate.open = held
|
|
level.map:set(gate.x, gate.y, held and Tiles.GATE_OPEN or Tiles.GATE_CLOSED)
|
|
end
|
|
end
|
|
end
|
|
|
|
return Blocks
|