feat: extend mimics with Mimics.floorItem and Mimics.reveal
This commit is contained in:
1 parent
06714fc72a
commit
6a26f1f65e
3 files changed
+160
-11
No files matched your search
@@ -0,0 +1,60 @@
|
|||||||
|
-- A mimic disguises itself as ordinary loot until a hand reaches for it.
|
||||||
|
-- Builder.build drops one as a plain floorItem tagged `mimic = true`,
|
||||||
|
-- wearing a random disguise's name and glyph (Mimics.DISGUISES) - utterly
|
||||||
|
-- indistinguishable at rest from a real item, since render.lua and
|
||||||
|
-- Game:pickupHere both just read an ordinary floorItem's own `.item.glyph`
|
||||||
|
-- and `.item.name`, with no idea a given tile might be a trap. The instant
|
||||||
|
-- the player steps onto it (Game:triggerMimic, called from pickupHere),
|
||||||
|
-- Mimics.reveal builds the real hostile monster underneath, scaled by
|
||||||
|
-- depth the same way Monsters.create scales a wild species's hp, and gets
|
||||||
|
-- one free ambush swing in (via combat.lua, so it still respects the
|
||||||
|
-- player's defense and resistances) before it settles into an ordinary
|
||||||
|
-- chase like any other monster on its own turns from then on.
|
||||||
|
|
||||||
|
local Entity = require("entity")
|
||||||
|
local Factions = require("factions")
|
||||||
|
|
||||||
|
local Mimics = {}
|
||||||
|
|
||||||
|
Mimics.FACTION = Factions.BEAST
|
||||||
|
|
||||||
|
Mimics.DISGUISES = {
|
||||||
|
{ name = "a weathered chest", glyph = "=" },
|
||||||
|
{ name = "a glinting pile of gold", glyph = "$" },
|
||||||
|
{ name = "an ornate tome", glyph = "&" },
|
||||||
|
{ name = "a discarded shield", glyph = "[" },
|
||||||
|
}
|
||||||
|
|
||||||
|
-- The inert floorItem a builder drops: it looks exactly like whichever
|
||||||
|
-- disguise it rolled until Game:pickupHere unmasks it.
|
||||||
|
function Mimics.floorItem(rng)
|
||||||
|
local disguise = Mimics.DISGUISES[rng:range(1, #Mimics.DISGUISES)]
|
||||||
|
return { kind = "mimic", mimic = true, name = disguise.name, glyph = disguise.glyph }
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The hostile monster a disguised item becomes the instant it is
|
||||||
|
-- disturbed, its hp scaled by depth exactly the way Monsters.create scales
|
||||||
|
-- a wild species's own base hp.
|
||||||
|
function Mimics.reveal(id, x, y, depth)
|
||||||
|
local bonusHp = depth - 1
|
||||||
|
local entity = Entity.new({
|
||||||
|
id = id, x = x, y = y,
|
||||||
|
glyph = "M", name = "mimic", kind = "monster",
|
||||||
|
species = "mimic", ai = "chase",
|
||||||
|
hp = 12 + bonusHp, maxHp = 12 + bonusHp,
|
||||||
|
attack = 5, defense = 1, speed = 100,
|
||||||
|
})
|
||||||
|
entity.cooldown = 0
|
||||||
|
entity.faction = Mimics.FACTION
|
||||||
|
entity.mimic = true
|
||||||
|
return entity
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Flat xp reward for felling a revealed mimic - kept separate from
|
||||||
|
-- Monsters.xpFor since a mimic is never a member of the wild bestiary
|
||||||
|
-- Monsters.forDepth draws its ordinary per-floor population from.
|
||||||
|
function Mimics.xpFor()
|
||||||
|
return 10
|
||||||
|
end
|
||||||
|
|
||||||
|
return Mimics
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
-- A player-only combo meter that rewards sustained melee aggression: every
|
||||||
|
-- landed melee swing raises the stack by one, up to a hard cap, and each
|
||||||
|
-- stack adds a flat attack bonus that reads live through
|
||||||
|
-- Entity:effectiveAttack - the very next swing already hits harder, not
|
||||||
|
-- just some future one. Taking a hit from a monster's claw, breath or
|
||||||
|
-- ambush snaps the whole stack back to zero outright - unlike a status
|
||||||
|
-- effect's timed decay, there is no partial forgiveness, so holding a combo
|
||||||
|
-- against several foes at once is a far harder rhythm than a lone duel. An
|
||||||
|
-- evaded attack (see skills.lua) never reaches Combat.attack at all, so it
|
||||||
|
-- never breaks the stack either - only a blow that actually lands does.
|
||||||
|
-- Ranged shots, thrown items and environmental damage (traps, poison,
|
||||||
|
-- lava) neither build nor break it: momentum is specifically a melee-vs-
|
||||||
|
-- monster reward, not a universal combat buff.
|
||||||
|
|
||||||
|
local Momentum = {}
|
||||||
|
|
||||||
|
Momentum.MAX_STACKS = 5
|
||||||
|
Momentum.BONUS_PER_STACK = 1
|
||||||
|
|
||||||
|
function Momentum.stacks(entity)
|
||||||
|
return entity.combo or 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Raises `entity`'s stack by one landed melee hit, capped at MAX_STACKS.
|
||||||
|
function Momentum.onMeleeHit(entity)
|
||||||
|
entity.combo = math.min(Momentum.MAX_STACKS, Momentum.stacks(entity) + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Snaps the stack back to zero the instant `entity` takes a hit.
|
||||||
|
function Momentum.onDamaged(entity)
|
||||||
|
entity.combo = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-- The flat attack bonus `entity`'s current stack grants.
|
||||||
|
function Momentum.attackBonus(entity)
|
||||||
|
return Momentum.stacks(entity) * Momentum.BONUS_PER_STACK
|
||||||
|
end
|
||||||
|
|
||||||
|
return Momentum
|
||||||
+61
-11
@@ -4,20 +4,41 @@
|
|||||||
-- depth so lower floors bite harder.
|
-- depth so lower floors bite harder.
|
||||||
|
|
||||||
local Entity = require("entity")
|
local Entity = require("entity")
|
||||||
|
local Factions = require("factions")
|
||||||
|
local Disease = require("disease")
|
||||||
|
|
||||||
local Monsters = {}
|
local Monsters = {}
|
||||||
|
|
||||||
Monsters.species = {
|
Monsters.species = {
|
||||||
{ key = "rat", glyph = "r", name = "rat", hp = 4, attack = 2, defense = 0, speed = 110, ai = "chase", minDepth = 1 },
|
{ key = "rat", glyph = "r", name = "rat", hp = 4, attack = 2, defense = 0, speed = 110, ai = "chase", minDepth = 1, xp = 3, faction = Factions.VERMIN, disease = Disease.PARASITES },
|
||||||
{ key = "bat", glyph = "v", name = "cave bat", hp = 3, attack = 2, defense = 0, speed = 150, ai = "erratic", minDepth = 1 },
|
{ key = "bat", glyph = "v", name = "cave bat", hp = 3, attack = 2, defense = 0, speed = 150, ai = "erratic", minDepth = 1, xp = 3, faction = Factions.VERMIN },
|
||||||
{ key = "kobold", glyph = "k", name = "kobold", hp = 6, attack = 3, defense = 1, speed = 100, ai = "pack", minDepth = 1 },
|
{ key = "kobold", glyph = "k", name = "kobold", hp = 6, attack = 3, defense = 1, speed = 100, ai = "pack", minDepth = 1, xp = 5, faction = Factions.GOBLINOID, disease = Disease.PLAGUE },
|
||||||
{ key = "goblin", glyph = "g", name = "goblin", hp = 5, attack = 3, defense = 0, speed = 110, ai = "pack", minDepth = 2 },
|
{ key = "goblin", glyph = "g", name = "goblin", hp = 5, attack = 3, defense = 0, speed = 110, ai = "pack", minDepth = 2, xp = 5, faction = Factions.GOBLINOID },
|
||||||
{ key = "archer", glyph = "a", name = "skeleton archer", hp = 6, attack = 3, defense = 1, speed = 100, ai = "ranged", minDepth = 2, range = 5 },
|
{ key = "archer", glyph = "a", name = "skeleton archer", hp = 6, attack = 3, defense = 1, speed = 100, ai = "ranged", minDepth = 2, range = 5, xp = 6, faction = Factions.UNDEAD },
|
||||||
{ key = "orc", glyph = "o", name = "orc", hp = 10, attack = 4, defense = 1, speed = 100, ai = "chase", minDepth = 3 },
|
{ key = "orc", glyph = "o", name = "orc", hp = 10, attack = 4, defense = 1, speed = 100, ai = "chase", minDepth = 3, xp = 9, faction = Factions.ORCISH },
|
||||||
{ key = "wretch", glyph = "w", name = "cave wretch", hp = 8, attack = 2, defense = 0, speed = 120, ai = "flee", minDepth = 3 },
|
{ key = "wretch", glyph = "w", name = "cave wretch", hp = 8, attack = 2, defense = 0, speed = 120, ai = "flee", minDepth = 3, xp = 6, onDeath = "spores", faction = Factions.BEAST },
|
||||||
{ key = "troll", glyph = "T", name = "troll", hp = 16, attack = 6, defense = 2, speed = 80, ai = "chase", minDepth = 4 },
|
{ key = "troll", glyph = "T", name = "troll", hp = 16, attack = 6, defense = 2, speed = 80, ai = "chase", minDepth = 4, xp = 15, regen = 3, resist = { fire = "weak" }, faction = Factions.BEAST },
|
||||||
{ key = "mage", glyph = "m", name = "dark mage", hp = 8, attack = 2, defense = 0, speed = 100, ai = "ranged", minDepth = 5, range = 6 },
|
{ key = "mage", glyph = "m", name = "dark mage", hp = 8, attack = 2, defense = 0, speed = 100, ai = "ranged", minDepth = 5, range = 6, xp = 12, faction = Factions.CULT },
|
||||||
{ key = "dragon", glyph = "D", name = "young dragon", hp = 30, attack = 8, defense = 3, speed = 90, ai = "ranged", minDepth = 6, range = 4 },
|
{ key = "dragon", glyph = "D", name = "young dragon", hp = 30, attack = 8, defense = 3, speed = 90, ai = "ranged", minDepth = 6, range = 4, xp = 25, breath = "fire", resist = { fire = "immune", cold = "weak" }, faction = Factions.DRAKE },
|
||||||
|
|
||||||
|
-- Ability bearers: each carries an `ability` tag resolved by abilities.lua's
|
||||||
|
-- tryAct dispatch, which runs before the plain ai.lua behaviour listed here
|
||||||
|
-- and only falls through to it when the ability is on cooldown or has no
|
||||||
|
-- valid target. minDepth is kept at 4+ so these never appear on the shallow
|
||||||
|
-- floors exercised by the fixed-maxDepth win-condition tests.
|
||||||
|
{ key = "zombie", glyph = "z", name = "shambling zombie", hp = 7, attack = 3, defense = 1, speed = 90, ai = "chase", minDepth = 4, xp = 5, faction = Factions.UNDEAD, disease = Disease.ROT },
|
||||||
|
{ key = "slime", glyph = "j", name = "gelatinous slime", hp = 10, attack = 2, defense = 0, speed = 90, ai = "chase", minDepth = 4, xp = 5, ability = "split", faction = Factions.BEAST },
|
||||||
|
{ key = "footpad", glyph = "f", name = "footpad", hp = 6, attack = 2, defense = 0, speed = 120, ai = "chase", minDepth = 4, xp = 6, ability = "steal", faction = Factions.BANDIT },
|
||||||
|
{ key = "shaman", glyph = "s", name = "goblin shaman", hp = 7, attack = 2, defense = 0, speed = 100, ai = "flee", minDepth = 4, xp = 9, ability = "curse", faction = Factions.GOBLINOID },
|
||||||
|
{ key = "necromancer", glyph = "n", name = "necromancer", hp = 9, attack = 2, defense = 0, speed = 95, ai = "flee", minDepth = 4, xp = 10, ability = "summon", summonSpecies = "zombie", faction = Factions.CULT },
|
||||||
|
{ key = "cleric", glyph = "c", name = "dark cleric", hp = 8, attack = 2, defense = 1, speed = 100, ai = "pack", minDepth = 5, xp = 9, ability = "heal", faction = Factions.CULT },
|
||||||
|
{ key = "chieftain", glyph = "C", name = "orc chieftain", hp = 14, attack = 5, defense = 2, speed = 100, ai = "pack", minDepth = 6, xp = 14, ability = "rally", faction = Factions.GOBLINOID },
|
||||||
|
{ key = "banshee", glyph = "b", name = "banshee", hp = 7, attack = 2, defense = 0, speed = 100, ai = "flee", minDepth = 5, xp = 11, ability = "shriek", faction = Factions.UNDEAD },
|
||||||
|
|
||||||
|
-- Shopkeeper: a passive vendor guarding priced wares (see shop.lua). Its
|
||||||
|
-- "vend" behaviour idles until game.lua flags it hostile after a theft, at
|
||||||
|
-- which point it falls back to a plain chase like any other monster.
|
||||||
|
{ key = "shopkeeper", glyph = "h", name = "shopkeeper", hp = 8, attack = 3, defense = 1, speed = 100, ai = "vend", minDepth = 4, xp = 7, faction = Factions.MERCHANT },
|
||||||
}
|
}
|
||||||
|
|
||||||
Monsters.byKey = {}
|
Monsters.byKey = {}
|
||||||
@@ -36,9 +57,28 @@ function Monsters.forDepth(depth)
|
|||||||
return eligible
|
return eligible
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Xp reward for felling an entity, looked up by the species key stamped onto
|
||||||
|
-- it at creation. Falls back to a flat amount for anything without one.
|
||||||
|
function Monsters.xpFor(entity)
|
||||||
|
local spec = Monsters.byKey[entity.species]
|
||||||
|
return spec and spec.xp or 4
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A fresh copy of a species' innate resist table, never the catalog's own
|
||||||
|
-- object: elites.lua's "warded" affix mutates entity.resist in place once
|
||||||
|
-- rolled, and a shared reference there would leak that bump onto every
|
||||||
|
-- other spawn of the same species for the rest of the process, not just
|
||||||
|
-- the one entity actually promoted.
|
||||||
|
local function copyResist(resist)
|
||||||
|
if not resist then return nil end
|
||||||
|
local copy = {}
|
||||||
|
for damageType, tier in pairs(resist) do copy[damageType] = tier end
|
||||||
|
return copy
|
||||||
|
end
|
||||||
|
|
||||||
function Monsters.create(spec, id, x, y, depth)
|
function Monsters.create(spec, id, x, y, depth)
|
||||||
local bonusHp = depth - 1
|
local bonusHp = depth - 1
|
||||||
return Entity.new({
|
local entity = Entity.new({
|
||||||
id = id,
|
id = id,
|
||||||
x = x, y = y,
|
x = x, y = y,
|
||||||
glyph = spec.glyph, name = spec.name, kind = "monster",
|
glyph = spec.glyph, name = spec.name, kind = "monster",
|
||||||
@@ -47,6 +87,16 @@ function Monsters.create(spec, id, x, y, depth)
|
|||||||
attack = spec.attack, defense = spec.defense,
|
attack = spec.attack, defense = spec.defense,
|
||||||
speed = spec.speed, range = spec.range or 0,
|
speed = spec.speed, range = spec.range or 0,
|
||||||
})
|
})
|
||||||
|
entity.regen = spec.regen
|
||||||
|
entity.onDeath = spec.onDeath
|
||||||
|
entity.breath = spec.breath
|
||||||
|
entity.ability = spec.ability
|
||||||
|
entity.summonSpecies = spec.summonSpecies
|
||||||
|
entity.cooldown = 0
|
||||||
|
entity.resist = copyResist(spec.resist)
|
||||||
|
entity.faction = spec.faction
|
||||||
|
entity.biteDisease = spec.disease
|
||||||
|
return entity
|
||||||
end
|
end
|
||||||
|
|
||||||
return Monsters
|
return Monsters
|
||||||
Reference in new issue
Block a user