diff --git a/src/tiles.lua b/src/tiles.lua index 9a5fa42..aa8cb2d 100644 --- a/src/tiles.lua +++ b/src/tiles.lua @@ -14,6 +14,14 @@ Tiles.DOOR_LOCKED = "x" Tiles.TRAP = "^" Tiles.WATER = "~" Tiles.RUBBLE = ":" +Tiles.LAVA = "%" +Tiles.CHASM = "V" +Tiles.ICE = "-" +Tiles.LEVER = "H" +Tiles.LEVER_PULLED = "I" +Tiles.VAULT_DOOR = "K" +Tiles.GATE_CLOSED = "8" +Tiles.GATE_OPEN = "9" local props = { [Tiles.WALL] = { walkable = false, blocksSight = true, door = false }, @@ -26,6 +34,14 @@ local props = { [Tiles.TRAP] = { walkable = true, blocksSight = false, door = false }, [Tiles.WATER] = { walkable = true, blocksSight = false, door = false }, [Tiles.RUBBLE] = { walkable = true, blocksSight = false, door = false }, + [Tiles.LAVA] = { walkable = true, blocksSight = false, door = false, lava = true }, + [Tiles.CHASM] = { walkable = true, blocksSight = false, door = false, chasm = true }, + [Tiles.ICE] = { walkable = true, blocksSight = false, door = false, ice = true }, + [Tiles.LEVER] = { walkable = true, blocksSight = false, door = false }, + [Tiles.LEVER_PULLED] = { walkable = true, blocksSight = false, door = false }, + [Tiles.VAULT_DOOR] = { walkable = false, blocksSight = true, door = false, vault = true }, + [Tiles.GATE_CLOSED] = { walkable = false, blocksSight = true, door = false, gate = true }, + [Tiles.GATE_OPEN] = { walkable = true, blocksSight = false, door = false, gate = true }, } local wallProps = props[Tiles.WALL] @@ -50,4 +66,24 @@ function Tiles.isClosedDoor(glyph) return glyph == Tiles.DOOR_CLOSED or glyph == Tiles.DOOR_LOCKED end +function Tiles.isLava(glyph) + return Tiles.of(glyph).lava == true +end + +function Tiles.isChasm(glyph) + return Tiles.of(glyph).chasm == true +end + +function Tiles.isIce(glyph) + return Tiles.of(glyph).ice == true +end + +function Tiles.isVaultDoor(glyph) + return Tiles.of(glyph).vault == true +end + +function Tiles.isGate(glyph) + return Tiles.of(glyph).gate == true +end + return Tiles diff --git a/src/titles.lua b/src/titles.lua new file mode 100644 index 0000000..2f9324d --- /dev/null +++ b/src/titles.lua @@ -0,0 +1,37 @@ +-- Renown-gated titles: a flavor rank meta.lua's own banked renown climbs +-- through, one tier per crossed threshold, entirely separate from a +-- character's class - a title describes how far this save file's whole +-- history has come, not what any single run's build looked like. + +local Titles = {} + +Titles.LADDER = { + { threshold = 0, name = "Whelp" }, + { threshold = 50, name = "Delver" }, + { threshold = 150, name = "Warden" }, + { threshold = 350, name = "Vanquisher" }, + { threshold = 700, name = "Warren Lord" }, + { threshold = 1500, name = "Undying" }, +} + +-- The highest title whose threshold `renown` clears, walked from the top of +-- the ladder down so a renown total that clears several thresholds at once +-- always resolves to the hardest-earned name rather than the first match. +function Titles.forRenown(renown) + for i = #Titles.LADDER, 1, -1 do + if renown >= Titles.LADDER[i].threshold then return Titles.LADDER[i].name end + end + return Titles.LADDER[1].name +end + +-- Renown still needed to clear the next threshold above `renown`, or nil +-- once renown has already cleared the top of the ladder and there is +-- nothing left to climb toward. +function Titles.toNext(renown) + for _, tier in ipairs(Titles.LADDER) do + if renown < tier.threshold then return tier.threshold - renown end + end + return nil +end + +return Titles diff --git a/src/vaults.lua b/src/vaults.lua new file mode 100644 index 0000000..d0a9659 --- /dev/null +++ b/src/vaults.lua @@ -0,0 +1,64 @@ +-- Lever-gated treasure rooms. A vault door only swings open once every lever +-- sharing its id has been pulled; the builder places the door at a doorway +-- pinch point and scatters its levers only on the side already reachable +-- without crossing it, so the puzzle is always solvable and the reward +-- sealed behind the door is always genuinely gated by it (see builder.lua's +-- vault placement, drawn dead last so it never perturbs any earlier +-- system's rng sequence). + +local Items = require("items") +local Affixes = require("affixes") + +local Vaults = {} + +local treasureWeapons = { + { name = "vault greatsword", power = 7 }, + { name = "vault warhammer", power = 8 }, +} +local treasureArmors = { + { name = "vault aegis", power = 6 }, +} + +-- A guaranteed affix on a base already well above anything the floor's own +-- loot table can roll at this depth - the deterministic reward for solving +-- the puzzle, distinct from a normal drop's coin-flip affix (see +-- affixes.rollWeapon/rollArmor). +function Vaults.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 + +function Vaults.doorFor(level, id) + for _, door in ipairs(level.vaultDoors) do + if door.id == id then return door end + end + return nil +end + +local function allPulled(level, id) + for _, lever in ipairs(level.vaultLevers) do + if lever.id == id and not lever.pulled then return false end + end + return true +end + +-- Pulls `lever`, an idempotent no-op returning false if it was already +-- thrown. Returns (true, door) the instant this pull completes every lever +-- sharing its id and the door has not opened yet, or (true, nil) for a pull +-- that leaves the vault still sealed (or already open). +function Vaults.pull(level, lever) + if lever.pulled then return false end + lever.pulled = true + if not allPulled(level, lever.id) then return true, nil end + local door = Vaults.doorFor(level, lever.id) + if door and not door.opened then return true, door end + return true, nil +end + +return Vaults