-- 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, }