145 lines
3.7 KiB
Lua
145 lines
3.7 KiB
Lua
-- 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
|
||
|
|
|
||
|
|
-- Speak all eight neighbours, clockwise from north. Terse by design: eight
|
||
|
|
-- descriptions at normal verbosity is a wall of speech.
|
||
|
|
function surroundings.scan()
|
||
|
|
local level = getLevel()
|
||
|
|
local px, py = playerPosition(level)
|
||
|
|
|
||
|
|
if not px then
|
||
|
|
return
|
||
|
|
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
|
||
|
|
|
||
|
|
speech.say(table.concat(parts, ". "), true)
|
||
|
|
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
|