mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
186 lines
6.9 KiB
Lua
186 lines
6.9 KiB
Lua
-- A move the target is immune to, and the two move lock-ins, on the real
|
|
-- battle screen.
|
|
--
|
|
-- POKEPORT_GAME=gold POKEPORT_DRIVER=tests/drivers/gold_noeffect_anim.lua love .
|
|
--
|
|
-- What a human is here to see:
|
|
--
|
|
-- 01 / 02 TACKLE (NORMAL) against a GASTLY (GHOST). The move text prints,
|
|
-- the screen holds still for MoveDelay, and then "It doesn't affect
|
|
-- GASTLY..." appears. No attack animation plays at any point.
|
|
-- BattleCommand_Stab's `.GotMatchup` writes wAttackMissed for a
|
|
-- zero matchup (effect_commands.asm:1337), `stab` runs ahead of
|
|
-- `moveanim` in every damaging effect list
|
|
-- (data/moves/effects.asm:5), and BattleCommand_MoveAnimNoSub
|
|
-- early-outs on wAttackMissed (:1958).
|
|
-- 03 LEECH SEED on a Grass type: same shape, `.grass` ->
|
|
-- AnimateFailedMove (move_effects/leech_seed.asm).
|
|
-- 04 - 09 ROLLOUT. CheckPlayerLockedIn quits ParsePlayerAction while
|
|
-- SUBSTATUS_ROLLOUT is set (core.asm:546), so the FIGHT menu never
|
|
-- comes back at all: after the one selection in 04 the move repeats
|
|
-- on its own for four more turns and the menu only returns once the
|
|
-- fifth hit clears the bit (09). The PP counter moves exactly once,
|
|
-- on the opening turn, because checkrollout skips past
|
|
-- doturn_command for every later turn of the lock.
|
|
--
|
|
-- The animation suppression is the deliverable here: no headless assertion can
|
|
-- see whether a sprite moved, so this driver is the check.
|
|
local U = require("tests.drivers.util")
|
|
|
|
local Mon = require("src.battle.gen2.Mon")
|
|
|
|
return function(game)
|
|
local out = os.getenv("POKEPORT_SHOT_DIR") or "/tmp/gold-noeffect-anim"
|
|
|
|
local function tap(button, frames)
|
|
game.input.pressQueue[#game.input.pressQueue + 1] = button
|
|
game.input.state[button] = true
|
|
U.wait(2)
|
|
game.input.state[button] = false
|
|
U.wait(frames or 6)
|
|
end
|
|
|
|
U.wait(45)
|
|
local world = game.world
|
|
assert(world and world.map, "gold world did not boot")
|
|
|
|
-- One mon carrying exactly the three moves this driver exercises, so the
|
|
-- FIGHT list is readable in the shots.
|
|
local player = Mon.new(game.data, "CYNDAQUIL", 30)
|
|
assert(player, "could not build a CYNDAQUIL from pokemon.lua")
|
|
player.moves = {
|
|
{ id = "TACKLE", pp = 35, maxPp = 35 },
|
|
{ id = "LEECH_SEED", pp = 10, maxPp = 10 },
|
|
{ id = "ROLLOUT", pp = 20, maxPp = 20 },
|
|
}
|
|
game.save.party = { player }
|
|
game.save.inventory = {}
|
|
|
|
-- Waits for the battle screen to be sitting on its menu again.
|
|
local function toMenu(battle, limit)
|
|
for _ = 1, (limit or 400) do
|
|
if battle.phase == "menu" or battle.battle.over then return end
|
|
tap("a", 3)
|
|
end
|
|
end
|
|
|
|
local function fight(battle, slot)
|
|
tap("a") -- FIGHT
|
|
U.wait(6)
|
|
for _ = 2, slot do tap("down", 4) end
|
|
U.wait(4)
|
|
return slot
|
|
end
|
|
|
|
local function openBattle(species, level)
|
|
local wild = Mon.new(game.data, species, level)
|
|
assert(wild, "could not build a wild " .. species)
|
|
assert(world:startBattle({ wild = wild }), "startBattle failed")
|
|
local battle
|
|
for _ = 1, 600 do
|
|
local top = game.stack:top()
|
|
if top and top.battle then battle = top break end
|
|
U.wait(1)
|
|
end
|
|
assert(battle and battle.battle, "battle screen never came up")
|
|
toMenu(battle)
|
|
return battle, wild
|
|
end
|
|
|
|
-- ---- immunity: NORMAL into GHOST ---------------------------------------
|
|
local battle = openBattle("GASTLY", 8)
|
|
U.shot(game, out .. "/00-menu.png")
|
|
fight(battle, 1) -- TACKLE
|
|
tap("a")
|
|
-- Straight after the "used TACKLE!" line is exactly where the animation
|
|
-- would be. Both shots must show a still screen.
|
|
U.wait(6)
|
|
U.shot(game, out .. "/01-tackle-no-anim.png")
|
|
U.wait(24)
|
|
U.shot(game, out .. "/02-doesnt-affect.png")
|
|
toMenu(battle)
|
|
|
|
-- ---- LEECH SEED into a Grass type --------------------------------------
|
|
for _ = 1, 200 do
|
|
if not (game.stack:top() and game.stack:top().battle) then break end
|
|
tap("b", 3)
|
|
if battle.battle.over then break end
|
|
tap("a", 3)
|
|
end
|
|
U.wait(30)
|
|
battle = openBattle("BELLSPROUT", 8)
|
|
fight(battle, 2) -- LEECH SEED
|
|
tap("a")
|
|
U.wait(6)
|
|
U.shot(game, out .. "/03-leech-seed-no-anim.png")
|
|
toMenu(battle)
|
|
|
|
-- ---- ROLLOUT locks the FIGHT list --------------------------------------
|
|
--
|
|
-- A high-level target so the five turns actually happen.
|
|
for _ = 1, 200 do
|
|
if not (game.stack:top() and game.stack:top().battle) then break end
|
|
tap("b", 3)
|
|
if battle.battle.over then break end
|
|
tap("a", 3)
|
|
end
|
|
U.wait(30)
|
|
battle = openBattle("SNORLAX", 40)
|
|
|
|
-- Both HP pools are widened first. ROLLOUT's power doubles every turn
|
|
-- (BattleCommand_RolloutPower), and at these levels either side faints inside
|
|
-- the five, which ends the battle and leaves the deliverable unshot: the lock
|
|
-- is what this segment is here to photograph, not a damage race.
|
|
local function widen(mon)
|
|
if not mon then return end
|
|
mon.stats = mon.stats or {}
|
|
mon.stats.hp, mon.maxHp, mon.hp = 999, 999, 999
|
|
end
|
|
widen(battle.battle.player)
|
|
widen(battle.battle.enemy)
|
|
|
|
local ppBefore = player.moves[3].pp
|
|
tap("a") -- FIGHT
|
|
U.wait(8)
|
|
U.shot(game, out .. "/04-rollout-picked-once.png")
|
|
tap("down", 4)
|
|
tap("down", 4)
|
|
tap("a") -- ROLLOUT, the only selection
|
|
|
|
-- From here the player never chooses again. A is still tapped, but only to
|
|
-- page the text along: if the menu ever reappears while the bit is set, the
|
|
-- port has lost CheckPlayerLockedIn. `rolloutLock` is the port's name for
|
|
-- SUBSTATUS_ROLLOUT and `rampCount` its counter minus one, so
|
|
-- `rampCount + 1` is the cart's wPlayerRolloutCount.
|
|
local menuDuringLock, shot, armed = false, {}, false
|
|
for _ = 1, 600 do
|
|
if battle.battle.over then break end
|
|
local v = player.volatile or {}
|
|
if v.rolloutLock then
|
|
armed = true
|
|
if battle.phase == "menu" then menuDuringLock = true end
|
|
local count = (v.rampCount or 0) + 1
|
|
if not shot[count] then
|
|
shot[count] = true
|
|
U.shot(game, ("%s/0%d-rollout-turn%d.png"):format(out, 4 + count, count))
|
|
end
|
|
elseif armed then
|
|
-- The fifth hit is the one that clears the bit, so the loop leaves on it
|
|
-- and 09 below is the menu coming back.
|
|
break
|
|
end
|
|
tap("a", 3)
|
|
end
|
|
toMenu(battle)
|
|
U.shot(game, out .. "/09-lock-released.png")
|
|
|
|
local spent = ppBefore - player.moves[3].pp
|
|
print(("[driver] %s the opening hit set SUBSTATUS_ROLLOUT")
|
|
:format(armed and "ok " or "FAIL"))
|
|
print(("[driver] %s the FIGHT menu stayed shut for the whole lock")
|
|
:format(menuDuringLock and "FAIL" or "ok "))
|
|
print(("[driver] %s ROLLOUT spent %d PP (1 is the cart: only turn one pays)")
|
|
:format(spent == 1 and "ok " or "FAIL", spent))
|
|
print("[driver] shots in " .. out)
|
|
end
|