sound and other misc fixes

This commit is contained in:
Your Name 2026-08-12 12:27:52 -04:00
parent 38eaba2fa6
commit 176216df8e
6 changed files with 97 additions and 15 deletions

View file

@ -6,6 +6,10 @@
Dialogable = {} Dialogable = {}
function Dialogable:onBump() function Dialogable:onBump()
if self.book then Dialogue.open(self.book) end if self.book then
local wasOpen = Dialogue.isOpen()
Dialogue.open(self.book)
if Sound and not wasOpen then Sound.jabber() end
end
return true return true
end end

View file

@ -41,6 +41,7 @@ end
function Door:unlock() function Door:unlock()
self.locked = false self.locked = false
self.collision = "none" self.collision = "none"
if Sound then Sound.play("doorUnlock") end
end end
function Door:lock() function Door:lock()

View file

@ -10,6 +10,7 @@ require "_helpers"
require "input" require "input"
require "profiler" require "profiler"
require "color" require "color"
require "sound"
require "room" require "room"
require "world" require "world"
require "world_seed" require "world_seed"
@ -26,6 +27,13 @@ narrator = require "narrator.narrator"
startSignBook = require "stories.start_sign" startSignBook = require "stories.start_sign"
jorgeBook = require "stories.jorge" jorgeBook = require "stories.jorge"
-- DEV/DEBUG mode. When on, the level editor is available and rooms with no
-- player copy present still render every channel, so whole levels stay visible
-- instead of a black screen. Turn it off for a player-facing build; both the
-- editor and that all-channels fallback then disappear. Override at launch with
-- the DEBUG env var (DEBUG=0 to force off, DEBUG=1 to force on).
DEBUG = os.getenv("DEBUG") ~= "0"
function love.load() function love.load()
globalAssetProperties = {} -- rebuilt from the filesystem (art/ + .meta) by getAllAssets globalAssetProperties = {} -- rebuilt from the filesystem (art/ + .meta) by getAllAssets
@ -95,9 +103,9 @@ function love.draw()
love.graphics.setCanvas(gameCanvas) love.graphics.setCanvas(gameCanvas)
love.graphics.clear() love.graphics.clear()
love.graphics.setBlendMode("screen") love.graphics.setBlendMode("screen")
if editorView == "world" then if DEBUG and editorView == "world" then
gameWorld:draw() gameWorld:draw()
elseif editorView == "wire" then elseif DEBUG and editorView == "wire" then
gameWorld:drawWireEditor(wireEditorCamera()) gameWorld:drawWireEditor(wireEditorCamera())
else else
currentRoom:draw() currentRoom:draw()
@ -127,7 +135,9 @@ function love.draw()
-- Render the sidebar at native panel resolution: the canvas is panel-sized, -- Render the sidebar at native panel resolution: the canvas is panel-sized,
-- and editorDraw works in the fixed virtual space, scaled up by a transform. -- and editorDraw works in the fixed virtual space, scaled up by a transform.
-- Blitting the canvas 1:1 afterwards means no rescaling of the final image -- Blitting the canvas 1:1 afterwards means no rescaling of the final image
-- (crisp, nearest-neighbor throughout — nothing is averaged). -- (crisp, nearest-neighbor throughout — nothing is averaged). The editor is a
-- DEBUG-only surface, so skip the whole panel in a player-facing build.
if DEBUG then
love.graphics.setCanvas(sideBarCanvas) love.graphics.setCanvas(sideBarCanvas)
-- The gameplay view now intentionally extends beyond its old room bounds. -- The gameplay view now intentionally extends beyond its old room bounds.
-- Keep the editor panel opaque so that neighbourhood preview never shows -- Keep the editor panel opaque so that neighbourhood preview never shows
@ -138,6 +148,7 @@ function love.draw()
editorDraw() editorDraw()
love.graphics.pop() love.graphics.pop()
love.graphics.setCanvas() love.graphics.setCanvas()
end
-- Collect every visible layer before the accessibility pass. Applying the -- Collect every visible layer before the accessibility pass. Applying the
-- shader here also covers the editor sidebar, FPS display, and dialogue, -- shader here also covers the editor sidebar, FPS display, and dialogue,
@ -147,7 +158,7 @@ function love.draw()
love.graphics.setBlendMode("alpha") love.graphics.setBlendMode("alpha")
love.graphics.setColor(1, 1, 1, 1) love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(finalCanvas, 0, 0) love.graphics.draw(finalCanvas, 0, 0)
love.graphics.draw(sideBarCanvas, sideBarBox.x, sideBarBox.y) if DEBUG then love.graphics.draw(sideBarCanvas, sideBarBox.x, sideBarBox.y) end
love.graphics.print("Current FPS: "..tostring(love.timer.getFPS( )), 10, 10) love.graphics.print("Current FPS: "..tostring(love.timer.getFPS( )), 10, 10)
PerfProfiler:draw() PerfProfiler:draw()
Dialogue.draw() Dialogue.draw()
@ -208,6 +219,9 @@ function love.keypressed(key)
-- worldSha = worldSha - 1 -- worldSha = worldSha - 1
-- end -- end
-- Editor text-field controls (room-name entry etc.): DEBUG-only.
if not DEBUG then return end
if key == "return" then if key == "return" then
response = "" response = ""
editorCompleteResponse() editorCompleteResponse()
@ -225,24 +239,31 @@ function love.keypressed(key)
end end
end end
-- Every mouse/text callback below only ever drives the editor, so they are
-- inert outside DEBUG: a player-facing build is keyboard-only.
function love.mousepressed(x, y, button) function love.mousepressed(x, y, button)
if not DEBUG then return end
editorMouseHandler(x, y, button) editorMouseHandler(x, y, button)
end end
function love.mousemoved(x, y) function love.mousemoved(x, y)
if not DEBUG then return end
editorMouseMoved(x, y) editorMouseMoved(x, y)
end end
function love.mousereleased(x, y, button) function love.mousereleased(x, y, button)
if not DEBUG then return end
editorMouseReleased(x, y, button) editorMouseReleased(x, y, button)
end end
function love.textinput(t) function love.textinput(t)
if not DEBUG then return end
response = response .. t response = response .. t
editorTextHandler(response) editorTextHandler(response)
end end
function love.wheelmoved(dx, dy) function love.wheelmoved(dx, dy)
if not DEBUG then return end
-- Scroll the editor's asset/folder list when the cursor is over the sidebar. -- Scroll the editor's asset/folder list when the cursor is over the sidebar.
if editorView == "room" and love.mouse.getX() > width then if editorView == "room" and love.mouse.getX() > width then
editorScrollRoomList(dy) editorScrollRoomList(dy)
@ -358,6 +379,8 @@ function cycleAccessibilityMode()
end end
function _initSounds() function _initSounds()
Sound.init()
staticWave = sfxr.newSound() staticWave = sfxr.newSound()
staticWave.waveform = sfxr.WAVEFORM.NOISE -- was `wavetype = 3`; field renamed in this sfxr staticWave.waveform = sfxr.WAVEFORM.NOISE -- was `wavetype = 3`; field renamed in this sfxr
staticWave.frequency.start = .1 staticWave.frequency.start = .1

