Files
gen1recomp/tests/engine/battle_substitute_doll_timing_bug2030.lua

138 lines
5.1 KiB
Lua

-- ../pokered/engine/battle/move_effects/substitute.asm:2-3, :47-55
-- ../pokered/engine/battle/animations.asm:1936-1973
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.modkit")
local Data = T.fixtures.fresh()
require("src.render.Font").load(Data)
local BattleState = require("src.battle.BattleState")
local Pokemon = require("src.pokemon.Pokemon")
local SaveData = require("src.core.SaveData")
local Timing = require("src.core.Timing")
local TypeChart = require("src.battle.TypeChart")
TypeChart.load(Data)
Data.moves.FIX_SUBSTITUTE = {
id = "FIX_SUBSTITUTE", name = "FIX SUB", index = 164,
power = 0, accuracy = 100, type = "NORMAL", pp = 10,
effect = "SUBSTITUTE_EFFECT",
anim = { seq = { { effect = "SE_SLIDE_MON_OFF" },
{ subanim = 71, delay = 8 },
{ effect = "SE_SUBSTITUTE_MON" } } },
}
local function newBattle(animations)
local save = SaveData.newGame()
save.party = { Pokemon.new(Data, "FIXMON_A", 30) }
save.options = save.options or {}
save.options.animations = animations
local game = { data = Data, save = save,
stack = { top = function() return nil end, push = function() end } }
local battle = BattleState.newWild(game, "FIXMON_C", 30)
battle.queue, battle.nextInsert = {}, 0
return battle
end
local function indexOf(battle, pred)
for i, item in ipairs(battle.queue) do
if pred(item) then return i end
end
return nil
end
local function useSub(battle)
battle.enemy.mon.hp = battle.enemy.mon.stats.hp
battle:performMove(battle.enemy, battle.player,
{ id = "FIX_SUBSTITUTE", pp = 10 })
end
for _, animations in ipairs({ true, false }) do
local label = animations and "animations on" or "animations off"
local battle = newBattle(animations)
useSub(battle)
T.check(battle.enemy.substituteHP ~= nil,
label .. ": the substitute is up in RAM as soon as the effect runs")
T.check(battle.enemy.substitutePending == true,
label .. ": but the doll is not on screen yet")
T.eq(battle:faintPicKind(battle.enemy), "pic",
label .. ": faintPicKind still reports the mon's own pic")
local used = indexOf(battle, function(it)
return it.text and it.text:find("used", 1, true) ~= nil
end)
local wait = indexOf(battle, function(it)
return it.wait == Timing.SUBSTITUTE_ENTRY
end)
local anim = indexOf(battle, function(it) return it.anim == "FIX_SUBSTITUTE" end)
local fn = indexOf(battle, function(it) return it.fn ~= nil end)
local said = indexOf(battle, function(it)
return it.text and it.text:find("SUBSTITUTE!", 1, true) ~= nil
end)
T.check(used ~= nil, label .. ": the announcement is queued")
T.check(wait ~= nil, label .. ": the 50-frame entry hold is queued")
T.check(fn ~= nil, label .. ": the doll reveal is queued as an act row")
T.check(said ~= nil, label .. ": _SubstituteText is queued")
T.check(used < wait, label .. ": the hold follows the announcement")
T.check(wait < fn, label .. ": the hold runs before the doll appears")
T.check(fn < said, label .. ": the doll appears before _SubstituteText")
if animations then
T.check(anim ~= nil, label .. ": the move animation row is queued")
T.check(wait < anim, label .. ": the hold runs before the animation")
T.check(anim < fn, label .. ": the animation plays before the doll lands")
else
T.eq(anim, nil, label .. ": no move animation row, so no 30-frame hold")
end
battle.queue[fn].fn()
T.eq(battle.enemy.substitutePending, nil,
label .. ": running the act row puts the doll on screen")
T.eq(battle:faintPicKind(battle.enemy), "doll",
label .. ": and faintPicKind reports the doll")
end
do
-- ../pokered/data/battle_anims/special_effect_pointers.asm:45
local battle = newBattle(true)
useSub(battle)
battle.animAttackerIsPlayer = false
local pf = battle:picFxFor(battle.enemy)
pf.kind, pf.hidden = "slideOff", true
battle:applyAnimEffect({ effect = "SE_SUBSTITUTE_MON" })
T.eq(battle.enemy.substitutePending, nil,
"SE_SUBSTITUTE_MON reveals the doll on its own")
T.eq(pf.kind, nil, "AnimationShowMonPic clears the slide-off")
T.eq(pf.hidden, nil, "AnimationShowMonPic unhides the pic slot")
end
do
local battle = newBattle(true)
useSub(battle)
battle.animAttackerIsPlayer = false
battle:applyAnimEffect({ effect = "SE_SLIDE_MON_OFF" })
T.eq(battle:picFxFor(battle.enemy).kind, "slideOff",
"the user still slides off under a pending substitute")
end
do
for _, case in ipairs({ "already", "tooWeak" }) do
local battle = newBattle(true)
battle.enemy.mon.hp = battle.enemy.mon.stats.hp
if case == "already" then
battle.enemy.substituteHP = 10
else
battle.enemy.mon.hp = math.floor(battle.enemy.mon.stats.hp / 4)
end
battle:performMove(battle.enemy, battle.player,
{ id = "FIX_SUBSTITUTE", pp = 10 })
T.check(indexOf(battle, function(it)
return it.wait == Timing.SUBSTITUTE_ENTRY
end) ~= nil, case .. ": the 50-frame hold is paid before the failure text")
T.eq(battle.enemy.substitutePending, nil,
case .. ": a failed substitute queues no doll reveal")
end
end
T.finish("the substitute doll lands with its animation (#2030)")