Files
warren/src/ascension.lua
T

137 lines
5.4 KiB
Lua

-- Optional run-wide difficulty mutators a player opts into before a run
-- starts, in exchange for extra renown once it ends: risk taken on purpose,
-- unlike events.lua's mutators which fire and fade on their own mid-run
-- regardless of anyone's choice. A mutator unlocks once meta.lua's own
-- cross-run tallies (best depth, wins) cross its fixed requirement -
-- computed live off those tallies rather than persisted, since both only
-- ever climb, so nothing here needs its own entry in meta.lua's save shape.
--
-- Active mutators for the run in progress live on game.ascension, a plain
-- array of keys in catalog order (see Ascension.resolve), exactly the shape
-- warren.lua's --mutators flag builds. Plain data, so it serializes with no
-- special save-format handling, and every combat/essence/sight hook that
-- reads it (Game:monsterDamageMultiplier, Game:essenceMultiplier,
-- Game:effectiveRadius) stays a pure function of that array alongside
-- whatever level-scoped event Events.lua itself is contributing.
local Ascension = {}
Ascension.MUTATORS = {
{ key = "bloodbound", name = "Bloodbound", desc = "monsters hit 50% harder",
renownBonus = 15, requiresDepth = 3, requiresWins = 0, damageMultiplier = 1.5 },
{ key = "famine", name = "Famine", desc = "essence income halved",
renownBonus = 10, requiresDepth = 2, requiresWins = 0, essenceMultiplier = 0.5 },
{ key = "nightblind", name = "Nightblind", desc = "sight radius reduced by 2",
renownBonus = 10, requiresDepth = 2, requiresWins = 0, sightPenalty = 2 },
{ key = "frail", name = "Frail", desc = "defense halved",
renownBonus = 12, requiresDepth = 4, requiresWins = 0,
applyToPlayer = function(player)
player.baseDefense = player.baseDefense // 2
player.defense = player.defense // 2
end },
{ key = "glasscannon", name = "Glass Cannon", desc = "max hp halved, attack doubled",
renownBonus = 20, requiresDepth = 0, requiresWins = 1,
applyToPlayer = function(player)
player.maxHp = math.max(1, player.maxHp // 2)
player.hp = math.min(player.hp, player.maxHp)
player.baseAttack = player.baseAttack * 2
player.attack = player.attack * 2
end },
}
Ascension.byKey = {}
Ascension.order = {}
for _, def in ipairs(Ascension.MUTATORS) do
Ascension.byKey[def.key] = def
Ascension.order[#Ascension.order + 1] = def.key
end
-- A meta-less caller (no --meta flag at all) can still activate whatever
-- costs nothing to unlock; every other mutator needs a persistent meta file
-- to have already banked the depth or win it requires.
function Ascension.isUnlocked(meta, key)
local def = Ascension.byKey[key]
if not def then return false end
if not meta then return def.requiresDepth == 0 and def.requiresWins == 0 end
return meta.bestDepth >= def.requiresDepth and meta.wins >= def.requiresWins
end
-- Validates a requested list of mutator keys against both the catalog and
-- `meta`'s own unlock state, returned de-duplicated in catalog order rather
-- than whatever order the caller supplied - so two runs activating the same
-- set always see it identically regardless of how --mutators listed them.
-- An unknown or still-locked key is silently dropped rather than failing
-- the whole run over one bad flag.
function Ascension.resolve(meta, requested)
if not requested or #requested == 0 then return {} end
local wanted = {}
for _, key in ipairs(requested) do wanted[key] = true end
local active = {}
for _, key in ipairs(Ascension.order) do
if wanted[key] and Ascension.isUnlocked(meta, key) then
active[#active + 1] = key
end
end
return active
end
function Ascension.isActive(active, key)
for _, k in ipairs(active or {}) do
if k == key then return true end
end
return false
end
function Ascension.damageMultiplier(active)
local mult = 1
for _, key in ipairs(active or {}) do
local def = Ascension.byKey[key]
if def and def.damageMultiplier then mult = mult * def.damageMultiplier end
end
return mult
end
function Ascension.essenceMultiplier(active)
local mult = 1
for _, key in ipairs(active or {}) do
local def = Ascension.byKey[key]
if def and def.essenceMultiplier then mult = mult * def.essenceMultiplier end
end
return mult
end
function Ascension.sightPenalty(active)
local penalty = 0
for _, key in ipairs(active or {}) do
local def = Ascension.byKey[key]
if def and def.sightPenalty then penalty = penalty + def.sightPenalty end
end
return penalty
end
-- Renown bonus percentage this active set adds on top of Meta.renownFor's
-- own base score, summed rather than compounded - three 15% mutators add
-- 45%, not a runaway 1.15^3 - so stacking more of them is always a
-- straightforward trade of more risk for more reward.
function Ascension.renownBonusPercent(active)
local percent = 0
for _, key in ipairs(active or {}) do
local def = Ascension.byKey[key]
if def then percent = percent + def.renownBonus end
end
return percent
end
-- Applies every active mutator's one-time player-stat delta, in catalog
-- order. Meant to run once in Game.new, after Meta.applyAll's perks and any
-- class delta, since a mutator is the final, voluntary layer of difficulty
-- stacked on top of whatever build the player already chose.
function Ascension.applyToPlayer(active, player)
for _, key in ipairs(active or {}) do
local def = Ascension.byKey[key]
if def and def.applyToPlayer then def.applyToPlayer(player) end
end
end
return Ascension