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
298 lines
12 KiB
Lua
298 lines
12 KiB
Lua
#!/usr/bin/env luajit
|
|
-- pokefirered/src/battle_controller_pokedude.c:2675 InitPokedudePartyAndOpponent
|
|
|
|
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 eq(a, b, msg)
|
|
check(a == b, string.format("%s (%s == %s)", msg, tostring(a), tostring(b)))
|
|
end
|
|
|
|
local Cache = require("tests.game3_cache")
|
|
local cacheRoot = Cache.mount("items/pack.lua")
|
|
if not cacheRoot then
|
|
print("[skip] game3_teachy_pokedude: " .. tostring(Cache.reason))
|
|
os.exit(0)
|
|
end
|
|
|
|
local Pokedude = require("src.core.game3.battle.pokedude")
|
|
local Battle = require("src.core.game3.battle.init")
|
|
local Ui = require("src.core.game3.battle.ui")
|
|
local Rules = require("src.core.game3.battle.rules")
|
|
local Pokemon = require("src.core.game3.pokemon")
|
|
local Bag = require("src.core.game3.bag")
|
|
local TeachyTv = require("src.core.game3.teachy_tv")
|
|
local Rng = require("src.core.game3.rng")
|
|
local Runtime = require("src.core.game3.runtime")
|
|
|
|
local S = TeachyTv.SCRIPT
|
|
|
|
print("[test] 1. pokefirered/src/battle_controller_pokedude.c:2326 the parties")
|
|
do
|
|
local party, foes = Pokedude.build(S.BATTLE)
|
|
eq(#party, 1, "one player mon for the battle lesson")
|
|
eq(party[1].species, 19, "RATTATA")
|
|
eq(party[1].level, 15, "at level 15")
|
|
eq(Pokemon.natureId(party[1].personality), 1, "NATURE_LONELY")
|
|
eq(party[1].gender, "M", "male")
|
|
eq(party[1].moves[3], 158, "HYPER FANG in slot 3")
|
|
eq(party[1].ivs.atk, 0, "CreateMon fixedIV 0")
|
|
eq(foes[1].species, 16, "vs PIDGEY")
|
|
eq(foes[1].level, 18, "at level 18")
|
|
eq(Pokemon.natureId(foes[1].personality), 4, "NATURE_NAUGHTY")
|
|
local mp = Pokedude.build(S.MATCHUPS)
|
|
eq(#mp, 2, "POLIWAG and BUTTERFREE for the matchups lesson")
|
|
eq(mp[2].species, 12, "BUTTERFREE second")
|
|
local _, cf = Pokedude.build(S.CATCHING)
|
|
eq(cf[1].species, 39, "the catching lesson faces JIGGLYPUFF")
|
|
eq(#cf[1].moves, 3, "with three moves")
|
|
end
|
|
|
|
print("[test] 2. pokefirered/src/battle_script_commands.c:1201 no critical hits")
|
|
do
|
|
local st = { pokedude = true }
|
|
local crits = 0
|
|
for i = 1, 400 do
|
|
if Rules.crit.roll({ species = 19 }, 158, true, function() return 0 end, st) then crits = crits + 1 end
|
|
end
|
|
eq(crits, 0, "a forced crit roll never crits under BATTLE_TYPE_POKEDUDE")
|
|
check(Rules.crit.roll({ species = 19 }, 158, true, function() return 0 end, {}), "the same roll crits outside it")
|
|
end
|
|
|
|
local function run_lesson(script, seed)
|
|
Rng.SeedRng(seed)
|
|
local session = { name = "RED", bag = Bag.new(), party = {}, modData = {}, dex = { seen = {}, owned = {}, caught = {} } }
|
|
Bag.add(session.bag, 13, 3)
|
|
local party, foes = Pokedude.build(script)
|
|
if Battle.isActive() then Battle.abort("run") end
|
|
Runtime.getSession = function() return session end
|
|
TeachyTv.initPokedudeBag(session, script)
|
|
local result
|
|
Battle.start({
|
|
wild = true,
|
|
pokedude = true,
|
|
pdScriptNum = script,
|
|
headless = true,
|
|
autoFight = false,
|
|
playerParty = party,
|
|
foe = foes[1],
|
|
session = session,
|
|
onDone = function(r) result = r end,
|
|
})
|
|
local st = Battle.getState()
|
|
Battle.runToEnd()
|
|
TeachyTv.restorePlayerBag(session)
|
|
return st, result, session
|
|
end
|
|
|
|
print("[test] 3. pokefirered/src/battle_controller_pokedude.c:2146 the voiceover order per lesson")
|
|
do
|
|
local named = {
|
|
[S.BATTLE] = { "Pokedude_Text_SpeedierBattlerGoesFirst", "Pokedude_Text_MyRattataFasterThanPidgey",
|
|
"Pokedude_Text_BattlersTakeTurnsAttacking", "Pokedude_Text_MyRattataWonGetsEXP" },
|
|
[S.STATUS] = { "Pokedude_Text_UhOhRattataPoisoned", "Pokedude_Text_UhOhRattataPoisoned",
|
|
"Pokedude_Text_HealStatusRightAway", "Pokedude_Text_UsingItemTakesTurn", "Pokedude_Text_YayWeManagedToWin" },
|
|
[S.MATCHUPS] = { "Pokedude_Text_WaterNotVeryEffectiveAgainstGrass", "Pokedude_Text_GrassEffectiveAgainstWater",
|
|
"Pokedude_Text_LetsTryShiftingMons", "Pokedude_Text_ShiftingUsesTurn",
|
|
"Pokedude_Text_ButterfreeDoubleResistsGrass", "Pokedude_Text_ButterfreeGoodAgainstOddish",
|
|
"Pokedude_Text_YeahWeWon" },
|
|
[S.CATCHING] = { "Pokedude_Text_WeakenMonBeforeCatching", "Pokedude_Text_WeakenMonBeforeCatching",
|
|
"Pokedude_Text_BestIfTargetStatused", "Pokedude_Text_CantDoubleUpOnStatus", "Pokedude_Text_LetMeThrowBall",
|
|
"Pokedude_Text_PickBestKindOfBall" },
|
|
}
|
|
local BattleText = require("src.core.game3.battle.battle_text")
|
|
for script, list in pairs(named) do
|
|
for i, label in ipairs(list) do
|
|
local key = Pokedude.textKey(script, i - 1)
|
|
check(BattleText.get(key, { playerName = "RED" }) == BattleText.get(label, { playerName = "RED" }),
|
|
key .. " reads " .. label .. " from the cache table")
|
|
end
|
|
end
|
|
local spoken = {
|
|
[S.BATTLE] = { 0, 1, 2, 3 },
|
|
[S.STATUS] = { 1, 2, 3, 4 },
|
|
[S.MATCHUPS] = { 0, 1, 2, 3, 4, 5, 6 },
|
|
[S.CATCHING] = { 0, 2, 3, 4, 5 },
|
|
}
|
|
local want = {}
|
|
for script, idx in pairs(spoken) do
|
|
want[script] = {}
|
|
for i, n in ipairs(idx) do want[script][i] = Pokedude.textKey(script, n) end
|
|
end
|
|
local names = { [S.BATTLE] = "battle", [S.STATUS] = "status", [S.MATCHUPS] = "matchups", [S.CATCHING] = "catching" }
|
|
for script = S.BATTLE, S.CATCHING do
|
|
for seed = 1, 6 do
|
|
local st, result, session = run_lesson(script, seed * 7919)
|
|
local got = table.concat(st.pd.log, ",")
|
|
local exp = table.concat(want[script], ",")
|
|
if got ~= exp then
|
|
print("[info] " .. names[script] .. " seed " .. seed .. " log: " .. got)
|
|
print("[info] battle log: " .. table.concat(Ui.log() or {}, " | "))
|
|
end
|
|
eq(got, exp, names[script] .. " lesson voiceovers in cart order, seed " .. seed)
|
|
eq(result, script == S.CATCHING and "catch" or "win", names[script] .. " ends in " .. tostring(result))
|
|
eq(#session.party, 0, "nothing joins the player's party")
|
|
eq(Bag.get(session.bag, 13), 3, "the player's POTIONS are back")
|
|
check(not session.dex.caught[39], "JIGGLYPUFF is not registered as caught")
|
|
check(not session.dex.seen[16] and not session.dex.seen[43], "no foe is marked seen")
|
|
end
|
|
end
|
|
end
|
|
|
|
print("[test] 4. pokefirered/src/battle_script_commands.c:1015 no move misses")
|
|
do
|
|
local misses = 0
|
|
for seed = 1, 12 do
|
|
run_lesson(S.BATTLE, seed * 104729)
|
|
for _, t in ipairs(Ui.log() or {}) do
|
|
if t:find("missed") then misses = misses + 1 end
|
|
end
|
|
end
|
|
eq(misses, 0, "SAND ATTACK and friends never miss across 12 battles")
|
|
end
|
|
|
|
local NO = { wasPressed = function() return false end, isDown = function() return false end }
|
|
local function press(key)
|
|
return { wasPressed = function(_, k) return k == key end, isDown = function() return false end }
|
|
end
|
|
|
|
print("[test] 5. pokefirered/src/item_menu.c:2262/2316 the timer-driven POKé DUDE bag")
|
|
do
|
|
local BagMenu = require("src.ui.game3.bag_menu")
|
|
local ItemsData = require("src.core.game3.items_data")
|
|
local session = { name = "RED", bag = Bag.new(), party = {}, modData = {} }
|
|
Bag.add(session.bag, 13, 3)
|
|
Bag.add(session.bag, 17, 2)
|
|
BagMenu.show(session.bag, { session = session })
|
|
BagMenu.cursor = 2
|
|
BagMenu.close()
|
|
TeachyTv.initPokedudeBag(session, S.STATUS)
|
|
|
|
local picked, cancelled, modeAtA, rowAtA
|
|
BagMenu.showPokedude(session.bag, { session = session, plan = "status",
|
|
onItem = function(id) picked = id end, onCancel = function() cancelled = true end })
|
|
for f = 1, 400 do
|
|
BagMenu.handleInput(NO)
|
|
if f == 250 then
|
|
modeAtA = BagMenu.mode
|
|
local row = BagMenu.list()[BagMenu.cursor]
|
|
rowAtA = row and ItemsData.toNumericId(row.id)
|
|
end
|
|
if picked or cancelled then break end
|
|
end
|
|
eq(modeAtA, "action", "A at 204 opened the USE/CANCEL context menu")
|
|
eq(rowAtA, 14, "on the ANTIDOTE one row down")
|
|
eq(picked, 14, "the STATUS bag closes with ANTIDOTE")
|
|
check(not BagMenu.isOpen() and not BagMenu.isPokedude(), "and the bag is closed")
|
|
|
|
picked = nil
|
|
local pocketMid, cursorMid
|
|
BagMenu.showPokedude(session.bag, { session = session, plan = "catching",
|
|
onItem = function(id) picked = id end })
|
|
for f = 1, 1000 do
|
|
BagMenu.handleInput(NO)
|
|
if f == 460 then pocketMid, cursorMid = BagMenu.currentPocket(), BagMenu.cursor end
|
|
if picked then break end
|
|
end
|
|
eq(pocketMid, "POKE_BALLS", "two pocket switches right land on the POKé BALLS pocket")
|
|
eq(cursorMid, 3, "two DPAD_DOWNs reach the NEST BALL")
|
|
eq(picked, 4, "the CATCHING bag closes with POKé BALL")
|
|
|
|
picked, cancelled = nil, nil
|
|
BagMenu.showPokedude(session.bag, { session = session, plan = "catching",
|
|
onItem = function(id) picked = id end, onCancel = function() cancelled = true end })
|
|
for f = 1, 200 do
|
|
BagMenu.handleInput(f == 60 and press("b") or NO)
|
|
if picked or cancelled then break end
|
|
end
|
|
check(cancelled and not picked, "B interrupts the scripted bag (item_menu.c:2192)")
|
|
check(not BagMenu.isOpen(), "and closes it")
|
|
|
|
TeachyTv.restorePlayerBag(session)
|
|
BagMenu.show(session.bag, { session = session, pocket = "ITEMS" })
|
|
eq(BagMenu.cursor, 2, "the player's bag cursor is restored (item_menu.c:2089)")
|
|
BagMenu.close()
|
|
|
|
Bag.add(session.bag, 366, 1)
|
|
BagMenu.show(session.bag, { session = session, pocket = "KEY_ITEMS" })
|
|
BagMenu.open = false
|
|
TeachyTv.initPokedudeBag(session, S.CATCHING)
|
|
picked = nil
|
|
BagMenu.showPokedude(session.bag, { session = session, plan = "catching",
|
|
onItem = function(id) picked = id end })
|
|
for _ = 1, 1000 do
|
|
BagMenu.handleInput(NO)
|
|
if picked then break end
|
|
end
|
|
TeachyTv.restorePlayerBag(session)
|
|
eq(picked, 4, "the catching bag ran over a hidden player bag")
|
|
eq(BagMenu.currentPocket(), "KEY_ITEMS",
|
|
"the hidden player bag view is back on KEY ITEMS, not the POKé DUDE's pocket (item_menu.c:2089)")
|
|
eq(BagMenu._bag, session.bag, "and it points at the player's bag again")
|
|
local row = BagMenu.list()[BagMenu.cursor]
|
|
eq(row and ItemsData.toNumericId(row.id), 366, "with the cursor on TEACHY TV")
|
|
end
|
|
|
|
print("[test] 6. pokefirered/src/party_menu.c:2020/2072 the timer-driven POKé DUDE party menu")
|
|
do
|
|
local PartyMenu = require("src.ui.game3.party_menu")
|
|
local session = { name = "RED", bag = Bag.new(), party = {}, modData = {} }
|
|
local party = Pokedude.build(S.MATCHUPS)
|
|
local chosen, cancelled, actionsAt
|
|
PartyMenu.showPokedude(party, { plan = "switch", session = session, activeSlot = 1,
|
|
onSelect = function(slot) chosen = slot end, onCancel = function() cancelled = true end })
|
|
for f = 1, 300 do
|
|
PartyMenu.handleInput(NO)
|
|
if f == 200 then actionsAt = PartyMenu.mode == "action" and PartyMenu.ACTIONS[1] end
|
|
if chosen or cancelled then break end
|
|
end
|
|
eq(actionsAt, "SHIFT", "A at 160 opened the selection window on SHIFT")
|
|
eq(chosen, 2, "the switch plan shifts to BUTTERFREE at 240")
|
|
check(not PartyMenu.isOpen(), "and closes the party menu")
|
|
|
|
local used, selected
|
|
local status = Pokedude.build(S.STATUS)
|
|
PartyMenu.showPokedude(status, { plan = "item", session = session, item = 14, activeSlot = 1,
|
|
onUse = function(slot) used = slot return "RATTATA was cured of its\npoisoning." end,
|
|
onSelect = function(slot) selected = slot end })
|
|
for _ = 1, 80 do PartyMenu.handleInput(NO) end
|
|
check(used == nil, "nothing is used while data[0] counts to 80")
|
|
PartyMenu.handleInput(NO)
|
|
eq(used, 1, "ItemUseCB_MedicineStep runs on RATTATA at frame 80")
|
|
eq(PartyMenu.mode, "message", "the cure text is in the party menu")
|
|
for _ = 1, 30 do PartyMenu.handleInput(NO) end
|
|
check(selected == nil and PartyMenu.isOpen(), "the text waits for a button (party_menu.c:453 autoScroll off)")
|
|
PartyMenu.handleInput(press("a"))
|
|
eq(selected, 1, "A closes the menu back to the battle")
|
|
|
|
cancelled = nil
|
|
PartyMenu.showPokedude(party, { plan = "switch", session = session, activeSlot = 1,
|
|
onSelect = function() end, onCancel = function() cancelled = true end })
|
|
for _ = 1, 10 do PartyMenu.handleInput(NO) end
|
|
PartyMenu.handleInput(press("b"))
|
|
check(cancelled and not PartyMenu.isOpen(), "B cancels the scripted party menu (party_menu.c:2052)")
|
|
end
|
|
|
|
print("[test] 7. pokefirered/src/pokeball.c:389 the POKé DUDE send-out ball starts at (32, 64)")
|
|
do
|
|
local x, y = Pokedude.sendOutOrigin({ pokedude = true })
|
|
eq(x * 1000 + y, 32064, "POKé DUDE throw origin")
|
|
x, y = Pokedude.sendOutOrigin({})
|
|
eq(x * 1000 + y, 48070, "the player's throw origin is unchanged")
|
|
end
|
|
|
|
if failed > 0 then
|
|
print(string.format("FAILED %d check(s)", failed))
|
|
os.exit(1)
|
|
end
|
|
print("All teachy pokedude checks passed.")
|