View file

@ -140,6 +140,9 @@ function Room:getRenderChannels()
for _, color in ipairs({"red", "green", "blue"}) do for _, color in ipairs({"red", "green", "blue"}) do
if self.player[color] then table.insert(channels, color) end if self.player[color] then table.insert(channels, color) end
end end
-- No player copy lives here, so the view would be black. In debug/dev mode
-- render every channel instead, letting whole (player-less) levels be seen.
if #channels == 0 and DEBUG then return {"red", "green", "blue"} end
return channels return channels
end end
@ -456,6 +459,7 @@ function Room:movePlayerLocal(key)
end end
if moved then if moved then
if Sound then Sound.play("playerMove") end
if useWorld then gameWorld:playerJoinCheck(self, previousPositions, newPositions) if useWorld then gameWorld:playerJoinCheck(self, previousPositions, newPositions)
else self:playerJoinCheck(previousPositions, newPositions) end else self:playerJoinCheck(previousPositions, newPositions) end
self:tick() self:tick()
@ -1119,6 +1123,7 @@ function nextRoom(pos)
local currentName = currentRoom.name local currentName = currentRoom.name
local nextName = gameWorld and currentName and gameWorld:getNeighbor(currentName, exit) local nextName = gameWorld and currentName and gameWorld:getNeighbor(currentName, exit)
if Sound and nextName and nextName ~= currentName then Sound.play("levelChange") end
currentRoom:save() currentRoom:save()
-- Missing map neighbours intentionally wrap back into the room just exited. -- Missing map neighbours intentionally wrap back into the room just exited.

