feat: add Bosses.forDepth and Bosses.create
This commit is contained in:
1 parent
9bcc8fe6e2
commit
4def2b8dda
1 file changed
+124
+124
@@ -0,0 +1,124 @@
|
|||||||
|
-- Named floor bosses: rare, sturdier variants of the bestiary that phase-shift
|
||||||
|
-- once bloodied ("enrage") and always drop a superior, boss-exclusive
|
||||||
|
-- artifact on death. A boss is otherwise a plain monster entity - it rides
|
||||||
|
-- the same scheduler, ai.lua behaviour and abilities.lua dispatch as every
|
||||||
|
-- other creature - so this module only adds what is genuinely new: the
|
||||||
|
-- catalog, the enrage phase check, and the guaranteed drop.
|
||||||
|
--
|
||||||
|
-- A boss's second, harder-won phase - the brace-then-unleash cataclysm once
|
||||||
|
-- it is battered all the way down to desperateAt - lives in phases.lua
|
||||||
|
-- instead of here, since unlike enrage it spends whole turns and needs its
|
||||||
|
-- own two-state machine; this module only supplies the catalog numbers
|
||||||
|
-- (desperateAt, desperateDefenseBonus, novaDamage, novaRadius) it reads.
|
||||||
|
--
|
||||||
|
-- Builder places at most one per floor from depth 6 on, its rarity roll
|
||||||
|
-- appended after every other placement (artifacts included) so it can never
|
||||||
|
-- perturb an earlier system's rng sequence, exactly like artifacts.lua does
|
||||||
|
-- for its own roll. Gating at depth 6+ also keeps bosses off every floor the
|
||||||
|
-- fixed-maxDepth win-condition tests generate (those use maxDepth <= 3).
|
||||||
|
|
||||||
|
local Entity = require("entity")
|
||||||
|
local Artifacts = require("artifacts")
|
||||||
|
|
||||||
|
local Bosses = {}
|
||||||
|
|
||||||
|
Bosses.catalog = {
|
||||||
|
{
|
||||||
|
key = "warlord", glyph = "W", name = "the Orc Warlord",
|
||||||
|
hp = 34, attack = 7, defense = 3, speed = 100, ai = "pack",
|
||||||
|
minDepth = 6, xp = 40, ability = "rally",
|
||||||
|
enrageAt = 0.5, enrageAttack = 4, enrageSpeed = 25, enrageHeal = 6,
|
||||||
|
desperateAt = 0.2, desperateDefenseBonus = 5, novaDamage = 12, novaRadius = 2,
|
||||||
|
lootKey = "warlords_cleaver",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key = "lich", glyph = "L", name = "the Lich",
|
||||||
|
hp = 28, attack = 5, defense = 2, speed = 100, ai = "flee", range = 6,
|
||||||
|
minDepth = 7, xp = 45, ability = "curse",
|
||||||
|
enrageAt = 0.5, enrageAttack = 3, enrageSpeed = 20, enrageHeal = 8,
|
||||||
|
desperateAt = 0.2, desperateDefenseBonus = 4, novaDamage = 10, novaRadius = 2,
|
||||||
|
lootKey = "lich_phylactery",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key = "bonequeen", glyph = "Q", name = "the Bone Queen",
|
||||||
|
hp = 32, attack = 4, defense = 2, speed = 95, ai = "flee",
|
||||||
|
minDepth = 8, xp = 50, ability = "summon", summonSpecies = "zombie",
|
||||||
|
enrageAt = 0.5, enrageAttack = 3, enrageSpeed = 15, enrageHeal = 10,
|
||||||
|
desperateAt = 0.2, desperateDefenseBonus = 4, novaDamage = 11, novaRadius = 2,
|
||||||
|
lootKey = "bone_queen_carapace",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Bosses.byKey = {}
|
||||||
|
for _, spec in ipairs(Bosses.catalog) do
|
||||||
|
Bosses.byKey[spec.key] = spec
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Boss species eligible at a depth, in registry order, mirroring
|
||||||
|
-- Monsters.forDepth so builder.lua can roll among them the same way.
|
||||||
|
function Bosses.forDepth(depth)
|
||||||
|
local eligible = {}
|
||||||
|
for _, spec in ipairs(Bosses.catalog) do
|
||||||
|
if spec.minDepth <= depth then eligible[#eligible + 1] = spec end
|
||||||
|
end
|
||||||
|
return eligible
|
||||||
|
end
|
||||||
|
|
||||||
|
function Bosses.create(spec, id, x, y)
|
||||||
|
local entity = Entity.new({
|
||||||
|
id = id, x = x, y = y,
|
||||||
|
glyph = spec.glyph, name = spec.name, kind = "monster",
|
||||||
|
species = "boss_" .. spec.key, ai = spec.ai,
|
||||||
|
hp = spec.hp, maxHp = spec.hp,
|
||||||
|
attack = spec.attack, defense = spec.defense,
|
||||||
|
speed = spec.speed, range = spec.range or 0,
|
||||||
|
})
|
||||||
|
entity.ability = spec.ability
|
||||||
|
entity.summonSpecies = spec.summonSpecies
|
||||||
|
entity.cooldown = 0
|
||||||
|
entity.isBoss = true
|
||||||
|
entity.bossKey = spec.key
|
||||||
|
entity.enraged = false
|
||||||
|
-- Always alert: sneaking up on a named boss is not part of the design
|
||||||
|
-- (see stealth.lua's header for how this differs from a wild monster).
|
||||||
|
entity.awareness = "hunting"
|
||||||
|
return entity
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Xp reward for felling a boss, looked up by the bossKey stamped onto it at
|
||||||
|
-- creation. Falls back to a flat amount for a boss entity missing one, which
|
||||||
|
-- should not happen outside of a malformed save.
|
||||||
|
function Bosses.xpFor(entity)
|
||||||
|
local spec = Bosses.byKey[entity.bossKey]
|
||||||
|
return spec and spec.xp or 20
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Checked at the start of a boss's own turn, after status ticks, hazards and
|
||||||
|
-- regen but before its ability or plain ai runs: once bloodied past its
|
||||||
|
-- enrageAt threshold it permanently gains attack and speed and takes a
|
||||||
|
-- one-time heal, exactly once for the rest of the fight. Mirrors the direct
|
||||||
|
-- stat-field mutation classes.lua uses for a permanent delta, rather than a
|
||||||
|
-- status.lua effect, since this should never expire or be cleansed.
|
||||||
|
function Bosses.tryEnrage(monster)
|
||||||
|
if not monster.isBoss or monster.enraged or not monster:isAlive() then return nil end
|
||||||
|
local spec = Bosses.byKey[monster.bossKey]
|
||||||
|
if not spec then return nil end
|
||||||
|
if monster.hp > monster.maxHp * spec.enrageAt then return nil end
|
||||||
|
monster.enraged = true
|
||||||
|
monster.attack = monster.attack + spec.enrageAttack
|
||||||
|
monster.baseAttack = monster.baseAttack + spec.enrageAttack
|
||||||
|
monster.speed = monster.speed + spec.enrageSpeed
|
||||||
|
monster.hp = math.min(monster.maxHp, monster.hp + spec.enrageHeal)
|
||||||
|
return string.format("%s flies into a bloodied rage", monster.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The guaranteed superior artifact a boss drops on death, dropped straight
|
||||||
|
-- onto the floor at its corpse's feet by Game:awardKill rather than rolled
|
||||||
|
-- into the ordinary loot table builder.lua scatters at level generation.
|
||||||
|
function Bosses.lootFor(entity)
|
||||||
|
local spec = Bosses.byKey[entity.bossKey]
|
||||||
|
if not spec then return nil end
|
||||||
|
return Artifacts.create(spec.lootKey)
|
||||||
|
end
|
||||||
|
|
||||||
|
return Bosses
|
||||||
Reference in new issue
Block a user