113 lines
4.2 KiB
Lua
113 lines
4.2 KiB
Lua
-- Named bands layered over the player's raw satiation counter, the same
|
|
-- shape sanity.lua already gives its own slow-moving meter: a plain number
|
|
-- that Game:hungerTorchTick drains by one every round (untouched here),
|
|
-- classified into FED/HUNGRY/WEAK/STARVING tiers that a caller reads back
|
|
-- instead of re-deriving the cutoffs itself. Falling into WEAK or worse
|
|
-- costs real attack and defense - wired into entity.lua's effectiveAttack/
|
|
-- effectiveDefense alongside Sanity's own penalty, so a gaunt fighter hits
|
|
-- and blocks softer without combat.lua needing to know hunger exists at
|
|
-- all - and blocks the passive mending hungerTorchTick already grants from
|
|
-- a worn ring, an ironclad set or consecrated ground: a starving adventurer
|
|
-- can still down a potion, just not coast on a ring of regeneration while
|
|
-- fading. STARVING alone keeps hungerTorchTick's own flat hp drain, exactly
|
|
-- as before.
|
|
--
|
|
-- Eating is the mirror case: Hunger.eat folds fresh nutrition into the same
|
|
-- counter, capped at Hunger.CAP, but food eaten while already at that cap
|
|
-- is entirely wasted and leaves the diner Status.SLOW for a few turns - the
|
|
-- same overfull stumble a real glutton earns, reusing a status the engine
|
|
-- already ticks, serializes and renders with no special handling of its own.
|
|
|
|
local Status = require("status")
|
|
|
|
local Hunger = {}
|
|
|
|
Hunger.FED = "fed"
|
|
Hunger.HUNGRY = "hungry"
|
|
Hunger.WEAK = "weak"
|
|
Hunger.STARVING = "starving"
|
|
|
|
-- Starting ration for a fresh game (Game.new) and the ceiling Hunger.eat
|
|
-- refuses to bank past. Comfortably above START so a couple of found
|
|
-- rations can stack up before a long dive, without letting the pool grow
|
|
-- without bound.
|
|
Hunger.START = 1000
|
|
Hunger.CAP = 1200
|
|
|
|
-- Tier ceilings: a value at or below HUNGRY_MAX but above WEAK_MAX reads as
|
|
-- HUNGRY, and so on downward. 0 itself is always STARVING regardless of
|
|
-- where these ceilings sit.
|
|
Hunger.HUNGRY_MAX = 300
|
|
Hunger.WEAK_MAX = 100
|
|
|
|
Hunger.BLOAT_DURATION = 4
|
|
|
|
local ATTACK_PENALTY = {
|
|
[Hunger.FED] = 0, [Hunger.HUNGRY] = 0, [Hunger.WEAK] = -2, [Hunger.STARVING] = -4,
|
|
}
|
|
local DEFENSE_PENALTY = {
|
|
[Hunger.FED] = 0, [Hunger.HUNGRY] = 0, [Hunger.WEAK] = -1, [Hunger.STARVING] = -3,
|
|
}
|
|
|
|
-- A fresh entity - or a monster, which never accrues hunger of its own
|
|
-- since nothing ever calls Hunger.tick for one - reads as fully fed.
|
|
function Hunger.current(entity)
|
|
return entity.satiation or Hunger.CAP
|
|
end
|
|
|
|
function Hunger.tierOf(entity)
|
|
local value = Hunger.current(entity)
|
|
if value <= 0 then return Hunger.STARVING end
|
|
if value <= Hunger.WEAK_MAX then return Hunger.WEAK end
|
|
if value <= Hunger.HUNGRY_MAX then return Hunger.HUNGRY end
|
|
return Hunger.FED
|
|
end
|
|
|
|
function Hunger.attackPenalty(entity)
|
|
return ATTACK_PENALTY[Hunger.tierOf(entity)]
|
|
end
|
|
|
|
function Hunger.defensePenalty(entity)
|
|
return DEFENSE_PENALTY[Hunger.tierOf(entity)]
|
|
end
|
|
|
|
-- WEAK or worse blocks the passive regen hungerTorchTick otherwise grants
|
|
-- from a ring, a set bonus or friendly weather - a fading body has nothing
|
|
-- left over to spend on mending.
|
|
function Hunger.suppressesRegen(entity)
|
|
local tier = Hunger.tierOf(entity)
|
|
return tier == Hunger.WEAK or tier == Hunger.STARVING
|
|
end
|
|
|
|
-- One round of drain. Returns a log line only when the tier actually
|
|
-- changes, except STARVING which - matching the flat drain this replaces -
|
|
-- reports and bites for 1 hp every single round it holds, not just on the
|
|
-- turn it is first reached.
|
|
function Hunger.tick(entity)
|
|
local before = Hunger.tierOf(entity)
|
|
entity.satiation = math.max(0, Hunger.current(entity) - 1)
|
|
local after = Hunger.tierOf(entity)
|
|
if after == Hunger.STARVING then
|
|
entity.hp = entity.hp - 1
|
|
return "you are starving"
|
|
end
|
|
if after ~= before then
|
|
return "you feel " .. after
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- Feeds `nutrition` in, clamped at Hunger.CAP. Returns true when the diner
|
|
-- was already at the cap - all of it wasted, and left Status.SLOW for
|
|
-- Hunger.BLOAT_DURATION turns - or false for an ordinary meal that landed.
|
|
function Hunger.eat(entity, nutrition)
|
|
local wasFull = Hunger.current(entity) >= Hunger.CAP
|
|
entity.satiation = math.min(Hunger.CAP, Hunger.current(entity) + nutrition)
|
|
if wasFull then
|
|
Status.apply(entity, Status.SLOW, Hunger.BLOAT_DURATION, 1)
|
|
end
|
|
return wasFull
|
|
end
|
|
|
|
return Hunger
|