47
sound.lua Normal file
View file

@ -0,0 +1,47 @@
-- Central sound-effect registry. One preloaded Source per named effect; the game
-- calls Sound.play("name") to (re)trigger it. Ambient static + music live in
-- main.lua's _initSounds; this covers the one-shot gameplay/UI feedback.
--
-- Top level does NO audio calls on purpose: requiring this under the test love
-- stub (which never runs Sound.init) must be side-effect free. Until Sound.init
-- runs, Sound.sfx is empty and Sound.play is a harmless no-op.
Sound = {}
Sound.sfx = {} -- name -> Source
Sound.jabbers = {} -- pool of npc chatter Sources, picked at random
local function load(path)
return love.audio.newSource(path, "static")
end
function Sound.init()
Sound.sfx = {
playerMove = load("sounds/player_move.ogg"),
switchFail = load("sounds/switch_fail.wav"),
switchSuccess = load("sounds/switch_success.wav"),
doorUnlock = load("sounds/door_unlock.ogg"),
levelChange = load("sounds/level_change.ogg"),
uiButton = load("sounds/ui/button.wav"),
}
Sound.jabbers = {
load("sounds/jabber_1.ogg"),
load("sounds/jabber_2.ogg"),
}
end
-- Restart-and-play a named effect. A static Source only plays one instance at a
-- time, so stopping first makes rapid retriggers (e.g. mashing move) audible.
function Sound.play(name)
local src = Sound.sfx[name]
if not src then return end
src:stop()
src:play()
end
-- Pick a random npc jabber clip and play it. Used when a Dialogable starts talking.
function Sound.jabber()
if #Sound.jabbers == 0 then return end
local src = Sound.jabbers[love.math.random(#Sound.jabbers)]
src:stop()
src:play()
end

View file

@ -707,6 +707,7 @@ function World:movePlayer(room, key)
current[color] = player:getGridPos() current[color] = player:getGridPos()
end end
if moved then if moved then
if Sound then Sound.play("playerMove") end
self:markDirty(room) self:markDirty(room)
self:playerJoinCheck(room, previous, current) self:playerJoinCheck(room, previous, current)
self:tick(room) self:tick(room)
@ -811,6 +812,7 @@ function World:nextRoom(room, pos)
elseif pos.y > gridHeight then exit, entry.y = "bottom", 1 elseif pos.y > gridHeight then exit, entry.y = "bottom", 1
elseif pos.y < 1 then exit, entry.y = "top", gridHeight end elseif pos.y < 1 then exit, entry.y = "top", gridHeight end
local nextName = self:getNeighbor(room.name, exit) or room.name local nextName = self:getNeighbor(room.name, exit) or room.name
if Sound and nextName ~= room.name then Sound.play("levelChange") end
local sourceLocation = self:roomLocation(room.name) local sourceLocation = self:roomLocation(room.name)
local destinationLocation = self:roomLocation(nextName) local destinationLocation = self:roomLocation(nextName)
local activeColors = room:getActiveColors() local activeColors = room:getActiveColors()