75 lines
3.1 KiB
Lua
75 lines
3.1 KiB
Lua
-- Sound propagation. Unlike los.lua's straight sightline, a noisy action's
|
|
-- racket bends around corners: it floods outward from its origin through
|
|
-- open, walkable cells only, using the same four-directional adjacency
|
|
-- astar.lua's own pathing already assumes (see its DIRS and game.lua's
|
|
-- MONSTER_DIRS), and fades by exactly one point of loudness for every step
|
|
-- the flood has to take to get there. A closed door or a solid wall stops
|
|
-- it dead - nothing leaks through a wall diagonally the way it might with
|
|
-- an eight-directional flood, so a noise never reaches anywhere a monster
|
|
-- could not actually have walked to investigate it.
|
|
--
|
|
-- The flood itself has no idea what a monster or a player is; it is a pure
|
|
-- function of the map, an origin and a starting loudness. game.lua's
|
|
-- Game:emitNoise is what turns a resulting field into a dormant monster
|
|
-- actually stirring (see stealth.lua's Stealth.hear).
|
|
|
|
local Noise = {}
|
|
|
|
local DIRS = { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } }
|
|
|
|
-- Loudness constants for the actions game.lua wires up to emitNoise, tuned
|
|
-- so a whisper-quiet action (nothing currently that quiet) would decay
|
|
-- within a couple of cells while a pick striking stone carries the length
|
|
-- of a small floor.
|
|
Noise.MELEE = 6
|
|
Noise.RANGED = 6
|
|
Noise.THROW = 5
|
|
Noise.SPELL = 8
|
|
Noise.DIG = 9
|
|
|
|
-- Floods outward from (x, y) through walkable cells, returning a table
|
|
-- keyed by map:index(nx, ny) -> the loudness still remaining once the sound
|
|
-- has traveled there (loudness itself at the origin, decaying by one per
|
|
-- step, never revisiting a cell at an intensity no better than one already
|
|
-- recorded there). A cell the flood never reaches simply has no entry. The
|
|
-- origin itself need not be walkable - a pick striking solid rock (see
|
|
-- game.lua's Game:dig) is exactly as loud as one striking rubble - only the
|
|
-- cells the flood actually spreads into have to be open for anything to
|
|
-- ever stand in them and hear it.
|
|
function Noise.propagate(map, x, y, loudness)
|
|
local field = {}
|
|
if loudness <= 0 or not map:inBounds(x, y) then return field end
|
|
local startIdx = map:index(x, y)
|
|
field[startIdx] = loudness
|
|
local queue = { { x = x, y = y, intensity = loudness } }
|
|
local head = 1
|
|
while head <= #queue do
|
|
local cur = queue[head]
|
|
head = head + 1
|
|
if cur.intensity > 0 then
|
|
local nextIntensity = cur.intensity - 1
|
|
for _, dir in ipairs(DIRS) do
|
|
local nx, ny = cur.x + dir[1], cur.y + dir[2]
|
|
if map:isWalkable(nx, ny) then
|
|
local idx = map:index(nx, ny)
|
|
if (field[idx] or -1) < nextIntensity then
|
|
field[idx] = nextIntensity
|
|
queue[#queue + 1] = { x = nx, y = ny, intensity = nextIntensity }
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return field
|
|
end
|
|
|
|
-- Remaining loudness a previously computed field still carries at (x, y),
|
|
-- or 0 for anywhere the flood never reached (including out of bounds, which
|
|
-- map:index would otherwise happily compute an index for).
|
|
function Noise.intensityAt(field, map, x, y)
|
|
if not map:inBounds(x, y) then return 0 end
|
|
return field[map:index(x, y)] or 0
|
|
end
|
|
|
|
return Noise
|