92 lines
2.9 KiB
Lua
92 lines
2.9 KiB
Lua
-- states/ingame_targeting_throw.lua — the ranged targeting cursor.
|
|||
|
|
--
|
||
|
|
-- Same shape, and same reasoning, as accessibility/states/ingame_observe.lua:
|
||
|
|
-- read level.cursorX/cursorY straight off the level model each frame rather than
|
||
|
|
-- listening to the game's cursor popup. The popup is refreshed from the cursor
|
||
|
|
-- animation's completion callback (`onFinish`), so relaying it would put every
|
||
|
|
-- announcement a keypress behind. animations/moveCursor.lua assigns cursorX/Y in
|
||
|
|
-- its constructor, so polling is correct on the frame the key is pressed.
|
||
|
|
--
|
||
|
|
-- The extra job here over examine mode is saying whether the tile is a LEGAL
|
||
|
|
-- target, which is the whole point of being in this state.
|
||
|
|
|
||
|
|
local describe = require("accessibility.describe")
|
||
|
|
|
||
|
|
-- Recompute validity from the level rather than reading the state's own
|
||
|
|
-- `targetAllowed` upvalue.
|
||
|
|
--
|
||
|
|
-- Why: states/ingame_targeting_throw.lua:228 sets targetAllowed = false for the
|
||
|
|
-- duration of the 0.3s cursor glide and only the animation's onFinish restores
|
||
|
|
-- it. We speak on the frame the key is pressed, i.e. mid-glide, so that upvalue
|
||
|
|
-- would report "blocked" for every single move. The three inputs the game
|
||
|
|
-- itself uses (:86-89) are all public fields, so recomputing is both cheap and
|
||
|
|
-- honest.
|
||
|
|
local function validity(level, x, y)
|
||
|
|
if not level.interactableArea then
|
||
|
|
return nil
|
||
|
|
end
|
||
|
|
|
||
|
|
if not (level.FoV and level.FoV[x] and level.FoV[x][y]) then
|
||
|
|
return "no line of sight"
|
||
|
|
end
|
||
|
|
|
||
|
|
if not (level.interactableArea[x] and level.interactableArea[x][y]) then
|
||
|
|
return "out of range"
|
||
|
|
end
|
||
|
|
|
||
|
|
local ok, throwable = pcall(level.canThrowOnCell, x, y)
|
||
|
|
if ok and not throwable then
|
||
|
|
return "blocked"
|
||
|
|
end
|
||
|
|
|
||
|
|
return "can throw"
|
||
|
|
end
|
||
|
|
|
||
|
|
return {
|
||
|
|
read = function()
|
||
|
|
local ok, world = pcall(require, "models.world")
|
||
|
|
if not ok or not world.currentLevel then
|
||
|
|
return nil, nil
|
||
|
|
end
|
||
|
|
|
||
|
|
local level = world.currentLevel
|
||
|
|
local x, y = level.cursorX, level.cursorY
|
||
|
|
|
||
|
|
if type(x) ~= "number" or type(y) ~= "number" then
|
||
|
|
return nil, nil
|
||
|
|
end
|
||
|
|
|
||
|
|
local player = level.playerActor
|
||
|
|
local px = player and player.x
|
||
|
|
local py = player and player.y
|
||
|
|
|
||
|
|
-- Terse: in combat the target's name, its legality and how far away it is
|
||
|
|
-- are what matter; the scenery adjectives are noise.
|
||
|
|
--
|
||
|
|
-- Described without an origin so that describe.tile does not append the
|
||
|
|
-- offset itself -- the validity belongs between the two, giving
|
||
|
|
-- "rat, can throw, 3 tiles north-east".
|
||
|
|
local previousVerbosity = describe.verbosity
|
||
|
|
describe.verbosity = "terse"
|
||
|
|
local okTile, text = pcall(describe.tile, level, x, y)
|
||
|
|
describe.verbosity = previousVerbosity
|
||
|
|
|
||
|
|
if not okTile or not text or text == "" then
|
||
|
|
return nil, nil
|
||
|
|
end
|
||
|
|
|
||
|
|
local parts = {text}
|
||
|
|
|
||
|
|
local state = validity(level, x, y)
|
||
|
|
if state then
|
||
|
|
parts[#parts + 1] = state
|
||
|
|
end
|
||
|
|
|
||
|
|
if px and py then
|
||
|
|
parts[#parts + 1] = describe.offset(x - px, y - py)
|
||
|
|
end
|
||
|
|
|
||
|
|
return table.concat(parts, ", "), x .. "," .. y
|
||
|
|
end,
|
||
|
|
}
|