mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
287 lines
11 KiB
Lua
287 lines
11 KiB
Lua
#!/usr/bin/env luajit
|
|
-- pokefirered/src/item_use.c:286 FieldUseFunc_Rod
|
|
-- pokefirered/src/item_use.c:359 FieldUseFunc_PokeFlute
|
|
-- pokefirered/src/item_use.c:582 FieldUseFunc_BlackWhiteFlute
|
|
-- pokefirered/src/field_player_avatar.c:1679 StartFishing
|
|
|
|
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("[test] FAILED " .. failed)
|
|
os.exit(1)
|
|
end
|
|
print("[test] all passed")
|
|
os.exit(0)
|
|
end
|
|
|
|
local GameVersion = require("src.core.GameVersion")
|
|
GameVersion.set("firered")
|
|
|
|
local Cache = require("tests.game3_cache")
|
|
local cacheRoot = Cache.mount("scripts/events.lua", { native = true })
|
|
if not cacheRoot then
|
|
print("[skip] game3_field_items_test: " .. tostring(Cache.reason))
|
|
os.exit(0)
|
|
end
|
|
print("[info] FireRed cache at " .. cacheRoot)
|
|
|
|
local Dataset = require("src.core.game3.dataset")
|
|
local Collision = require("src.core.game3.collision")
|
|
local Objects = require("src.core.game3.objects")
|
|
local Player = require("src.core.game3.player")
|
|
local Field = require("src.core.game3.field")
|
|
local Space = require("src.core.game3.scripting.space")
|
|
local Flags = require("src.core.game3.scripting.flags")
|
|
local ItemUse = require("src.core.game3.item_use")
|
|
local ItemsData = require("src.core.game3.items_data")
|
|
local Encounters = require("src.core.game3.encounters")
|
|
local Message = require("src.ui.game3.message")
|
|
local Rng = require("src.core.game3.rng")
|
|
local Runtime = require("src.core.game3.runtime")
|
|
local Bag = require("src.core.game3.bag")
|
|
local BagMenu = require("src.ui.game3.bag_menu")
|
|
local Game3 = require("src.core.Game3")
|
|
|
|
local selectInput = {
|
|
wasPressed = function(_, key) return key == "select" end,
|
|
isDown = function() return false end,
|
|
}
|
|
local bagInput = {
|
|
_p = {},
|
|
wasPressed = function(self, key) return self._p[key] == true end,
|
|
isDown = function() return false end,
|
|
press = function(self, key) self._p = { [key] = true } end,
|
|
}
|
|
|
|
local ITEM_OLD_ROD, ITEM_GOOD_ROD, ITEM_SUPER_ROD = 262, 263, 264
|
|
local ITEM_BLACK_FLUTE, ITEM_WHITE_FLUTE, ITEM_POKE_FLUTE = 42, 43, 350
|
|
local FLAG_SYS_WHITE_FLUTE_ACTIVE = 0x803
|
|
local FLAG_SYS_BLACK_FLUTE_ACTIVE = 0x804
|
|
|
|
local game = { data = {} }
|
|
Dataset.hydrate(game)
|
|
local session = { map = nil, flags = {}, vars = {}, party = {}, name = "RED", bag = Bag.new() }
|
|
game.session = session
|
|
Runtime.session = session
|
|
Bag.add(session.bag, ITEM_OLD_ROD, 1)
|
|
Bag.add(session.bag, ITEM_POKE_FLUTE, 1)
|
|
|
|
local function enterMap(mapId)
|
|
local def = game.data.maps[mapId]
|
|
if not def then return nil end
|
|
Field._game = game
|
|
Field._session = session
|
|
session.map = mapId
|
|
Field.running = true
|
|
Field.locked = false
|
|
Field.clearMetatiles()
|
|
Space.activate(nil, mapId, game, nil)
|
|
Collision.bindMap(game, mapId, def)
|
|
Objects.loadMap(game, mapId, def)
|
|
return def
|
|
end
|
|
|
|
local function standAt(x, y, facing)
|
|
Player.moving = false
|
|
Player.progress = 0
|
|
Player.surfing = false
|
|
Player.cellX, Player.cellY = x, y
|
|
Player.targetX, Player.targetY = x, y
|
|
Player.px, Player.py = x * 16, y * 16
|
|
Player.facing = facing or "down"
|
|
end
|
|
|
|
print("[test] 1. the rods and flutes are the items pret says they are")
|
|
check(ItemsData.info(ITEM_OLD_ROD).name == "OLD ROD", "262 is the OLD ROD")
|
|
check(tonumber(ItemsData.info(ITEM_OLD_ROD).secondaryId) == 0, "its secondaryId is rod 0")
|
|
check(tonumber(ItemsData.info(ITEM_GOOD_ROD).secondaryId) == 1, "GOOD ROD is rod 1")
|
|
check(tonumber(ItemsData.info(ITEM_SUPER_ROD).secondaryId) == 2, "SUPER ROD is rod 2")
|
|
check(ItemsData.fieldUseKind(ITEM_OLD_ROD) == "key",
|
|
"the pack files the rods under fieldUse key, which the old reject swallowed")
|
|
check(ItemsData.fieldUseKind(ITEM_WHITE_FLUTE) == "black_white_flute",
|
|
"and the WHITE FLUTE under FieldUseFunc_BlackWhiteFlute")
|
|
|
|
print("[test] 2. Pallet Town beach: CanFish")
|
|
local def = enterMap("FR_PALLET_TOWN")
|
|
check(def ~= nil, "FR_PALLET_TOWN is in the cache")
|
|
if not def then finish() end
|
|
standAt(7, 16, "up")
|
|
check(ItemUse.canFish() == false, "facing inland, the rod is refused")
|
|
local ok, kind, text = ItemUse.useField(session, session.bag, ITEM_OLD_ROD, nil)
|
|
check(ok == false and kind == "rod", "useField refuses it")
|
|
check(type(text) == "string" and text:find("time to use that", 1, true) ~= nil,
|
|
"with gText_OakForbidsUseOfItemHere")
|
|
check(Field.isFishing() == false, "no fishing task started")
|
|
check(Message.isOpen() == false, "and useField itself printed nothing")
|
|
-- pokefirered/src/item_menu.c:2022 UseRegisteredKeyItemOnField
|
|
session.registeredItem = ITEM_OLD_ROD
|
|
Game3._handleRegisteredItem({ input = selectInput, session = session })
|
|
-- pokefirered/src/item_use.c:294 PrintNotTheTimeToUseThat
|
|
check(Message.isOpen() == true, "the refusal prints on the field instead of vanishing")
|
|
local guardA = 0
|
|
while Message.isOpen() and guardA < 40 do
|
|
Message.advance()
|
|
guardA = guardA + 1
|
|
end
|
|
check(Message.isOpen() == false, "one A press per page closes it")
|
|
|
|
standAt(7, 16, "down")
|
|
check(ItemUse.canFish() == true, "facing the water, the rod is allowed")
|
|
check(Encounters.hasFishingMons("FR_PALLET_TOWN") == true, "Pallet Town has fishing mons")
|
|
|
|
print("[test] 3. the dot game runs and misses")
|
|
local function seedFor(wantBite)
|
|
for s = 1, 2000 do
|
|
Rng.SeedRng(s)
|
|
Rng.Random()
|
|
if (((Rng.Random() % 2) == 0) == wantBite) then return s end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local missSeed = seedFor(false)
|
|
check(missSeed ~= nil, "found a seed whose second roll misses")
|
|
Rng.SeedRng(missSeed)
|
|
ok, kind = ItemUse.useField(session, session.bag, ITEM_OLD_ROD, nil)
|
|
check(ok == true and kind == "rod", "useField starts the rod")
|
|
check(Field.isFishing() == true, "a fishing task is running")
|
|
check(Player.fishing == true, "the player is holding the rod")
|
|
check(Field.locked == true, "field controls are locked")
|
|
check(Message.isOpen() == false, "no text box in the first second")
|
|
|
|
for _ = 1, 59 do Field.updateFishing() end
|
|
check(Message.isOpen() == false, "still none at 59 frames")
|
|
Field.updateFishing()
|
|
check(Message.isOpen() == true, "the box opens after 60 frames")
|
|
|
|
local dots = 0
|
|
local guard = 0
|
|
while Field.isFishing() and guard < 600 do
|
|
guard = guard + 1
|
|
Field.updateFishing()
|
|
local page = Message.currentPage()
|
|
local n = select(2, page:gsub("·", ""))
|
|
if n > dots then dots = n end
|
|
if page:find("nibble", 1, true) or page:find("hook", 1, true) then break end
|
|
end
|
|
check(dots >= 4 and dots <= 10, "between 4 and 10 dots were printed (" .. dots .. ")")
|
|
check(Message.currentPage():find("Not even a nibble", 1, true) ~= nil,
|
|
"the miss prints Not even a nibble (" .. tostring(Message.currentPage()) .. ")")
|
|
check(Field.isFishing() == true, "the task waits for the player to close the box")
|
|
for _ = 1, 400 do
|
|
if Message.isWaiting() then break end
|
|
Message.tick()
|
|
end
|
|
Message.advance()
|
|
check(Field.isFishing() == false, "closing it ends the task")
|
|
check(Player.fishing == false, "the rod is put away")
|
|
check(Field.locked == false, "field controls are free again")
|
|
|
|
print("[test] 4. a bite reaches the hook message")
|
|
local biteSeed = seedFor(true)
|
|
check(biteSeed ~= nil, "found a seed whose second roll bites")
|
|
Rng.SeedRng(biteSeed)
|
|
ItemUse.useField(session, session.bag, ITEM_SUPER_ROD, nil)
|
|
check(Field.isFishing() == true, "the SUPER ROD starts a task too")
|
|
guard = 0
|
|
while Field.isFishing() and guard < 600 do
|
|
guard = guard + 1
|
|
Field.updateFishing()
|
|
if Message.currentPage():find("hook", 1, true) then break end
|
|
end
|
|
check(Message.currentPage():find("A POKéMON's on the hook!", 1, true) ~= nil,
|
|
"the bite prints gText_PokemonOnHook (" .. tostring(Message.currentPage()) .. ")")
|
|
Message.close()
|
|
Field._fishing = nil
|
|
Player.fishing = false
|
|
Field.locked = false
|
|
|
|
print("[test] 5. the POKé FLUTE wakes sleeping party mons")
|
|
session.party = {
|
|
{ species = 1, level = 10, hp = 20, maxHp = 20, status = "SLP", sleep = 3 },
|
|
{ species = 4, level = 10, hp = 20, maxHp = 20 },
|
|
}
|
|
ok, kind, text = ItemUse.useField(session, session.bag, ITEM_POKE_FLUTE, nil)
|
|
check(ok == true and kind == "flute", "the flute plays")
|
|
check(session.party[1].status == nil and (tonumber(session.party[1].sleep) or 0) == 0,
|
|
"the sleeping mon woke up")
|
|
check(type(text) == "string" and text:find("awakened", 1, true) ~= nil,
|
|
"it prints gText_PokeFluteAwakenedMon")
|
|
check(Message.isOpen() == false, "useField itself printed nothing")
|
|
|
|
session.party[1].status = "SLP"
|
|
session.party[1].sleep = 3
|
|
-- pokefirered/src/data/items.h:5318 ITEM_POKE_FLUTE registrability 0
|
|
BagMenu.show(session, { session = session, bag = session.bag, pocket = "KEY_ITEMS" })
|
|
BagMenu.settle()
|
|
for i, r in ipairs(BagMenu.list()) do
|
|
if ItemsData.toNumericId(r.id) == ITEM_POKE_FLUTE then BagMenu.cursor = i end
|
|
end
|
|
bagInput:press("a")
|
|
BagMenu.handleInput(bagInput)
|
|
check(BagMenu.mode == "action", "the flute opens the item's action menu")
|
|
for k, a in ipairs(BagMenu.ACTIONS) do
|
|
if a == "USE" then BagMenu.actionCursor = k end
|
|
end
|
|
bagInput:press("a")
|
|
BagMenu.handleInput(bagInput)
|
|
BagMenu.settle()
|
|
check(session.party[1].status == nil and (tonumber(session.party[1].sleep) or 0) == 0,
|
|
"the bag USE woke the mon too")
|
|
-- pokefirered/src/item_use.c:374 DisplayItemMessageInBag
|
|
check(BagMenu.isOpen() == true and BagMenu.mode == "message",
|
|
"and the box is on screen, not dropped on the floor")
|
|
check(Message.isOpen() == false, "nothing escaped to the field message box")
|
|
local pages = {}
|
|
local guardF = 0
|
|
while BagMenu.mode == "message" and guardF < 40 do
|
|
pages[#pages + 1] = tostring(BagMenu.messageText)
|
|
bagInput:press("a")
|
|
BagMenu.handleInput(bagInput)
|
|
guardF = guardF + 1
|
|
end
|
|
check(table.concat(pages, "\n"):find("awakened", 1, true) ~= nil,
|
|
"gText_PokeFluteAwakenedMon is one of the bag's pages")
|
|
check(BagMenu.mode == "list", "paging through it returns to the item list")
|
|
BagMenu.close()
|
|
ok, kind, text = ItemUse.useField(session, session.bag, ITEM_POKE_FLUTE, nil)
|
|
check(ok == true and text:find("catchy tune", 1, true) ~= nil,
|
|
"with nobody asleep it prints gText_PlayedPokeFluteCatchy")
|
|
|
|
print("[test] 6. the WHITE and BLACK FLUTES move the encounter rate")
|
|
local base = Encounters.encounterRate(20, { ignoreAbility = true })
|
|
ok, kind, text = ItemUse.useField(session, session.bag, ITEM_WHITE_FLUTE, nil)
|
|
check(ok == true and kind == "black_white_flute", "the WHITE FLUTE is usable")
|
|
check(text:find("lured", 1, true) ~= nil, "it prints gText_UsedVar2WildLured")
|
|
check(Flags.getFlag(Space.store, nil, FLAG_SYS_WHITE_FLUTE_ACTIVE) == true,
|
|
"FLAG_SYS_WHITE_FLUTE_ACTIVE is set")
|
|
check(Flags.getFlag(Space.store, nil, FLAG_SYS_BLACK_FLUTE_ACTIVE) == false,
|
|
"FLAG_SYS_BLACK_FLUTE_ACTIVE is clear")
|
|
local lured = Encounters.encounterRate(20, { ignoreAbility = true })
|
|
check(lured == base + math.floor(base / 2),
|
|
"the rate rises by half (" .. base .. " -> " .. lured .. ")")
|
|
|
|
ok, kind, text = ItemUse.useField(session, session.bag, ITEM_BLACK_FLUTE, nil)
|
|
check(ok == true and text:find("repelled", 1, true) ~= nil,
|
|
"the BLACK FLUTE prints gText_UsedVar2WildRepelled")
|
|
check(Flags.getFlag(Space.store, nil, FLAG_SYS_BLACK_FLUTE_ACTIVE) == true,
|
|
"FLAG_SYS_BLACK_FLUTE_ACTIVE is set")
|
|
check(Flags.getFlag(Space.store, nil, FLAG_SYS_WHITE_FLUTE_ACTIVE) == false,
|
|
"and the white one was cleared, pret FlagClear")
|
|
local repelled = Encounters.encounterRate(20, { ignoreAbility = true })
|
|
check(repelled == math.floor(base / 2),
|
|
"the rate halves (" .. base .. " -> " .. repelled .. ")")
|
|
|
|
finish()
|