mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
123feef3a1
CLOSES #2425, CLOSES #2428, CLOSES #2431, CLOSES #2432, CLOSES #2433, CLOSES #2435, CLOSES #2436, CLOSES #2437, CLOSES #2440
338 lines
12 KiB
Lua
338 lines
12 KiB
Lua
#!/usr/bin/env luajit
|
|
-- pokefirered/src/party_menu_specials.c:44, :50, :61, :92
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local failed = 0
|
|
local function check(cond, msg)
|
|
if cond then
|
|
print("[ok] " .. msg)
|
|
else
|
|
failed = failed + 1
|
|
print("[FAIL] " .. msg)
|
|
end
|
|
end
|
|
|
|
local function finish()
|
|
if failed > 0 then
|
|
print(string.format("[FAIL] %d check(s) failed", failed))
|
|
os.exit(1)
|
|
end
|
|
print("[PASS] game3_moveteach_deleter")
|
|
os.exit(0)
|
|
end
|
|
|
|
local Cache = require("tests.game3_cache")
|
|
local cacheRoot = Cache.mount("pokemon/learnsets.lua")
|
|
if not cacheRoot then
|
|
print("[skip] game3_moveteach_deleter: " .. tostring(Cache.reason))
|
|
os.exit(0)
|
|
end
|
|
print("[info] FireRed cache at " .. cacheRoot)
|
|
|
|
local Pokemon = require("src.core.game3.pokemon")
|
|
Pokemon.install(nil)
|
|
|
|
local MoveLearn = require("src.core.game3.move_learn")
|
|
local Natives = require("src.core.game3.scripting.natives")
|
|
local SummaryMenu = require("src.ui.game3.summary_menu")
|
|
local Ctx = require("src.core.game3.scripting.ctx")
|
|
local Flags = require("src.core.game3.scripting.flags")
|
|
|
|
local TACKLE, GROWL, LEECH_SEED, VINE_WHIP = 33, 45, 73, 22
|
|
local CUT = 15
|
|
local BULBASAUR = 1
|
|
|
|
local function monAt(species, level)
|
|
local moves, pp, maxPp = Pokemon.movesAtLevel(species, level)
|
|
return { species = species, level = level, moves = moves, pp = pp, maxPp = maxPp }
|
|
end
|
|
|
|
local function fourMoveMon()
|
|
local mon = monAt(BULBASAUR, 50)
|
|
mon.moves = { TACKLE, GROWL, LEECH_SEED, VINE_WHIP }
|
|
mon.pp = { 35, 40, 10, 25 }
|
|
mon.maxPp = { 35, 40, 10, 25 }
|
|
return mon
|
|
end
|
|
|
|
print("[test] 1. MoveDeleterForgetMove clears the slot and shifts the rest up")
|
|
do
|
|
local mon = fourMoveMon()
|
|
check(MoveLearn.forgetMove(mon, 1) == true, "forgetting move slot 1 (0-based) reports success")
|
|
check(Pokemon.moveIdAt(mon, 1) == TACKLE, "slot 1 still holds TACKLE, got "
|
|
.. tostring(Pokemon.moveName(Pokemon.moveIdAt(mon, 1))))
|
|
check(Pokemon.moveIdAt(mon, 2) == LEECH_SEED, "LEECH SEED shifted down into slot 2, got "
|
|
.. tostring(Pokemon.moveName(Pokemon.moveIdAt(mon, 2))))
|
|
check(Pokemon.moveIdAt(mon, 3) == VINE_WHIP, "VINE WHIP shifted down into slot 3, got "
|
|
.. tostring(Pokemon.moveName(Pokemon.moveIdAt(mon, 3))))
|
|
check(Pokemon.moveIdAt(mon, 4) == nil, "slot 4 is now empty")
|
|
check(mon.pp[2] == 10 and mon.pp[3] == 25, "PP travelled with the shifted moves, got "
|
|
.. tostring(mon.pp[2]) .. "/" .. tostring(mon.pp[3]))
|
|
check(mon.maxPp[4] == nil and mon.pp[4] == nil, "the freed slot carries no PP")
|
|
check(Pokemon.moveSlotCount(mon) == 3, "the mon knows three moves, got "
|
|
.. tostring(Pokemon.moveSlotCount(mon)))
|
|
end
|
|
|
|
print("[test] 2. deleting the last slot leaves the others alone")
|
|
do
|
|
local mon = fourMoveMon()
|
|
MoveLearn.forgetMove(mon, 3)
|
|
check(Pokemon.moveIdAt(mon, 4) == nil and Pokemon.moveIdAt(mon, 3) == LEECH_SEED,
|
|
"VINE WHIP is gone and slot 3 still holds "
|
|
.. tostring(Pokemon.moveName(Pokemon.moveIdAt(mon, 3))))
|
|
check(MoveLearn.forgetMove(mon, 3) == false, "deleting an empty slot does nothing")
|
|
check(MoveLearn.forgetMove(mon, 4) == false, "an out of range slot does nothing")
|
|
end
|
|
|
|
print("[test] 3. PP Ups ride along with the shift")
|
|
do
|
|
local mon = fourMoveMon()
|
|
mon.ppBonuses = { 0, 3, 1, 0 }
|
|
MoveLearn.forgetMove(mon, 1)
|
|
check(mon.ppBonuses[2] == 1, "the third slot's PP Up bonus moved to slot 2, got "
|
|
.. tostring(mon.ppBonuses[2]))
|
|
check(mon.ppBonuses[4] == nil, "and the freed slot has none")
|
|
|
|
local packed = fourMoveMon()
|
|
-- pokefirered/src/pokemon.c:3904 RemoveMonPPBonus
|
|
packed.ppBonusesPacked = 0 + (3 * 4) + (1 * 16)
|
|
MoveLearn.forgetMove(packed, 1)
|
|
check(bit.band(bit.rshift(packed.ppBonusesPacked, 2), 3) == 1,
|
|
"the packed bonus shifted down too, got "
|
|
.. tostring(bit.band(bit.rshift(packed.ppBonusesPacked, 2), 3)))
|
|
end
|
|
|
|
print("[test] 4. GetNumMovesSelectedMonHas feeds the one-move branch")
|
|
do
|
|
local prevRuntime = package.loaded["src.core.game3.runtime"]
|
|
local party = { fourMoveMon(), monAt(BULBASAUR, 5) }
|
|
party[2].moves = { TACKLE }
|
|
party[2].pp = { 35 }
|
|
party[2].maxPp = { 35 }
|
|
package.loaded["src.core.game3.runtime"] = {
|
|
getSession = function() return { party = party } end,
|
|
isActive = function() return true end,
|
|
}
|
|
|
|
local ctx = Ctx.new({})
|
|
ctx.mode = "bytecode"
|
|
ctx.status = "running"
|
|
local handled = select(3, Natives.special(ctx, 0xDF, { log = function() end }))
|
|
check(handled == true, "special 0xDF is bound (pokefirered/data/specials.inc:234)")
|
|
check(Flags.getVar(nil, ctx, 0x800D) == 4,
|
|
"VAR_RESULT is the four known moves, got " .. tostring(Flags.getVar(nil, ctx, 0x800D)))
|
|
|
|
Flags.setVar(nil, ctx, 0x8004, 1)
|
|
Natives.special(ctx, 0xDF, { log = function() end })
|
|
check(Flags.getVar(nil, ctx, 0x800D) == 1,
|
|
"the one-move mon reports 1, which is the script's CantForgetOnlyMove branch, got "
|
|
.. tostring(Flags.getVar(nil, ctx, 0x800D)))
|
|
|
|
package.loaded["src.core.game3.runtime"] = prevRuntime
|
|
end
|
|
|
|
print("[test] 5. BufferMoveDeleterNicknameAndMove fills STR_VAR_1 and STR_VAR_2")
|
|
do
|
|
local prevRuntime = package.loaded["src.core.game3.runtime"]
|
|
local mon = fourMoveMon()
|
|
mon.nickname = "SPROUT"
|
|
package.loaded["src.core.game3.runtime"] = {
|
|
getSession = function() return { party = { mon } } end,
|
|
isActive = function() return true end,
|
|
}
|
|
|
|
local ctx = Ctx.new({})
|
|
ctx.mode = "bytecode"
|
|
ctx.status = "running"
|
|
Flags.setVar(nil, ctx, 0x8005, 2)
|
|
local buffered = {}
|
|
local handled = select(3, Natives.special(ctx, 0xDE, {
|
|
log = function() end,
|
|
setStringVar = function(idx, text) buffered[idx] = text end,
|
|
}))
|
|
check(handled == true, "special 0xDE is bound (pokefirered/data/specials.inc:233)")
|
|
check(buffered[1] == "SPROUT", "STR_VAR_1 is the nickname, got " .. tostring(buffered[1]))
|
|
check(buffered[2] == Pokemon.moveName(LEECH_SEED),
|
|
"STR_VAR_2 is the move in VAR_0x8005, got " .. tostring(buffered[2]))
|
|
check(ctx.stringVars[1] == "SPROUT" and ctx.stringVars[2] == buffered[2],
|
|
"and the script ctx carries the same pair")
|
|
|
|
package.loaded["src.core.game3.runtime"] = prevRuntime
|
|
end
|
|
|
|
print("[test] 6. SelectMoveDeleterMove drives the summary screen and MoveDeleterForgetMove")
|
|
do
|
|
local prevRuntime = package.loaded["src.core.game3.runtime"]
|
|
local mon = fourMoveMon()
|
|
package.loaded["src.core.game3.runtime"] = {
|
|
getSession = function() return { party = { mon } } end,
|
|
isActive = function() return true end,
|
|
}
|
|
|
|
local ctx = Ctx.new({})
|
|
ctx.mode = "bytecode"
|
|
ctx.status = "running"
|
|
local yielded = Natives.special(ctx, 0xDC, { log = function() end })
|
|
check(yielded == true, "the script waits while the summary screen is up")
|
|
check(SummaryMenu.isOpen(), "special 0xDC opened the summary screen")
|
|
|
|
local pressed = nil
|
|
local input = { wasPressed = function(_, key) return key == pressed end }
|
|
local function press(key)
|
|
pressed = key
|
|
SummaryMenu.handleInput(input)
|
|
pressed = nil
|
|
end
|
|
|
|
press("down")
|
|
press("a")
|
|
check(not SummaryMenu.isOpen(), "picking a move closed the screen")
|
|
check(ctx.nativePoll and ctx.nativePoll() == true, "and the script resumed")
|
|
check(Flags.getVar(nil, ctx, 0x8005) == 1,
|
|
"VAR_0x8005 is the chosen move slot, got " .. tostring(Flags.getVar(nil, ctx, 0x8005)))
|
|
|
|
Natives.special(ctx, 0xDD, { log = function() end })
|
|
check(Pokemon.moveIdAt(mon, 2) == LEECH_SEED,
|
|
"special 0xDD deleted GROWL and shifted LEECH SEED up, got "
|
|
.. tostring(Pokemon.moveName(Pokemon.moveIdAt(mon, 2))))
|
|
check(Pokemon.moveSlotCount(mon) == 3, "three moves are left, got "
|
|
.. tostring(Pokemon.moveSlotCount(mon)))
|
|
|
|
package.loaded["src.core.game3.runtime"] = prevRuntime
|
|
end
|
|
|
|
print("[test] 7. backing out of the summary screen returns MAX_MON_MOVES")
|
|
do
|
|
local prevRuntime = package.loaded["src.core.game3.runtime"]
|
|
local mon = fourMoveMon()
|
|
package.loaded["src.core.game3.runtime"] = {
|
|
getSession = function() return { party = { mon } } end,
|
|
isActive = function() return true end,
|
|
}
|
|
|
|
local ctx = Ctx.new({})
|
|
ctx.mode = "bytecode"
|
|
ctx.status = "running"
|
|
Natives.special(ctx, 0xDC, { log = function() end })
|
|
local pressed = nil
|
|
local input = { wasPressed = function(_, key) return key == pressed end }
|
|
pressed = "b"
|
|
SummaryMenu.handleInput(input)
|
|
pressed = nil
|
|
check(Flags.getVar(nil, ctx, 0x8005) == 4,
|
|
"VAR_0x8005 is MAX_MON_MOVES so the script loops back to the mon chooser, got "
|
|
.. tostring(Flags.getVar(nil, ctx, 0x8005)))
|
|
check(Pokemon.moveSlotCount(mon) == 4, "nothing was deleted")
|
|
|
|
package.loaded["src.core.game3.runtime"] = prevRuntime
|
|
end
|
|
|
|
print("[test] 8. the Move Deleter forgets an HM move")
|
|
do
|
|
local prevRuntime = package.loaded["src.core.game3.runtime"]
|
|
local mon = fourMoveMon()
|
|
mon.moves[1] = CUT
|
|
package.loaded["src.core.game3.runtime"] = {
|
|
getSession = function() return { party = { mon } } end,
|
|
isActive = function() return true end,
|
|
}
|
|
|
|
local ctx = Ctx.new({})
|
|
ctx.mode = "bytecode"
|
|
ctx.status = "running"
|
|
Natives.special(ctx, 0xDC, { log = function() end })
|
|
local pressed = nil
|
|
local input = { wasPressed = function(_, key) return key == pressed end }
|
|
pressed = "a"
|
|
SummaryMenu.handleInput(input)
|
|
pressed = nil
|
|
check(not SummaryMenu.isOpen(),
|
|
"PSS_MODE_FORGET_MOVE accepts an HM (pokefirered/src/pokemon_summary_screen.c:3772)")
|
|
check(SummaryMenu._hmNotice ~= true, "no HM refusal is printed")
|
|
check(Flags.getVar(nil, ctx, 0x8005) == 0,
|
|
"VAR_0x8005 is CUT's slot, got " .. tostring(Flags.getVar(nil, ctx, 0x8005)))
|
|
Natives.special(ctx, 0xDD, { log = function() end })
|
|
check(Pokemon.moveIdAt(mon, 1) == GROWL,
|
|
"special 0xDD deleted CUT and shifted GROWL up, got "
|
|
.. tostring(Pokemon.moveName(Pokemon.moveIdAt(mon, 1))))
|
|
check(Pokemon.moveSlotCount(mon) == 3, "three moves are left, got "
|
|
.. tostring(Pokemon.moveSlotCount(mon)))
|
|
|
|
package.loaded["src.core.game3.runtime"] = prevRuntime
|
|
end
|
|
|
|
print("[test] 9. a move-learn summary screen still refuses an HM")
|
|
do
|
|
local mon = fourMoveMon()
|
|
mon.moves[1] = CUT
|
|
local picked = "unset"
|
|
SummaryMenu.openMenu({ mon }, 1, {
|
|
mode = "select_move",
|
|
moveToLearn = TACKLE,
|
|
onSelectMove = function(slot) picked = slot end,
|
|
})
|
|
local pressed = nil
|
|
local input = { wasPressed = function(_, key) return key == pressed end }
|
|
pressed = "a"
|
|
SummaryMenu.handleInput(input)
|
|
pressed = nil
|
|
check(SummaryMenu.isOpen(), "PSS_MODE_SELECT_MOVE keeps the screen up on an HM")
|
|
check(SummaryMenu._hmNotice == true,
|
|
"the HM refusal is printed (pokefirered/src/pokemon_summary_screen.c:3772)")
|
|
check(picked == "unset", "no slot was reported")
|
|
SummaryMenu.close()
|
|
end
|
|
|
|
print("[test] 10. the deleter cursor skips empty move slots onto CANCEL")
|
|
do
|
|
local prevRuntime = package.loaded["src.core.game3.runtime"]
|
|
local mon = monAt(BULBASAUR, 5)
|
|
mon.moves = { TACKLE, GROWL }
|
|
mon.pp = { 35, 40 }
|
|
mon.maxPp = { 35, 40 }
|
|
package.loaded["src.core.game3.runtime"] = {
|
|
getSession = function() return { party = { mon } } end,
|
|
isActive = function() return true end,
|
|
}
|
|
|
|
local ctx = Ctx.new({})
|
|
ctx.mode = "bytecode"
|
|
ctx.status = "running"
|
|
Natives.special(ctx, 0xDC, { log = function() end })
|
|
local pressed = nil
|
|
local input = { wasPressed = function(_, key) return key == pressed end }
|
|
local function press(key)
|
|
pressed = key
|
|
SummaryMenu.handleInput(input)
|
|
pressed = nil
|
|
end
|
|
|
|
press("down")
|
|
check(SummaryMenu._moveCursor == 2, "down lands on GROWL, got " .. tostring(SummaryMenu._moveCursor))
|
|
press("down")
|
|
check(SummaryMenu._moveCursor == 5,
|
|
"down skips the two empty slots onto CANCEL (pokefirered/src/pokemon_summary_screen.c:3829), got "
|
|
.. tostring(SummaryMenu._moveCursor))
|
|
press("up")
|
|
check(SummaryMenu._moveCursor == 2,
|
|
"up from CANCEL skips back to GROWL (pokefirered/src/pokemon_summary_screen.c:3802), got "
|
|
.. tostring(SummaryMenu._moveCursor))
|
|
press("down")
|
|
press("down")
|
|
check(SummaryMenu._moveCursor == 1, "down from CANCEL wraps to slot 1, got "
|
|
.. tostring(SummaryMenu._moveCursor))
|
|
press("up")
|
|
check(SummaryMenu._moveCursor == 5, "up from slot 1 wraps to CANCEL, got "
|
|
.. tostring(SummaryMenu._moveCursor))
|
|
press("a")
|
|
check(not SummaryMenu.isOpen(), "A on CANCEL closes the screen")
|
|
check(Flags.getVar(nil, ctx, 0x8005) == 4,
|
|
"VAR_0x8005 is 4 on CANCEL, got " .. tostring(Flags.getVar(nil, ctx, 0x8005)))
|
|
check(Pokemon.moveSlotCount(mon) == 2, "nothing was deleted")
|
|
|
|
package.loaded["src.core.game3.runtime"] = prevRuntime
|
|
end
|
|
|
|
finish()
|