mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
pokecenter fix (#126)
This commit is contained in:
@@ -42,8 +42,10 @@ Game Boy equivalent:
|
||||
rows above the player recede and rows below come toward the viewer. Only
|
||||
things that actually *stand* on the ground draw as upright billboards,
|
||||
unscaled and pixel-identical to flat mode: the player, NPCs, item balls,
|
||||
and the screen-anchored FX attached to them (heal machine glow, emote
|
||||
bubbles, the fishing rod, the FLY bird). An earlier revision tried
|
||||
and the standing FX attached to them (emote bubbles, the fishing rod,
|
||||
the FLY bird). The Poké Center heal-machine overlay stays on the ground
|
||||
plane with the machine tiles (it is OAM glued to a BG graphic, not a
|
||||
standing sprite). An earlier revision tried
|
||||
billboarding buildings/trees/signs too (cutting them out of the ground
|
||||
per hand-curated per-tileset tables); that chased an endless tail of
|
||||
special cases, dense tree canopy, fences fused into grass, building
|
||||
|
||||
@@ -230,6 +230,25 @@ function ChipAudio.ensureMusicPlaying()
|
||||
end
|
||||
end
|
||||
|
||||
-- Threaded playMusic returns an empty QueueableSource and only calls
|
||||
-- Source:play once the first worker buffer lands (~1 frame later). Until
|
||||
-- then Source:isPlaying is false -- callers that treat that as "song over"
|
||||
-- (Music.oneShotPlaying / pendingRestore) must wait here instead, or a
|
||||
-- playOnce jingle like Music_PkmnHealed is cut off before it starts.
|
||||
local forceAwaitingFirstBuffer -- test-only override (see _simulate*)
|
||||
|
||||
function ChipAudio.awaitingFirstBuffer()
|
||||
if forceAwaitingFirstBuffer then return true end
|
||||
local m = currentMusic
|
||||
if not (m and m.threaded and not m.started and not m.finished) then
|
||||
return false
|
||||
end
|
||||
-- a dead worker will never deliver the first buffer
|
||||
if workerReady == false then return false end
|
||||
if worker and worker.getError and worker:getError() then return false end
|
||||
return true
|
||||
end
|
||||
|
||||
function ChipAudio.stopMusic()
|
||||
if currentMusic and currentMusic.source then
|
||||
pcall(currentMusic.source.stop, currentMusic.source)
|
||||
@@ -240,6 +259,7 @@ function ChipAudio.stopMusic()
|
||||
end
|
||||
pendingBuf = nil
|
||||
currentMusic = nil
|
||||
forceAwaitingFirstBuffer = nil
|
||||
end
|
||||
|
||||
-- hot reload: the next play re-reads programs.bin (a mod may have swapped the
|
||||
@@ -302,6 +322,20 @@ end
|
||||
-- test hooks (headless): synchronous synthesis straight through ChipSynth
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- Force the "threaded, first buffer not yet queued" window so Music's
|
||||
-- playOnce / pendingRestore race can be asserted without love.thread.
|
||||
-- Returns a clear() that drops the override (call after the assertion).
|
||||
function ChipAudio._simulateAwaitingFirstBufferForTest()
|
||||
local m = currentMusic
|
||||
if not m or not m.source then return nil end
|
||||
m.threaded = true
|
||||
m.started = false
|
||||
m.finished = false
|
||||
pcall(function() m.source.playing = false end)
|
||||
forceAwaitingFirstBuffer = true
|
||||
return function() forceAwaitingFirstBuffer = nil end
|
||||
end
|
||||
|
||||
function ChipAudio._renderMusicForTest(data, header, seconds)
|
||||
local engine = ChipSynth.newEngine(data, header, { allowLoops = true })
|
||||
return ChipSynth.soundData(engine, math.floor(seconds * SAMPLE_RATE), 2)
|
||||
|
||||
+14
-1
@@ -254,6 +254,7 @@ function Music.stop()
|
||||
require("src.core.ChipAudio").stopMusic()
|
||||
state.current, state.source, state.loopSource, state.fade = nil, nil, nil, nil
|
||||
state.chip = false
|
||||
state.pendingRestore = nil
|
||||
if previous and Runtime.wants("music.stopped") then
|
||||
Runtime.emit("music.stopped", { song = previous })
|
||||
end
|
||||
@@ -347,14 +348,24 @@ end
|
||||
function Music.playOnce(data, song)
|
||||
if not songDef(data, song) then return false end
|
||||
Music.play(data, song, false, { reason = "once" })
|
||||
-- play() can no-op (hook silence, failed def); only arm restore when
|
||||
-- the jingle actually became current
|
||||
if state.current ~= song then return false end
|
||||
state.pendingRestore = true
|
||||
return true
|
||||
end
|
||||
|
||||
local function chipAwaitingFirstBuffer()
|
||||
return state.chip
|
||||
and require("src.core.ChipAudio").awaitingFirstBuffer()
|
||||
end
|
||||
|
||||
-- is a playOnce jingle still sounding? (AnimateHealingMachine's
|
||||
-- .waitLoop2 holds the healing machine until MUSIC_PKMN_HEALED ends)
|
||||
function Music.oneShotPlaying()
|
||||
if not state.pendingRestore then return false end
|
||||
-- threaded chip songs start silent for ~1 frame; that gap is not "over"
|
||||
if chipAwaitingFirstBuffer() then return true end
|
||||
local src = state.source
|
||||
if not src then return false end
|
||||
local ok, playing = pcall(src.isPlaying, src)
|
||||
@@ -441,8 +452,10 @@ function Music.update(data)
|
||||
state.source = loopSrc
|
||||
pcall(loopSrc.play, loopSrc)
|
||||
end
|
||||
-- do not treat "threaded source still waiting on its first buffer" as
|
||||
-- ended, or playOnce jingles get restored over before they can sound
|
||||
if state.pendingRestore and sourceStopped(state.source)
|
||||
and not state.loopSource then
|
||||
and not state.loopSource and not chipAwaitingFirstBuffer() then
|
||||
Music.restoreMap(data)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3415,8 +3415,11 @@ function OverworldState:drawWorld()
|
||||
love.graphics.setShader(shader)
|
||||
end
|
||||
end
|
||||
local ox = ha.px - 64 - cam.x
|
||||
local oy = ha.py - 64 - cam.y
|
||||
-- TileRenderer windows with -floor(cam), so the overlay must use the
|
||||
-- same snap or a fractional camera (odd fill/tilt view sizes) parks
|
||||
-- the balls a pixel off the machine tiles
|
||||
local ox = ha.px - 64 - math.floor(cam.x)
|
||||
local oy = ha.py - 64 - math.floor(cam.y)
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.draw(img, self.healMachineQuads[1], ox + 44, oy + 20)
|
||||
for i = 1, math.min(ha.lit, #HEAL_BALL_XY) do
|
||||
@@ -3632,11 +3635,13 @@ function OverworldState:drawWorld()
|
||||
else
|
||||
-- === TILT PATH: ground-hugging FX stay on the projected ground, all
|
||||
-- standing things billboard upright over it in a separate pass. ======
|
||||
-- Dust is ground-hugging smoke -> ground canvas (puts it
|
||||
-- with the flat layer, so it projects with the ground). Flat mode
|
||||
-- draws it last, over the sprites, in the same canvas; here the two
|
||||
-- Dust / cut / the Poké Center heal overlay hug the BG (the heal
|
||||
-- machine is a tileset graphic; its OAM balls must ride that plane or
|
||||
-- they float off the machine once the ground foreshortens). Flat mode
|
||||
-- draws them last, over the sprites, in the same canvas; here the two
|
||||
-- layers are separate and composited ground-under-upright, so drawing
|
||||
-- it now into the still-active ground canvas is order-equivalent.
|
||||
-- them now into the still-active ground canvas is order-equivalent.
|
||||
fxHeal()
|
||||
fxDust()
|
||||
fxCutTree()
|
||||
|
||||
@@ -3692,18 +3697,11 @@ function OverworldState:drawWorld()
|
||||
end
|
||||
end
|
||||
|
||||
-- Screen-anchored world FX : each billboards at the
|
||||
-- ground foot of the character it belongs to, so it stands upright and
|
||||
-- scales with that character's depth.
|
||||
-- heal machine -> the healed player's foot (the machine stands on
|
||||
-- the ground in front of where the player was)
|
||||
-- Standing world FX: each billboards at the ground foot of the
|
||||
-- character it belongs to, so it stays upright over the tilted ground.
|
||||
-- emote bubble -> the spotting NPC's foot (rides above its head)
|
||||
-- fly bird, rod -> the player's foot
|
||||
if self.healAnim then
|
||||
local fx = self.healAnim.px - cam.x + 8
|
||||
local fy = self.healAnim.py - cam.y + 16
|
||||
self:billboard(fx, fy, vw, vh, zoneColorsAt(zones, fx, fy), false, fxHeal)
|
||||
end
|
||||
-- (heal machine is ground-hugging -- drawn above with dust/cut)
|
||||
if self.emote and self.emote.npc then
|
||||
local fx = self.emote.npc.px - cam.x + 8
|
||||
local fy = self.emote.npc.py - cam.y + 16
|
||||
|
||||
@@ -344,6 +344,24 @@ check(lastSource().queueable and lastSource().playing,
|
||||
"a chip song still plays after a file song")
|
||||
check(not body.playing, "the outgoing file song was stopped")
|
||||
|
||||
-- playOnce must survive the threaded "empty QueueableSource" window:
|
||||
-- Source:isPlaying is false until the first worker buffer lands, and that
|
||||
-- gap must not look like the jingle already ended (Poké Center heal).
|
||||
data = reset(fixtureData())
|
||||
Music.playMap(data, "PALLET_TOWN", false, false)
|
||||
check(Music.playOnce(data, "Music_Chip"), "playOnce starts a chip jingle")
|
||||
local jingle = lastSource()
|
||||
local clearAwait = ChipAudio._simulateAwaitingFirstBufferForTest()
|
||||
check(clearAwait ~= nil, "test can force the awaiting-first-buffer window")
|
||||
check(Music.oneShotPlaying(),
|
||||
"oneShotPlaying stays true while the first buffer is still in flight")
|
||||
Music.update(data)
|
||||
check(jingle == lastSource() and jingle.queueable,
|
||||
"pendingRestore does not swap the map theme over a pending chip jingle")
|
||||
check(ChipAudio.awaitingFirstBuffer(),
|
||||
"awaitingFirstBuffer reports the forced window")
|
||||
clearAwait()
|
||||
|
||||
-- sfx shape dispatch
|
||||
check(Sound.play(data, "Beep") == nil, "Sound.play returns nothing")
|
||||
check(lastSource().file == "assets/beep.wav", "a bare string sfx is a static source")
|
||||
|
||||
Reference in New Issue
Block a user