-- Wayside altars the player can pray at for a permanent boon at a gold price. -- A shrine's favor wears thin with use: each prayer at the same altar is more -- likely to backfire into a curse than the last, so greedily re-praying at a -- single shrine is a real risk rather than a free stat pump. Shrines are plain -- data on level.shrines, keyed by position, so they serialize like traps and -- hazards do with no special save-format handling. local Status = require("status") local Curses = require("curses") local Shrines = {} Shrines.WAR = "war" Shrines.VITALITY = "vitality" Shrines.WISDOM = "wisdom" Shrines.FORTUNE = "fortune" -- Fixed order so a given rng draw always names the same kind. Shrines.order = { Shrines.WAR, Shrines.VITALITY, Shrines.WISDOM, Shrines.FORTUNE } Shrines.names = { [Shrines.WAR] = "shrine of war", [Shrines.VITALITY] = "shrine of vitality", [Shrines.WISDOM] = "shrine of wisdom", [Shrines.FORTUNE] = "shrine of fortune", } local BASE_COST = 20 local COST_STEP = 10 -- Prayer cost climbs with prior use at this specific altar, the same shape as -- its rising curse risk: the shrine asks more and gives worse odds once its -- patience with the petitioner is spent. function Shrines.costFor(shrine) return BASE_COST + shrine.uses * COST_STEP end -- Numerator over a denominator of 6: the first prayer risks 1-in-6, climbing -- by one for every prior use, capped at a certainty of 6-in-6. function Shrines.curseChance(shrine) return math.min(shrine.uses + 1, 6), 6 end local function grantBlessing(player, kind) if kind == Shrines.WAR then player.attack = player.attack + 1 player.baseAttack = player.baseAttack + 1 return "your grip tightens with martial favor" elseif kind == Shrines.VITALITY then player.maxHp = player.maxHp + 5 player.hp = player.hp + 5 return "vigor floods through you" elseif kind == Shrines.WISDOM then player.maxMana = player.maxMana + 3 player.mana = player.maxMana return "arcane insight fills your mind" else local cleansed = false local kept = {} for _, active in ipairs(player.statuses) do if active.kind == Status.POISON or active.kind == Status.BURNING or active.kind == Status.WEAKEN or active.kind == Status.SLOW then cleansed = true else kept[#kept + 1] = active end end player.statuses = kept Status.apply(player, Status.SHIELD, 10, 1) if Curses.cleanseWorn(player) > 0 then cleansed = true end if cleansed then return "fortune cleanses your ailments and wards you" end return "fortune wards you against harm" end end local function inflictCurse(player) Status.apply(player, Status.WEAKEN, 8, 1) Status.apply(player, Status.SLOW, 8, 1) return "the shrine's favor turns against you" end -- Attempts a prayer at `shrine` for `game.player`. When the price cannot be -- met nothing is spent and this returns (false, lines). Otherwise the gold is -- always spent, the shrine's use count always advances (so its price and -- curse odds keep climbing), and this returns (true, lines) describing -- whichever of a blessing or a curse landed. function Shrines.pray(game, shrine) local player = game.player local cost = Shrines.costFor(shrine) if player.gold < cost then return false, { "you cannot afford " .. Shrines.names[shrine.kind] .. "'s price" } end player.gold = player.gold - cost local numerator, denominator = Shrines.curseChance(shrine) local cursed = game.rng:chance(numerator, denominator) shrine.uses = shrine.uses + 1 local line if cursed then line = inflictCurse(player) else line = grantBlessing(player, shrine.kind) end return true, { string.format("you pray at the %s for %d gold", Shrines.names[shrine.kind], cost), line, } end return Shrines