Files

156 lines
4.0 KiB
Lua
Raw Permalink Normal View History

2026-08-01 21:26:35 +02:00
-- surroundings.lua — probe the tiles around the player without moving anything.
--
-- The game's examine mode (ingame_observe) already walks a cursor around the map
-- and is now narrated, but entering it, stepping, and leaving is heavy when you
-- just want to know what is immediately next to you. This answers that in one
-- keypress, with no state change and no game side effects.
local speech = require("accessibility.speech")
local describe = require("accessibility.describe")
local surroundings = {}
-- Screen coordinates: +y is south.
local numpadDirections = {
kp1 = {-1, 1},
kp2 = {0, 1},
kp3 = {1, 1},
kp4 = {-1, 0},
kp5 = {0, 0},
kp6 = {1, 0},
kp7 = {-1, -1},
kp8 = {0, -1},
kp9 = {1, -1},
}
-- Clockwise from north, which is how people scan a compass.
local scanOrder = {
{0, -1, "north"},
{1, -1, "north-east"},
{1, 0, "east"},
{1, 1, "south-east"},
{0, 1, "south"},
{-1, 1, "south-west"},
{-1, 0, "west"},
{-1, -1, "north-west"},
}
local function getLevel()
local ok, world = pcall(require, "models.world")
if not ok or not world.currentLevel then
return nil
end
return world.currentLevel
end
local function playerPosition(level)
local actor = level and level.playerActor
if not actor or not actor.x or not actor.y then
return nil
end
return actor.x, actor.y
end
--------------------------------------------------------------------------------
-- Probing
--------------------------------------------------------------------------------
-- Speak a single tile at an offset from the player.
function surroundings.probe(dx, dy)
local level = getLevel()
local px, py = playerPosition(level)
if not px then
return
end
if dx == 0 and dy == 0 then
speech.say("Standing on: " .. describe.tile(level, px, py), true)
return
end
speech.say(describe.tile(level, px + dx, py + dy, px, py), true)
end
-- All eight neighbours, clockwise from north, as one line. Terse by design:
-- eight descriptions at normal verbosity is a wall of speech.
--
-- Returns the text rather than speaking it, because melee targeting needs the
-- same sentence on entry (accessibility/states/ingame_targeting_melee.lua).
function surroundings.describeNeighbours()
2026-08-01 21:26:35 +02:00
local level = getLevel()
local px, py = playerPosition(level)
if not px then
return nil
2026-08-01 21:26:35 +02:00
end
local previousVerbosity = describe.verbosity
describe.verbosity = "terse"
local parts = {}
for _, entry in ipairs(scanOrder) do
local dx, dy, name = entry[1], entry[2], entry[3]
local text = describe.tile(level, px + dx, py + dy)
parts[#parts + 1] = name .. ", " .. text
end
describe.verbosity = previousVerbosity
return table.concat(parts, ". ")
end
-- Speak all eight neighbours.
function surroundings.scan()
local text = surroundings.describeNeighbours()
if text then
speech.say(text, true)
end
2026-08-01 21:26:35 +02:00
end
-- Where am I: level name, in-game time, coordinates.
function surroundings.whereAmI()
local level = getLevel()
local px, py = playerPosition(level)
if not px then
return
end
local parts = {}
if level.name then
parts[#parts + 1] = level.name
end
local ok, world = pcall(require, "models.world")
if ok and world.printableDatetime then
parts[#parts + 1] = world.printableDatetime
end
parts[#parts + 1] = "position " .. px .. ", " .. py
speech.say(table.concat(parts, ". "), true)
end
--------------------------------------------------------------------------------
-- Key registration
--------------------------------------------------------------------------------
function surroundings.registerKeys(hotkeys)
-- Numpad probing is modifier-gated: kp1-kp9 are movement in game, so an
-- unmodified numpad key must keep moving the player. Ctrl is the game's
-- moveCamera modifier but that only acts on direction commands we consume
-- here, so Ctrl+numpad is free.
for scancode, offset in pairs(numpadDirections) do
hotkeys.bind(scancode, "ctrl", function()
surroundings.probe(offset[1], offset[2])
end)
end
hotkeys.bind("/", nil, surroundings.scan)
hotkeys.bind(";", nil, surroundings.whereAmI)
end
return surroundings