Files

157 lines
4.3 KiB
Lua
Raw Permalink Normal View History

-- status.lua — player state on demand, plus unprompted alerts for things you
-- must not miss.
--
-- The HP bar, status icons and current-action indicator are all permanently
-- on-screen furniture. A sighted player absorbs them at a glance and never
-- "requests" them, so there is no game event to hook -- these have to be pull
-- keys, plus a push alert for damage.
local speech = require("accessibility.speech")
local status = {}
local function getPlayerActor()
local ok, world = pcall(require, "models.world")
if not ok or not world.currentLevel then
return nil
end
return world.currentLevel.playerActor
end
--------------------------------------------------------------------------------
-- Pull: health and statuses
--------------------------------------------------------------------------------
local function statusNames(actor)
local names = {}
for _, entry in ipairs(actor.statusList or {}) do
local name = entry.name or entry.templateId
if name then
names[#names + 1] = name
end
end
return names
end
function status.announceHealth()
local actor = getPlayerActor()
if not actor then
speech.say("No character", true)
return
end
local parts = {}
if actor.hp and actor.maxhp then
parts[#parts + 1] = string.format("Health %d of %d", actor.hp, actor.maxhp)
-- A bare number means little without a sense of how bad it is.
local fraction = actor.hp / actor.maxhp
if fraction <= 0.25 then
parts[#parts + 1] = "critical"
elseif fraction <= 0.5 then
parts[#parts + 1] = "wounded"
end
end
local names = statusNames(actor)
if #names > 0 then
parts[#parts + 1] = table.concat(names, ", ")
else
parts[#parts + 1] = "no active effects"
end
speech.say(table.concat(parts, ". "), true)
end
--------------------------------------------------------------------------------
-- Pull: current action
--------------------------------------------------------------------------------
function status.announceAction()
local ok, player = pcall(require, "models.player")
if not ok or not player.currentSelectedAction then
speech.say("No action selected", true)
return
end
local action = player.currentSelectedAction
-- `title` is the display string; `name` is the internal id ("interact"), so
-- capitalise when falling back to it.
local label = action.title
if not label then
label = action.name or "unknown"
label = label:sub(1, 1):upper() .. label:sub(2)
end
speech.say("Action: " .. label, true)
end
--------------------------------------------------------------------------------
-- Push: damage alerts
--------------------------------------------------------------------------------
-- Polled rather than hooked. ingame.checkInterruptions(hpLoss) exists, but it
-- only fires for interruptions during queued movement -- damage taken on your own
-- turn does not go through it. Watching hp directly catches every source
-- (combat, poison, fire, falling) with no game edit.
local lastHp
local lastActor
function status.update()
local actor = getPlayerActor()
if not actor or not actor.hp then
return
end
-- New character or new level: re-baseline instead of reporting a "change".
if actor ~= lastActor then
lastActor = actor
lastHp = actor.hp
return
end
if lastHp == nil then
lastHp = actor.hp
return
end
if actor.hp < lastHp then
local lost = lastHp - actor.hp
lastHp = actor.hp
-- Interrupting and immediate: taking damage outranks whatever flavour
-- text is queued, and waiting for end of frame is wrong for an alert.
speech.immediate(string.format("Hit, minus %d, health %d of %d",
lost, actor.hp, actor.maxhp or actor.hp), true)
if actor.maxhp and actor.hp / actor.maxhp <= 0.25 and actor.hp > 0 then
speech.say("Critical health", false)
end
elseif actor.hp > lastHp then
-- Healing is worth knowing but never urgent, so it queues.
local gained = actor.hp - lastHp
lastHp = actor.hp
speech.say(string.format("Healed %d, health %d of %d",
gained, actor.hp, actor.maxhp or actor.hp), false)
end
end
--------------------------------------------------------------------------------
-- Key registration
--------------------------------------------------------------------------------
function status.registerKeys(hotkeys)
hotkeys.bind("'", nil, status.announceHealth)
hotkeys.bind(",", nil, status.announceAction)
end
return status