feat: extend game with Game:awardKill and Game:applyWeaponRiders

This commit is contained in:
dualfroz committed 2026-09-06 15:12:41 +02:00
1 parent 928f1e6a9e
commit 9d17fcb7d8
1 file changed
+165 -7
+165 -7
View File
@@ -695,9 +695,54 @@ function Game:takeMonsterTurn(monster)
self:onActorDeath(monster) self:onActorDeath(monster)
return return
end end
if not Status.skipsTurn(monster) then Conductivity.tick(self.level, monster)
local hazardLine = Hazards.afflict(self.level, monster)
if hazardLine then self.log:add(hazardLine) end
if not monster:isAlive() then
self:onActorDeath(monster)
return
end
-- Mirrors Game:hungerTorchTick's own Disease.tick(self.player) call - a
-- disease is plain generic entity state (see disease.lua), and since
-- Infighting.strike can now infect a rival monster or the player's own
-- companion/hireling/thrall (not just the player), every non-player actor
-- needs its own infection to actually progress on its turn too, or an
-- infected ally would sit permanently incubating with no way to ever
-- erupt, escalate, or be noticed.
local diseaseLine = Disease.tick(monster)
if diseaseLine then self.log:add(diseaseLine) end
if not monster:isAlive() then
self:onActorDeath(monster)
return
end
local regenLine = Abilities.regenTick(monster)
if regenLine then self.log:add(regenLine) end
local enrageLine = Bosses.tryEnrage(monster)
if enrageLine then self.log:add(enrageLine) end
local phaseLine, phaseConsumedTurn = Phases.tick(self, monster)
if phaseLine then self.log:add(phaseLine) end
if not phaseConsumedTurn and not Status.skipsTurn(monster) then
if monster.kind == "companion" then
Companions.act(self, monster)
elseif monster.kind == "hireling" then
Mercenaries.act(self, monster)
elseif monster.kind == "thrall" then
Reanimation.act(self, monster)
elseif Infighting.tryAct(self, monster) then
-- A rivalry or a discorded mind has nothing to do with how this
-- monster currently feels about the player: it fights regardless of
-- wariness or - unlike Stealth.tick's own gating - whether it has
-- otherwise noticed the player at all this turn.
elseif Factions.isWary(self.player, monster) then
-- A wary tribe member holds its ground: neither provoked nor hated
-- into open hostility yet, it leaves the player be this turn.
elseif Stealth.tick(self, monster) then
Abilities.tickCooldown(monster)
if not Abilities.tryAct(self, monster) then
Ai.act(self, monster) Ai.act(self, monster)
end end
end
end
Scheduler.spend(monster) Scheduler.spend(monster)
self:cleanupDead() self:cleanupDead()
end end
@@ -713,15 +758,128 @@ end
-- Player actions. -- Player actions.
function Game:playerAttack(target) -- Common tail of any player-caused kill: log it, grant the xp it was worth,
local outcome = Combat.attack(self.player, target) -- and sweep it off the level. Shared by melee and offensive spells so xp
self.log:add(string.format("you hit %s for %d", target.name, outcome.damage)) -- always tracks a felled monster regardless of what felled it.
local weapon = self.player.weapon function Game:awardKill(target)
if weapon and weapon.affix and weapon.affix.onHit and target:isAlive() then self.log:add(target.name .. " dies")
local rider = weapon.affix.onHit local burst = Abilities.onDeath(self, target)
if burst then self.log:add(burst) end
local eliteBurst = Elites.onDeath(self, target)
if eliteBurst then self.log:add(eliteBurst) end
self.stats.kills = self.stats.kills + 1
if target.isBoss then self.stats.bossKills = self.stats.bossKills + 1 end
Factions.registerKill(self.player, target)
local xp = target.isBoss and Bosses.xpFor(target) or target.mimic and Mimics.xpFor()
or target.grave and Graves.xpFor() or Monsters.xpFor(target)
xp = xp + Elites.xpBonus(target, xp)
if target.nemesis then xp = xp + Nemesis.xpBonus(target.nemesisGrudge or 1) end
self.log:addAll(Progression.grantXp(self.player, xp))
self.player.essence = self.player.essence + math.floor(Crafting.essenceFor(xp) * self:essenceMultiplier())
Quests.registerKill(self.player, target.species)
Compendium.noteKill(self.compendium, target.species)
Corpses.spawn(self.level, target)
self:resolveQuest()
if target.isBoss then
local loot = Bosses.lootFor(target)
if loot then
self.level.floorItems[#self.level.floorItems + 1] = { x = target.x, y = target.y, item = loot }
self.log:add(string.format("%s drops %s", target.name, loot.name))
end
end
if target.grave then
local gold, essence = Graves.lootFor(target)
if gold > 0 then
self.player.gold = self.player.gold + gold
self.log:add(string.format("the shade scatters %d gold as it fades", gold))
end
if essence > 0 then
self.player.essence = self.player.essence + essence
end
end
if target.nemesis then
local bounty = Nemesis.bountyGold(target.nemesisGrudge or 1)
self.player.gold = self.player.gold + bounty
self.log:add(string.format("the hunt is over - you claim %d gold from %s", bounty, target.name))
self.nemesis = nil
end
self:cleanupDead()
end
-- Applies a weapon's on-hit rider - rolled affix or socketed gem, or both -
-- to a still-living target, logging each status it lands.
function Game:applyWeaponRiders(weapon, target)
if not (weapon and target:isAlive()) then return end
local riders = {}
if weapon.affix and weapon.affix.onHit then riders[#riders + 1] = weapon.affix.onHit end
if weapon.socket and weapon.socket.onHit then riders[#riders + 1] = weapon.socket.onHit end
for _, rider in ipairs(riders) do
Status.apply(target, rider.status, rider.duration, rider.potency) Status.apply(target, rider.status, rider.duration, rider.potency)
self.log:add(string.format("%s is %s", target.name, rider.status)) self.log:add(string.format("%s is %s", target.name, rider.status))
end end
end
-- Marks a monster as no longer wary (see factions.lua): once the player has
-- aimed any offensive action at it, a tribal target that used to hold back
-- fights (or flees) like any other monster from then on, hit or miss. Any
-- offensive action also wakes a dormant target outright (see stealth.lua) -
-- an arrow or a spell that misses still gives away exactly where you are.
function Game:provoke(target)
if target and target.kind == "monster" then
target.provoked = true
Stealth.alert(target, self.player.x, self.player.y)
end
end
-- Floods a burst of noise.lua sound out from (x, y) and gives every other
-- still-dormant monster the flood actually reaches a chance to stir - a
-- clash of steel three rooms over does not single out whichever target
-- Game:provoke already woke outright, it startles whatever else is close
-- enough to hear it too. A monster startled this way only ever starts
-- searching the noise's origin (see Stealth.hear), never snaps straight to
-- hunting the way a direct hit does.
function Game:emitNoise(x, y, loudness)
local field = Noise.propagate(self.level.map, x, y, loudness)
for _, entity in ipairs(self.level.entities) do
if entity.kind == "monster" and Stealth.isUnaware(entity) then
local intensity = Noise.intensityAt(field, self.level.map, entity.x, entity.y)
if intensity > 0 and self.rng:chance(intensity, loudness) then
Stealth.hear(entity, x, y)
self.log:add(string.format("%s stirs at the noise", entity.name))
end
end
end
end
function Game:playerAttack(target)
-- Read before provoke() wakes the target below, so the bonus reflects
-- whatever it actually was the instant the blow landed.
local sneak = Stealth.sneakMultiplier(target)
self:provoke(target)
self:emitNoise(self.player.x, self.player.y, Noise.MELEE)
local multiplier = Skills.meleeDamageMultiplier(self.player, target) * sneak
local outcome = Combat.attack(self.player, target, multiplier)
Momentum.onMeleeHit(self.player)
if sneak > 1 then
self.log:add(string.format("you catch %s completely unaware", target.name))
end
self.log:add(string.format("you hit %s for %d", target.name, outcome.damage))
local broke = Durability.wearWeapon(self.player)
if broke then self.log:add(broke) end
local lifesteal = Artifacts.lifestealAmount(self.player, outcome.damage)
if lifesteal > 0 then
self.player.hp = math.min(self.player.maxHp, self.player.hp + lifesteal)
self.log:add(string.format("your %s drinks %d hp from %s", self.player.weapon.name, lifesteal, target.name))
end
self:applyWeaponRiders(self.player.weapon, target)
if Skills.consumePoisonBlade(self.player) and target:isAlive() then
Status.apply(target, Status.POISON, 5, 2)
self.log:add(string.format("%s is envenomed", target.name))
end
local masteredKey, masteredBonus = Proficiency.onMeleeHit(self.player)
if masteredKey then
self.log:add(string.format("you grow more skilled with your %s (+%d attack)", masteredKey, masteredBonus))
end
if outcome.killed then if outcome.killed then
self.log:add(target.name .. " dies") self.log:add(target.name .. " dies")
self:cleanupDead() self:cleanupDead()