Files
gen1recomp/tests/engine/fishing_cast_timing_bug2064.lua

180 lines
6.6 KiB
Lua

-- engine/items/item_effects.asm:1893
-- engine/overworld/player_animations.asm:378, :453
-- luajit tests/engine/fishing_cast_timing_bug2064.lua
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check, eq = T.check, T.eq
love = love or require("tests.love_stub")
local ItemEffects = require("src.inventory.ItemEffects")
local OverworldState = require("src.world.OverworldController")
check(type(ItemEffects.itemUseLine) == "function",
"ItemEffects exports the used-line helper")
eq(ItemEffects.itemUseLine({}, { player = { name = "RED" } }, "OLD ROD"),
"RED used\nOLD ROD!", "it is the _ItemUseText001 line")
local function setUpvalue(fn, name, value)
local i = 1
while true do
local n = debug.getupvalue(fn, i)
if not n then return false end
if n == name then
debug.setupvalue(fn, i, value)
return true
end
i = i + 1
end
end
eq(OverworldState.fishAnimFrames(false), 190, "no bite: 80 + 10 + 100 frames")
eq(OverworldState.fishAnimFrames(true), 280, "a bite adds 30 shake + 60 bubble")
local function runSteps(bite)
local fa, evs = { frames = 0, bite = bite }, {}
for i = 1, OverworldState.fishAnimFrames(bite) do
local ev = OverworldState.stepFishAnim(fa)
if ev then evs[#evs + 1] = { ev, i } end
end
return evs
end
local dry = runSteps(false)
eq(#dry, 2, "a dry cast is exactly two events")
eq(dry[1][1] .. "@" .. dry[1][2], "cast@90", "the pose goes up on frame 90")
eq(dry[2][1] .. "@" .. dry[2][2], "verdict@190", "the verdict lands on frame 190")
local wet = runSteps(true)
eq(wet[1][1] .. "@" .. wet[1][2], "cast@90", "a bite casts on the same frame")
local shakes, bubbleAt, verdictAt = 0, nil, nil
for _, e in ipairs(wet) do
if e[1] == "shake" then shakes = shakes + 1 end
if e[1] == "bubble" then bubbleAt = e[2] end
if e[1] == "verdict" then verdictAt = e[2] end
end
eq(shakes, 10, ".ShakePlayerSprite toggles ten times (b = 10, Delay3)")
eq(bubbleAt, 221, "the bubble opens right after the 30 shake frames")
eq(verdictAt, 280, "and the text waits out the 60-frame bubble")
local function fakeOw(facing)
return setmetatable({ player = { facing = facing or "right", px = 0, py = 0 },
map = { id = "VIRIDIAN_CITY" } },
{ __index = OverworldState })
end
local ow = fakeOw("right")
local fa = { frames = 0, bite = true }
for i = 1, 89 do
OverworldState.tickFishAnim(ow, fa)
if i == 89 then
check(ow.fishing == nil and ow.player.fishing == nil,
"the rod and pose stay down for the whole used-line hold")
end
end
OverworldState.tickFishAnim(ow, fa)
check(ow.fishing ~= nil and ow.fishing.facing == "right",
"the cast raises the rod OAM for the faced direction")
eq(ow.player.fishing, true, "and the fishing pose with it")
local shakeSeen = {}
for _ = 91, 220 do
OverworldState.tickFishAnim(ow, fa)
if ow.player.fishShakeDy then shakeSeen[ow.player.fishShakeDy] = true end
end
check(shakeSeen[1], "the sprite offsets 1px during the shake")
eq(ow.player.fishShakeDy, 0, "an even toggle count leaves it level again")
OverworldState.tickFishAnim(ow, fa)
check(ow.emote ~= nil and ow.emote.npc == ow.player,
"the bubble hold is an emote over the player")
eq(ow.emote and ow.emote.frames, 60, "EmotionBubble's 60-frame DelayFrames")
eq(ow.emote and ow.emote.bubble, 1, "EXCLAMATION_BUBBLE")
eq(ow.player.fishShakeDy, nil, "the shake offset is cleared for it")
for _ = 222, 280 do OverworldState.tickFishAnim(ow, fa) end
eq(ow.emote, nil, "the bubble is gone when the verdict prints")
check(ow.fishing ~= nil, "the rod is still up for the verdict box (#321)")
local up = fakeOw("up")
local upFa = { frames = 0, bite = true }
for _ = 1, 221 do OverworldState.tickFishAnim(up, upFa) end
eq(up.fishing.hideRod, true, "facing up parks the rod OAM off the bubble")
for _ = 222, 280 do OverworldState.tickFishAnim(up, upFa) end
eq(up.fishing.hideRod, nil, "and gives it back for the verdict")
local pushed = {}
local FakeBox = { new = function(_, text, onDone, opts)
local box = { text = text, onDone = onDone, opts = opts }
return box
end }
local fakeGame = {
data = { items = { OLD_ROD = { name = "OLD ROD" },
SUPER_ROD = { name = "SUPER ROD" } } },
save = { player = { name = "RED" } },
stack = { push = function(_, box) pushed[#pushed + 1] = box end },
}
local pool, encStub
check(setUpvalue(OverworldState.goFishing, "Game", fakeGame), "Game is swappable")
check(setUpvalue(OverworldState.goFishing, "TextBox", FakeBox), "TextBox is swappable")
check(setUpvalue(OverworldState.goFishing, "fishingPool",
function() return pool end), "fishingPool is swappable")
check(setUpvalue(OverworldState.goFishing, "catchFrom",
function() return encStub end), "catchFrom is swappable")
local function cast(rod, ow2)
pushed = {}
OverworldState.goFishing(ow2, rod)
return pushed[1]
end
encStub = { species = "MAGIKARP", level = 5 }
local bite = cast("OLD_ROD", fakeOw("down"))
eq(bite.text, "RED used\nOLD ROD!", "the box prints the used line, not dots")
check(bite.opts and bite.opts.auto, "it is an auto box (no A press to skip)")
eq(bite.opts.auto.wait, false, "text_end: no trailing button wait")
eq(bite.opts.auto.delay, 280, "a bite holds the box for the whole animation")
check(type(bite.opts.auto.sound) == "function", "SFX_HEAL_AILMENT rides the box")
check(type(bite.opts.auto.tick) == "function", "the cast is driven per frame")
encStub = nil
pool = nil
local dryBox = cast("OLD_ROD", fakeOw("down"))
eq(dryBox.opts.auto.delay, 190, "a dry cast holds 190 frames")
local vow = fakeOw("down")
pushed = {}
OverworldState.fishVerdict(vow, nil, false)
eq(pushed[1].text, "Not even a nibble!", "no bite on a map with a group")
pushed[1].onDone()
eq(vow.fishing, nil, "the rod OAM goes out with the box")
eq(vow.fishPose, 10, "the pose holds the #384 tail")
pushed = {}
OverworldState.fishVerdict(vow, nil, true)
eq(pushed[1].text, "Looks like there's\nnothing here.",
"wRodResponse = 2 prints _NothingHereText (item_effects.asm:2855)")
pushed = {}
OverworldState.fishVerdict(vow, { species = "MAGIKARP", level = 5 }, false)
eq(pushed[1].text, "Oh!\nIt's a bite!", "a hooked fish announces itself")
pool = nil
local nothing = cast("SUPER_ROD", fakeOw("down"))
pushed = {}
nothing.onDone()
eq(pushed[1].text, "Looks like there's\nnothing here.",
"a SUPER ROD on a group-less map says nothing here")
pool = { { species = "TENTACOOL", level = 15 } }
local nibble = cast("SUPER_ROD", fakeOw("down"))
pushed = {}
nibble.onDone()
eq(pushed[1].text, "Not even a nibble!",
"a SUPER ROD on a real group that rolled nothing says not even a nibble")
T.finish("fishing_cast_timing_bug2064")