mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-20 04:31:09 +02:00
CLOSES #1508
This commit is contained in:
@@ -424,11 +424,12 @@ local function renderEffect(data, header, options)
|
|||||||
return love.audio.newSource(sd, "static")
|
return love.audio.newSource(sd, "static")
|
||||||
end
|
end
|
||||||
|
|
||||||
function ChipAudio.newSfx(data, name, pitch, tempo, header)
|
function ChipAudio.newSfx(data, name, pitch, tempo, header, plainFrames)
|
||||||
header = header or data.audio.sfx[name]
|
header = header or data.audio.sfx[name]
|
||||||
return renderEffect(data, header, {
|
return renderEffect(data, header, {
|
||||||
frequencyOffset = pitch or 0,
|
frequencyOffset = pitch or 0,
|
||||||
frameTicks = 0x80 + (tempo or 0x80),
|
frameTicks = 0x80 + (tempo or 0x80),
|
||||||
|
plainFrames = plainFrames,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
+12
-6
@@ -280,6 +280,7 @@ function Channel.new(engine, spec, options)
|
|||||||
allowLoops = options.allowLoops ~= false,
|
allowLoops = options.allowLoops ~= false,
|
||||||
frequencyOffset = options.frequencyOffset or 0,
|
frequencyOffset = options.frequencyOffset or 0,
|
||||||
frameTicks = options.frameTicks or FRAME_TICKS,
|
frameTicks = options.frameTicks or FRAME_TICKS,
|
||||||
|
plainTicks = (options.plainFrames or 0) * FRAME_TICKS,
|
||||||
speed = 12,
|
speed = 12,
|
||||||
noteLength = 1, -- Gen 2 CHANNEL_NOTE_LENGTH (note_type)
|
noteLength = 1, -- Gen 2 CHANNEL_NOTE_LENGTH (note_type)
|
||||||
durationModifier = 0, -- Gen 2 fractional-frame carry
|
durationModifier = 0, -- Gen 2 fractional-frame carry
|
||||||
@@ -349,8 +350,9 @@ function Channel:frequencyGen2(note, octave)
|
|||||||
return bit.band(register + self.frequencyOffset, 0x7FF)
|
return bit.band(register + self.frequencyOffset, 0x7FF)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Channel:durationTicks(length)
|
function Channel:durationTicks(length, plain)
|
||||||
local tempo = self.sfx and self.frameTicks or self.engine.tempo
|
local tempo = self.sfx and (plain and FRAME_TICKS or self.frameTicks)
|
||||||
|
or self.engine.tempo
|
||||||
local speed = self.sfx and (self.executeMusic and self.speed or 1)
|
local speed = self.sfx and (self.executeMusic and self.speed or 1)
|
||||||
or self.speed
|
or self.speed
|
||||||
return length * speed * tempo
|
return length * speed * tempo
|
||||||
@@ -586,6 +588,9 @@ function Channel:nextEvent()
|
|||||||
local packed = self:byte()
|
local packed = self:byte()
|
||||||
local volume = bit.rshift(packed, 4)
|
local volume = bit.rshift(packed, 4)
|
||||||
local fade = fadeValue(bit.band(packed, 0x0F))
|
local fade = fadeValue(bit.band(packed, 0x0F))
|
||||||
|
-- audio/engine_2.asm:991-1013, :1015-1033, :1077-1096
|
||||||
|
local plain = self.timeTicks < self.plainTicks
|
||||||
|
local offset = plain and 0 or self.frequencyOffset
|
||||||
if self.noise then
|
if self.noise then
|
||||||
-- Audio2_ApplyWavePatternAndFrequency adds wFrequencyModifier to the
|
-- Audio2_ApplyWavePatternAndFrequency adds wFrequencyModifier to the
|
||||||
-- frequency low byte for every channel at or past CHAN5, the noise
|
-- frequency low byte for every channel at or past CHAN5, the noise
|
||||||
@@ -595,12 +600,12 @@ function Channel:nextEvent()
|
|||||||
-- byte that noise does not use for frequency. Dropping it left the
|
-- byte that noise does not use for frequency. Dropping it left the
|
||||||
-- battle hit sounds at their unmodified pitches, where super effective
|
-- battle hit sounds at their unmodified pitches, where super effective
|
||||||
-- reads as the duller of the two (#826).
|
-- reads as the duller of the two (#826).
|
||||||
local parameter = bit.band(self:byte() + self.frequencyOffset, 0xFF)
|
local parameter = bit.band(self:byte() + offset, 0xFF)
|
||||||
return self:noiseEvent(
|
return self:noiseEvent(
|
||||||
self:durationTicks(length), volume, fade, parameter)
|
self:durationTicks(length, plain), volume, fade, parameter)
|
||||||
end
|
end
|
||||||
local register = bit.band(self:word() + self.frequencyOffset, 0x7FF)
|
local register = bit.band(self:word() + offset, 0x7FF)
|
||||||
return self:tone(self:durationTicks(length), register, volume, fade)
|
return self:tone(self:durationTicks(length, plain), register, volume, fade)
|
||||||
elseif command == 0x10 then
|
elseif command == 0x10 then
|
||||||
local packed = self:byte()
|
local packed = self:byte()
|
||||||
self.sweep = {
|
self.sweep = {
|
||||||
@@ -1260,6 +1265,7 @@ function Engine.new(data, header, options)
|
|||||||
allowLoops = options.allowLoops,
|
allowLoops = options.allowLoops,
|
||||||
frequencyOffset = options.frequencyOffset,
|
frequencyOffset = options.frequencyOffset,
|
||||||
frameTicks = frameTicks,
|
frameTicks = frameTicks,
|
||||||
|
plainFrames = options.plainFrames,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
return engine
|
return engine
|
||||||
|
|||||||
+107
-36
@@ -8,6 +8,7 @@
|
|||||||
local Assets = require("src.render.Assets")
|
local Assets = require("src.render.Assets")
|
||||||
local Logger = require("src.core.Logger")
|
local Logger = require("src.core.Logger")
|
||||||
local Runtime = require("src.mods.Runtime")
|
local Runtime = require("src.mods.Runtime")
|
||||||
|
local bit = require("bit")
|
||||||
|
|
||||||
local Sound = {}
|
local Sound = {}
|
||||||
|
|
||||||
@@ -142,10 +143,10 @@ local function newFileSource(def)
|
|||||||
return s
|
return s
|
||||||
end
|
end
|
||||||
|
|
||||||
local function newSfxSource(data, key, def, pitch, tempo)
|
local function newSfxSource(data, key, def, pitch, tempo, plain)
|
||||||
if isChipDef(def) then
|
if isChipDef(def) then
|
||||||
local ok, s = pcall(require("src.core.ChipAudio").newSfx,
|
local ok, s = pcall(require("src.core.ChipAudio").newSfx,
|
||||||
data, key:match("^([^@]+)") or key, pitch, tempo, def)
|
data, key:match("^([^@]+)") or key, pitch, tempo, def, plain)
|
||||||
if not ok then return nil, tostring(s) end
|
if not ok then return nil, tostring(s) end
|
||||||
if not s then return nil, "no source" end
|
if not s then return nil, "no source" end
|
||||||
return s
|
return s
|
||||||
@@ -153,12 +154,12 @@ local function newSfxSource(data, key, def, pitch, tempo)
|
|||||||
return newFileSource(def)
|
return newFileSource(def)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function playPath(data, key, def, pitch, tempo)
|
local function playPath(data, key, def, pitch, tempo, plain)
|
||||||
if not love.audio or not def then return nil end
|
if not love.audio or not def then return nil end
|
||||||
local src = cache[key]
|
local src = cache[key]
|
||||||
if src == false then return nil end -- known bad, already logged
|
if src == false then return nil end -- known bad, already logged
|
||||||
if not src then
|
if not src then
|
||||||
local s, err = newSfxSource(data, key, def, pitch, tempo)
|
local s, err = newSfxSource(data, key, def, pitch, tempo, plain)
|
||||||
if not s then
|
if not s then
|
||||||
cache[key] = false
|
cache[key] = false
|
||||||
reportBadDef("sfx", key, owner(data, "sfx", key), err)
|
reportBadDef("sfx", key, owner(data, "sfx", key), err)
|
||||||
@@ -413,50 +414,109 @@ end
|
|||||||
-- still sounding when the second row starts, so the original never plays
|
-- still sounding when the second row starts, so the original never plays
|
||||||
-- SFX_BATTLE_2A (CHAN5+6+8) at all -- unguarded, its tail is heard running
|
-- SFX_BATTLE_2A (CHAN5+6+8) at all -- unguarded, its tail is heard running
|
||||||
-- past the end of the animation (#844).
|
-- past the end of the animation (#844).
|
||||||
local lastMoveSfx -- { src, rank, engine, channels } of the last row sound
|
local moveSfxChannels = {} -- software channel (5-8) -> { src, address, engine }
|
||||||
|
|
||||||
local function channelsOverlap(a, b)
|
local function sourceAlive(entry)
|
||||||
if not (a and b) then return false end
|
local ok, playing = pcall(entry.src.isPlaying, entry.src)
|
||||||
for _, x in ipairs(a) do
|
return ok and playing
|
||||||
for _, y in ipairs(b) do
|
end
|
||||||
if x == y then return true end
|
|
||||||
end
|
local function pruneMoveSfx()
|
||||||
|
for ch, cur in pairs(moveSfxChannels) do
|
||||||
|
if not sourceAlive(cur) then moveSfxChannels[ch] = nil end
|
||||||
end
|
end
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- would PlaySound start this def now? Taking a channel over also stops the
|
-- would PlaySound start this def now? Taking a channel over also stops the
|
||||||
-- sound that held it, the way .playChannel resets the channel.
|
-- sound that held it, the way .playChannel resets the channel.
|
||||||
local function sfxChannelGate(data, def)
|
local function sfxChannelGate(data, def)
|
||||||
local cur = lastMoveSfx
|
pruneMoveSfx()
|
||||||
if not cur then return true end
|
|
||||||
local ok, playing = pcall(cur.src.isPlaying, cur.src)
|
|
||||||
if not (ok and playing) then
|
|
||||||
lastMoveSfx = nil
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
-- an unrankable def (file asset, or another engine's bank) has no
|
-- an unrankable def (file asset, or another engine's bank) has no
|
||||||
-- comparable sound id: leave it to the mixer, as before
|
-- comparable sound id: leave it to the mixer, as before
|
||||||
if type(def) ~= "table" or not def.address or def.engine ~= cur.engine then
|
if type(def) ~= "table" or not def.address then return true end
|
||||||
return true
|
|
||||||
end
|
|
||||||
local channels = require("src.core.ChipSynth").effectChannels(data, def)
|
local channels = require("src.core.ChipSynth").effectChannels(data, def)
|
||||||
if not channelsOverlap(channels, cur.channels) then return true end
|
if not channels then return true end
|
||||||
if def.address > cur.rank then return false end
|
local takeover
|
||||||
pcall(cur.src.stop, cur.src)
|
for _, ch in ipairs(channels) do
|
||||||
lastMoveSfx = nil
|
local cur = moveSfxChannels[ch]
|
||||||
return true
|
if cur and cur.engine == def.engine then
|
||||||
|
if def.address > cur.address then return false end
|
||||||
|
takeover = takeover or {}
|
||||||
|
takeover[cur.src] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if takeover then
|
||||||
|
for ch, cur in pairs(moveSfxChannels) do
|
||||||
|
if takeover[cur.src] then moveSfxChannels[ch] = nil end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true, takeover
|
||||||
end
|
end
|
||||||
|
|
||||||
local function noteMoveSfx(data, def, src)
|
local function noteMoveSfx(data, def, src)
|
||||||
if not src or type(def) ~= "table" or not def.address then
|
if not src or type(def) ~= "table" or not def.address then
|
||||||
lastMoveSfx = nil
|
moveSfxChannels = {}
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
lastMoveSfx = {
|
local channels = require("src.core.ChipSynth").effectChannels(data, def)
|
||||||
src = src, rank = def.address, engine = def.engine,
|
if not channels then
|
||||||
channels = require("src.core.ChipSynth").effectChannels(data, def),
|
moveSfxChannels = {}
|
||||||
}
|
return
|
||||||
|
end
|
||||||
|
local entry = { src = src, address = def.address, engine = def.engine }
|
||||||
|
for _, ch in ipairs(channels) do moveSfxChannels[ch] = entry end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- constants/music_constants.asm:4
|
||||||
|
local function sfxHeaderId(def)
|
||||||
|
if type(def) ~= "table" or not def.address then return nil end
|
||||||
|
local rel = def.address - 0x4000
|
||||||
|
if rel <= 0 or rel % 3 ~= 0 then return nil end
|
||||||
|
return rel / 3
|
||||||
|
end
|
||||||
|
|
||||||
|
local function remainingFrames(src)
|
||||||
|
local ok, dur = pcall(src.getDuration, src, "seconds")
|
||||||
|
if not ok or type(dur) ~= "number" then return nil end
|
||||||
|
local pos
|
||||||
|
ok, pos = pcall(src.tell, src, "seconds")
|
||||||
|
if not ok or type(pos) ~= "number" then return nil end
|
||||||
|
return math.max(0, math.ceil((dur - pos) * 60))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- audio/engine_2.asm:1077-1096, :991-1013, :1015-1033
|
||||||
|
local function plainMoveFrames(data, def, channels)
|
||||||
|
local id = sfxHeaderId(def)
|
||||||
|
local sfx = data.audio and data.audio.sfx
|
||||||
|
if not (id and sfx and channels) then return 0 end
|
||||||
|
local first = sfxHeaderId(sfx.Peck) -- constants/music_constants.asm:178
|
||||||
|
local last = sfxHeaderId(sfx.Trainer_Appeared) -- constants/music_constants.asm:228
|
||||||
|
if not (first and last) then return 0 end
|
||||||
|
local claimed = {}
|
||||||
|
for _, ch in ipairs(channels) do claimed[ch] = true end
|
||||||
|
local base = (claimed[5] or claimed[8]) and id or 0
|
||||||
|
local others = {}
|
||||||
|
for _, ch in ipairs({ 5, 8 }) do
|
||||||
|
local cur = (not claimed[ch]) and moveSfxChannels[ch] or nil
|
||||||
|
if cur and cur.engine == def.engine and sourceAlive(cur) then
|
||||||
|
local otherId = sfxHeaderId(cur)
|
||||||
|
local rem = remainingFrames(cur.src)
|
||||||
|
if otherId and rem and rem > 0 then
|
||||||
|
others[#others + 1] = { id = otherId, rem = rem }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(others, function(a, b) return a.rem < b.rem end)
|
||||||
|
local plain = 0
|
||||||
|
for drop = 0, #others do
|
||||||
|
local combined = base
|
||||||
|
for i = drop + 1, #others do
|
||||||
|
combined = bit.bor(combined, others[i].id)
|
||||||
|
end
|
||||||
|
if combined >= first and combined <= last then return plain end
|
||||||
|
if drop < #others then plain = others[drop + 1].rem end
|
||||||
|
end
|
||||||
|
return plain
|
||||||
end
|
end
|
||||||
|
|
||||||
function Sound.playMove(data, anim)
|
function Sound.playMove(data, anim)
|
||||||
@@ -466,13 +526,20 @@ function Sound.playMove(data, anim)
|
|||||||
local name = anim.sound
|
local name = anim.sound
|
||||||
local pitch, tempo = anim.pitch or 0, anim.tempo or 0x80
|
local pitch, tempo = anim.pitch or 0, anim.tempo or 0x80
|
||||||
local def = sfx[name]
|
local def = sfx[name]
|
||||||
if not sfxChannelGate(data, def) then return end
|
local allowed, superseded = sfxChannelGate(data, def)
|
||||||
|
if not allowed then return end
|
||||||
local src
|
local src
|
||||||
-- a chip program synthesizes the modified variant on demand; a file def
|
-- a chip program synthesizes the modified variant on demand; a file def
|
||||||
-- can only reach for a pre-rendered one
|
-- can only reach for a pre-rendered one
|
||||||
if isChipDef(def) then
|
if isChipDef(def) then
|
||||||
src = playPath(data, ("%s@%02x%02x"):format(name, pitch, tempo),
|
local plain = 0
|
||||||
def, pitch, tempo)
|
if pitch ~= 0 or tempo ~= 0x80 then
|
||||||
|
local channels = require("src.core.ChipSynth").effectChannels(data, def)
|
||||||
|
plain = plainMoveFrames(data, def, channels)
|
||||||
|
end
|
||||||
|
local key = ("%s@%02x%02x"):format(name, pitch, tempo)
|
||||||
|
if plain > 0 then key = ("%s~%d"):format(key, plain) end
|
||||||
|
src = playPath(data, key, def, pitch, tempo, plain)
|
||||||
else
|
else
|
||||||
local key = ("%s@%02x%02x"):format(name, pitch, tempo)
|
local key = ("%s@%02x%02x"):format(name, pitch, tempo)
|
||||||
if (pitch ~= 0 or tempo ~= 0x80) and sfx[key] then
|
if (pitch ~= 0 or tempo ~= 0x80) and sfx[key] then
|
||||||
@@ -481,6 +548,10 @@ function Sound.playMove(data, anim)
|
|||||||
src = playPath(data, name, def)
|
src = playPath(data, name, def)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
-- audio/engine_2.asm:1537
|
||||||
|
if superseded then
|
||||||
|
for old in pairs(superseded) do pcall(old.stop, old) end
|
||||||
|
end
|
||||||
if src then
|
if src then
|
||||||
played("move", name)
|
played("move", name)
|
||||||
noteMoveSfx(data, def, src)
|
noteMoveSfx(data, def, src)
|
||||||
@@ -711,7 +782,7 @@ end
|
|||||||
-- hot reload / jukebox A-B: drop one key's sources (its pitch-tempo
|
-- hot reload / jukebox A-B: drop one key's sources (its pitch-tempo
|
||||||
-- variants included) or all of them, so the next play re-resolves the def
|
-- variants included) or all of them, so the next play re-resolves the def
|
||||||
function Sound.invalidate(name)
|
function Sound.invalidate(name)
|
||||||
lastMoveSfx = nil -- its source is about to be dropped or stopped
|
moveSfxChannels = {} -- their sources are about to be dropped or stopped
|
||||||
-- Same for wCurSFX, and a reloaded table can repoint the id order.
|
-- Same for wCurSFX, and a reloaded table can repoint the id order.
|
||||||
curSfx = nil
|
curSfx = nil
|
||||||
sfxIds = nil
|
sfxIds = nil
|
||||||
|
|||||||
@@ -166,7 +166,8 @@ function EvolutionState:draw()
|
|||||||
if sprite then
|
if sprite then
|
||||||
local x = math.floor((160 - sprite:getWidth()) / 2)
|
local x = math.floor((160 - sprite:getWidth()) / 2)
|
||||||
local y = math.max(8, 64 - sprite:getHeight())
|
local y = math.max(8, 64 - sprite:getHeight())
|
||||||
love.graphics.draw(sprite, x, y)
|
-- engine/movie/evolution.asm:103
|
||||||
|
love.graphics.draw(sprite, x + sprite:getWidth(), y, 0, -1, 1)
|
||||||
if spriteTrueColor then
|
if spriteTrueColor then
|
||||||
require("src.render.PaletteFX").markTrueColor(x, y, sprite:getDimensions())
|
require("src.render.PaletteFX").markTrueColor(x, y, sprite:getDimensions())
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -445,7 +445,8 @@ function TradeAnim:draw()
|
|||||||
-- mon pic on the BG at hlcoord 7, 2; info box on the window at
|
-- mon pic on the BG at hlcoord 7, 2; info box on the window at
|
||||||
-- hWY $50, so it sits in the bottom half of the screen
|
-- hWY $50, so it sits in the bottom half of the screen
|
||||||
if self.monVisible and self.sentSprite then
|
if self.monVisible and self.sentSprite then
|
||||||
love.graphics.draw(self.sentSprite, 56, 16)
|
-- engine/movie/trade.asm:751
|
||||||
|
love.graphics.draw(self.sentSprite, 56 + self.sentSprite:getWidth(), 16, 0, -1, 1)
|
||||||
if self.sentSpriteTrueColor then
|
if self.sentSpriteTrueColor then
|
||||||
require("src.render.PaletteFX").markTrueColor(
|
require("src.render.PaletteFX").markTrueColor(
|
||||||
56 - self.scx, 16, self.sentSprite:getDimensions())
|
56 - self.scx, 16, self.sentSprite:getDimensions())
|
||||||
@@ -501,7 +502,8 @@ function TradeAnim:draw()
|
|||||||
|
|
||||||
elseif p == "show_enemy" then
|
elseif p == "show_enemy" then
|
||||||
if self.monVisible and self.recvSprite then
|
if self.monVisible and self.recvSprite then
|
||||||
love.graphics.draw(self.recvSprite, 56, 16)
|
-- engine/movie/trade.asm:751
|
||||||
|
love.graphics.draw(self.recvSprite, 56 + self.recvSprite:getWidth(), 16, 0, -1, 1)
|
||||||
if self.recvSpriteTrueColor then
|
if self.recvSpriteTrueColor then
|
||||||
require("src.render.PaletteFX").markTrueColor(
|
require("src.render.PaletteFX").markTrueColor(
|
||||||
56, 16, self.recvSprite:getDimensions())
|
56, 16, self.recvSprite:getDimensions())
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
return function(game)
|
||||||
|
local U = dofile("tests/drivers/util.lua")
|
||||||
|
local DIR = os.getenv("SHOT_DIR") or "/tmp/bug1412"
|
||||||
|
local Pokemon = require("src.pokemon.Pokemon")
|
||||||
|
local Evolution = require("src.pokemon.Evolution")
|
||||||
|
local Screens = require("src.ui.Screens")
|
||||||
|
local TradeAnim = require("src.ui.TradeAnim")
|
||||||
|
local TextBox = require("src.render.TextBox")
|
||||||
|
|
||||||
|
local function check(label, ok)
|
||||||
|
U.log(ok and "PASS" or "FAIL", label)
|
||||||
|
return ok
|
||||||
|
end
|
||||||
|
|
||||||
|
U.log("Issue #1412: mon pics must be mirrored on the evolution and trade")
|
||||||
|
U.log("screens, and only there; battle keeps the raw orientation.")
|
||||||
|
|
||||||
|
U.teleport(game, "ROUTE_1", 5, 5, "down")
|
||||||
|
U.wait(10)
|
||||||
|
check("the overworld fixture is ready", game.overworld ~= nil)
|
||||||
|
|
||||||
|
local mon = Pokemon.new(game.data, "PIKACHU", 20)
|
||||||
|
game.save.party = { mon }
|
||||||
|
-- engine/movie/evolution.asm:103
|
||||||
|
Evolution.evolve(game, mon, "RAICHU", nil, "ITEM")
|
||||||
|
U.wait(12)
|
||||||
|
local top = game.stack:top()
|
||||||
|
check("the evolution screen opened",
|
||||||
|
top and top.screenId == "EvolutionState")
|
||||||
|
U.shot(game, DIR .. "/bug1412_evo_old_pikachu.png")
|
||||||
|
|
||||||
|
for _ = 1, 600 do
|
||||||
|
if top.done then break end
|
||||||
|
U.wait(1)
|
||||||
|
end
|
||||||
|
check("the evolution finished", top.done and not top.canceled)
|
||||||
|
U.wait(2)
|
||||||
|
U.shot(game, DIR .. "/bug1412_evo_new_raichu.png")
|
||||||
|
for _ = 1, 60 do
|
||||||
|
if game.stack:top() == game.overworld then break end
|
||||||
|
U.tap(game, "a")
|
||||||
|
U.wait(4)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- engine/movie/trade.asm:751
|
||||||
|
local sent = Pokemon.new(game.data, "SPEAROW", 10)
|
||||||
|
local recv = Pokemon.new(game.data, "FARFETCHD", 10)
|
||||||
|
recv.nickname = "DUX"
|
||||||
|
recv.traded = true
|
||||||
|
recv.ot = "TRAINER"
|
||||||
|
U.teleport(game, "ROUTE_1", 5, 5, "down")
|
||||||
|
local anim = Screens.push(game, "TradeAnim", {
|
||||||
|
sent = sent, received = recv, enemyName = "TRAINER",
|
||||||
|
})
|
||||||
|
check("the trade cinematic opened", getmetatable(anim) == TradeAnim)
|
||||||
|
for _ = 1, 200 do
|
||||||
|
if anim.sub == "hold" then break end
|
||||||
|
U.wait(1)
|
||||||
|
end
|
||||||
|
check("the sent SPEAROW pic is on screen",
|
||||||
|
anim.phase == "show_player" and anim.sub == "hold" and anim.monVisible)
|
||||||
|
U.shot(game, DIR .. "/bug1412_trade_sent_spearow.png")
|
||||||
|
|
||||||
|
local function topIsText()
|
||||||
|
return getmetatable(game.stack:top()) == TextBox
|
||||||
|
end
|
||||||
|
for _ = 1, 8000 do
|
||||||
|
if anim.phase == "show_enemy" and anim.monVisible then break end
|
||||||
|
if anim.phase == "done" then break end
|
||||||
|
if anim.waitingText or topIsText() then
|
||||||
|
U.wait(1)
|
||||||
|
else
|
||||||
|
anim:update(1 / 60)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
U.wait(2)
|
||||||
|
check("the received FARFETCH'D pic is on screen",
|
||||||
|
anim.phase == "show_enemy" and anim.monVisible)
|
||||||
|
U.shot(game, DIR .. "/bug1412_trade_recv_farfetchd.png")
|
||||||
|
|
||||||
|
U.log("shots in", DIR)
|
||||||
|
U.log("Right: all four pics are mirrored left-right compared to the same")
|
||||||
|
U.log("mon's battle front sprite (compare the hardware capture on #1412:")
|
||||||
|
U.log("PIKACHU's face points the other way than it does in battle).")
|
||||||
|
U.log("Wrong: any of the four matches the battle orientation.")
|
||||||
|
U.log("Battle, pokedex, title, Hall of Fame, League PC, credits, and the")
|
||||||
|
U.log("museum fossils are untouched and must still match the cart.")
|
||||||
|
|
||||||
|
while true do
|
||||||
|
coroutine.yield()
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
-- data/moves/sfx.asm:46, data/moves/animations.asm:448, audio/engine_2.asm:1077
|
||||||
|
return function(game)
|
||||||
|
local U = dofile("tests/drivers/util.lua")
|
||||||
|
local Pokemon = require("src.pokemon.Pokemon")
|
||||||
|
local BattleState = require("src.battle.BattleState")
|
||||||
|
local DIR = os.getenv("SHOT_DIR") or "/tmp/shots"
|
||||||
|
|
||||||
|
local function check(label, ok)
|
||||||
|
U.log(ok and "PASS" or "FAIL", label)
|
||||||
|
return ok
|
||||||
|
end
|
||||||
|
|
||||||
|
local opts = game.save.options or {}
|
||||||
|
check(("sfx volume %d (needs > 0 to hear anything)"):format(opts.sfxVol or 7),
|
||||||
|
(opts.sfxVol or 7) > 0)
|
||||||
|
check("battle animations are on", opts.animations ~= false)
|
||||||
|
|
||||||
|
local mdef = game.data.moves.LEER
|
||||||
|
check("LEER is in the move table", mdef ~= nil)
|
||||||
|
local anim = mdef and mdef.anim
|
||||||
|
check(("LEER maps to %s pitch %s tempo %s (wants Battle_31 255 64)"):format(
|
||||||
|
anim and tostring(anim.sound) or "?",
|
||||||
|
anim and tostring(anim.pitch) or "?",
|
||||||
|
anim and tostring(anim.tempo) or "?"),
|
||||||
|
anim ~= nil and anim.sound == "Battle_31"
|
||||||
|
and anim.pitch == 255 and anim.tempo == 64)
|
||||||
|
|
||||||
|
local lead = Pokemon.new(game.data, "CHARMANDER", 20)
|
||||||
|
lead.moves = { { id = "LEER", pp = 30 } }
|
||||||
|
game.save.party = { lead }
|
||||||
|
U.teleport(game, "ROUTE_1", 5, 5, "down")
|
||||||
|
U.wait(15)
|
||||||
|
local ow = game.overworld
|
||||||
|
check("standing on ROUTE_1", ow.map.id == "ROUTE_1")
|
||||||
|
|
||||||
|
local ChipAudio = require("src.core.ChipAudio")
|
||||||
|
local renders = {}
|
||||||
|
local origNewSfx = ChipAudio.newSfx
|
||||||
|
ChipAudio.newSfx = function(data, name, pitch, tempo, header, plain)
|
||||||
|
renders[#renders + 1] = { name = name, pitch = pitch, tempo = tempo,
|
||||||
|
plain = plain }
|
||||||
|
return origNewSfx(data, name, pitch, tempo, header, plain)
|
||||||
|
end
|
||||||
|
|
||||||
|
local battle = BattleState.newWild(game, "PIDGEY", 8)
|
||||||
|
battle.onFinish = function() end
|
||||||
|
ow:pushBattle(battle)
|
||||||
|
|
||||||
|
local function waitPhase(phase, tries)
|
||||||
|
for _ = 1, tries do
|
||||||
|
if battle.phase == phase then return true end
|
||||||
|
U.tap(game, "a")
|
||||||
|
U.wait(6)
|
||||||
|
end
|
||||||
|
return battle.phase == phase
|
||||||
|
end
|
||||||
|
|
||||||
|
U.log("")
|
||||||
|
U.log("LISTEN: the cart's LEER opens with a short PIERCING high tone")
|
||||||
|
U.log(" (about a third of a second, while the seed-drop noise is still")
|
||||||
|
U.log(" running) and only then falls to the slow low buzz. Before the")
|
||||||
|
U.log(" fix the recomp skipped the piercing part and played the whole")
|
||||||
|
U.log(" sound as the low buzz.")
|
||||||
|
U.log("")
|
||||||
|
|
||||||
|
for round = 1, 3 do
|
||||||
|
check(("round %d: menu is up"):format(round), waitPhase("menu", 150))
|
||||||
|
U.tap(game, "a")
|
||||||
|
check(("round %d: move list is up"):format(round),
|
||||||
|
waitPhase("moveSelect", 12))
|
||||||
|
U.tap(game, "a")
|
||||||
|
U.wait(150)
|
||||||
|
if round == 1 then U.shot(game, DIR .. "/bug1414_leer.png") end
|
||||||
|
end
|
||||||
|
|
||||||
|
ChipAudio.newSfx = origNewSfx
|
||||||
|
local sawPlain, sawSeed = false, false
|
||||||
|
for _, r in ipairs(renders) do
|
||||||
|
if r.name == "Battle_31" and r.pitch == 255 and r.tempo == 64
|
||||||
|
and (r.plain or 0) > 0 then
|
||||||
|
sawPlain = true
|
||||||
|
end
|
||||||
|
if r.name == "Battle_1B" then sawSeed = true end
|
||||||
|
end
|
||||||
|
check("the seed-drop noise (Battle_1B) rendered", sawSeed)
|
||||||
|
check("LEER's Battle_31 rendered with an unmodified opening"
|
||||||
|
.. " while the noise still ran", sawPlain)
|
||||||
|
for _, r in ipairs(renders) do
|
||||||
|
U.log((" rendered %s pitch=%s tempo=%s plainFrames=%s"):format(
|
||||||
|
r.name, tostring(r.pitch), tostring(r.tempo), tostring(r.plain)))
|
||||||
|
end
|
||||||
|
|
||||||
|
U.log("done; the window stays open, pick LEER again to re-listen")
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user