mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 05:32:07 -04:00
300 lines
11 KiB
Lua
300 lines
11 KiB
Lua
#!/usr/bin/env luajit
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
require("tests.fixture_data.game3_items").install()
|
|
|
|
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 romBundle = require("tests.game3_cache").bundle()
|
|
if not romBundle then
|
|
package.loaded["src.core.game3.rom_text"] = {
|
|
plain = function(key) return key end, box = function(key) return key end,
|
|
ascii = function(key) return key end, has = function() return true end,
|
|
ir = function(key) return { { t = "text", s = key } } end,
|
|
key = function(n, i, j) return j and (n .. "[" .. i .. "][" .. j .. "]") or (n .. "[" .. i .. "]") end,
|
|
at = function(n, i, j) return j and (n .. "[" .. i .. "][" .. j .. "]") or (n .. "[" .. i .. "]") end,
|
|
count = function() return 0 end, list = function() return {} end,
|
|
lazy = function(map) return setmetatable({}, { __index = function(_, k) return map[k] end }) end,
|
|
}
|
|
end
|
|
|
|
local fanfares = {}
|
|
package.loaded["src.core.game3.audio"] = setmetatable({
|
|
playFanfare = function(id) fanfares[#fanfares + 1] = id return true end,
|
|
waitFanfare = function(cb) if cb then cb() end end,
|
|
}, { __index = function() return function() end end })
|
|
|
|
local Schema = require("src.core.game3.save_schema_firered")
|
|
|
|
local session = Schema.newGame({ rngSeed = 0x1234 })
|
|
session.store = { flags = {}, vars = {} }
|
|
package.loaded["src.core.game3.runtime"] = {
|
|
getSession = function() return session end,
|
|
isActive = function() return true end,
|
|
}
|
|
|
|
local MysteryGift = require("src.core.game3.mystery_gift")
|
|
local Natives = require("src.core.game3.scripting.natives")
|
|
local Gift = require("src.core.game3.scripting.natives_gift")
|
|
local Bag = require("src.core.game3.bag")
|
|
|
|
-- pokefirered/data/specials.inc:395 ValidateSavedWonderCard
|
|
local SPECIAL_VALIDATE = Gift.SPECIAL_IDS.ValidateSavedWonderCard
|
|
-- pokefirered/include/constants/flags.h:704
|
|
local FLAG_RECEIVED_MYSTIC_TICKET = 0x2A8
|
|
-- pokefirered/include/constants/flags.h:1408
|
|
local FLAG_ENABLE_SHIP_NAVEL_ROCK = 0x84A
|
|
|
|
local function builtin(key)
|
|
for _, entry in ipairs(MysteryGift.builtins()) do
|
|
if entry.key == key then return entry end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function newCtx()
|
|
return { specialVars = {} }
|
|
end
|
|
|
|
local quiet = { log = function() end }
|
|
|
|
print("[test] 1. The deliveryman shows only for a valid saved card")
|
|
do
|
|
MysteryGift.clear(session)
|
|
local yield, value, known = Natives.special(newCtx(), SPECIAL_VALIDATE, quiet)
|
|
check(known, "ValidateSavedWonderCard is bound")
|
|
check(not yield, "it does not yield")
|
|
eq(value, 0, "no saved card hides the deliveryman")
|
|
|
|
local mystic = builtin("mystic_ticket")
|
|
check(mystic ~= nil, "the built-in set carries the MYSTIC TICKET card")
|
|
check(MysteryGift.receiveCard(session, mystic.card), "the card saves")
|
|
local _, value2 = Natives.special(newCtx(), SPECIAL_VALIDATE, quiet)
|
|
eq(value2, 1, "a saved card clears FLAG_HIDE_MG_DELIVERYMEN")
|
|
end
|
|
|
|
print("[test] 2. The once-only flag per card")
|
|
do
|
|
session.bag = Bag.new()
|
|
MysteryGift.clear(session)
|
|
session.store.flags = {}
|
|
local mystic = builtin("mystic_ticket")
|
|
MysteryGift.receiveCard(session, mystic.card)
|
|
check(MysteryGift.isGiftNotReceived(session), "the gift starts uncollected")
|
|
|
|
local first = MysteryGift.deliverGift(session)
|
|
eq(first, MysteryGift.DELIVER_GIVEN, "the first visit hands the ticket over")
|
|
eq(Bag.get(session.bag, MysteryGift.ITEM_MYSTIC_TICKET), 1, "one ticket in the bag")
|
|
check(MysteryGift.getFlag(session, FLAG_ENABLE_SHIP_NAVEL_ROCK),
|
|
"FLAG_ENABLE_SHIP_NAVEL_ROCK is set")
|
|
check(MysteryGift.getFlag(session, FLAG_RECEIVED_MYSTIC_TICKET),
|
|
"FLAG_RECEIVED_MYSTIC_TICKET is set")
|
|
check(not MysteryGift.isGiftNotReceived(session), "the card reads as collected")
|
|
|
|
local second = MysteryGift.deliverGift(session)
|
|
eq(second, MysteryGift.DELIVER_ALREADY, "the second visit hands over nothing")
|
|
eq(Bag.get(session.bag, MysteryGift.ITEM_MYSTIC_TICKET), 1, "still one ticket in the bag")
|
|
end
|
|
|
|
print("[test] 3. The received-gift flag alone closes a card")
|
|
do
|
|
session.bag = Bag.new()
|
|
MysteryGift.clear(session)
|
|
session.store.flags = {}
|
|
local cave = builtin("altering_cave")
|
|
MysteryGift.receiveCard(session, cave.card)
|
|
local flagId = MysteryGift.getCardFlagId(session)
|
|
local giftFlag = MysteryGift.receivedGiftFlag(flagId)
|
|
check(giftFlag ~= nil, "the card maps into sReceivedGiftFlags")
|
|
eq(MysteryGift.deliverGift(session), MysteryGift.DELIVER_GIVEN, "the first visit runs it")
|
|
check(MysteryGift.getFlag(session, giftFlag), "and sets its sReceivedGiftFlags entry")
|
|
eq(MysteryGift.deliverGift(session), MysteryGift.DELIVER_ALREADY, "a second visit does not")
|
|
end
|
|
|
|
print("[test] 4. trywondercardscript is the deliveryman's whole script")
|
|
do
|
|
session.bag = Bag.new()
|
|
MysteryGift.clear(session)
|
|
session.store.flags = {}
|
|
|
|
local shown = nil
|
|
local adapters = {
|
|
log = function() end,
|
|
playerName = "RED",
|
|
openMessageAsync = function(text, done)
|
|
shown = text
|
|
done()
|
|
end,
|
|
}
|
|
|
|
local ctx = newCtx()
|
|
check(Gift.runWonderCardScript(ctx, adapters) == false,
|
|
"with no saved card the script falls straight through")
|
|
check(shown == nil, "and opens no message box")
|
|
|
|
local mystic = builtin("mystic_ticket")
|
|
MysteryGift.receiveCard(session, mystic.card)
|
|
ctx = newCtx()
|
|
Gift.runWonderCardScript(ctx, adapters)
|
|
eq(Gift.lastDelivery, MysteryGift.DELIVER_GIVEN, "the deliveryman hands the gift over")
|
|
check(Bag.has(session.bag, MysteryGift.ITEM_MYSTIC_TICKET, 1), "the ticket is in the bag")
|
|
check(type(shown) == "table" or type(shown) == "string", "a message box was opened")
|
|
|
|
shown = nil
|
|
ctx = newCtx()
|
|
Gift.runWonderCardScript(ctx, adapters)
|
|
eq(Gift.lastDelivery, MysteryGift.DELIVER_ALREADY, "a second talk gives nothing")
|
|
eq(Bag.get(session.bag, MysteryGift.ITEM_MYSTIC_TICKET), 1, "and no second ticket")
|
|
end
|
|
|
|
print("[test] 5. The deliveryman's four outcomes")
|
|
do
|
|
local already = Gift.deliveryText(session, MysteryGift.DELIVER_ALREADY)
|
|
local noRoom = Gift.deliveryText(session, MysteryGift.DELIVER_NO_ROOM)
|
|
local full = Gift.deliveryText(session, MysteryGift.DELIVER_PARTY_FULL)
|
|
if romBundle then
|
|
check(tostring(already):find("MYSTERY"), "the already-collected line thanks the player")
|
|
-- pokefirered/data/mystery_event_msg.s:319 sText_MysticTicketNoPlace
|
|
check(tostring(noRoom):find("KEY ITEMS POCKET", 1, true), "the no-room line names the KEY ITEMS POCKET")
|
|
check(tostring(noRoom):find(tostring(session.name), 1, true), "and the player")
|
|
check(tostring(full):find("party"), "the party-full line names the party")
|
|
else
|
|
eq(already, "sText_MysticTicketGot", "the mystic card's already-collected line")
|
|
eq(noRoom, "sText_MysticTicketNoPlace", "the mystic card's no-room line")
|
|
eq(full, "sText_FullParty", "the party-full line")
|
|
end
|
|
local given = Gift.deliveryText(session, MysteryGift.DELIVER_GIVEN)
|
|
check(tostring(given):find("ticket"), "the handover line reads the card's own body text")
|
|
-- pokefirered/data/mystery_event_msg.s:303 sText_MysticTicket2
|
|
local pages = {}
|
|
for page in (tostring(given) .. "\\p"):gmatch("(.-)\\p") do pages[#pages + 1] = page end
|
|
eq(#pages, 2, "four body lines become two message pages")
|
|
for i, page in ipairs(pages) do
|
|
local lines = 1
|
|
for _ in page:gmatch("\n") do lines = lines + 1 end
|
|
eq(lines, 2, "page " .. i .. " is two lines high")
|
|
end
|
|
end
|
|
|
|
print("[test] 6. The 0xCF opcode runs the card script through the VM")
|
|
do
|
|
local Vm = require("src.core.game3.scripting.vm")
|
|
local Flags = require("src.core.game3.scripting.flags")
|
|
local FALL_THROUGH = 0x300
|
|
|
|
local function runMan()
|
|
session.bag = Bag.new()
|
|
session.store = Flags.newStore()
|
|
local shown = 0
|
|
local vm = Vm.new({
|
|
store = session.store,
|
|
scripts = {
|
|
-- pokefirered/data/scripts/cable_club.inc:15 CableClub_EventScript_MysteryGiftMan
|
|
man = {
|
|
{ op = "trywondercardscript", opcode = 207 },
|
|
{ op = "setflag", [1] = FALL_THROUGH, flag = FALL_THROUGH, opcode = 41 },
|
|
{ op = "end", opcode = 3 },
|
|
},
|
|
},
|
|
adapters = {
|
|
log = function() end,
|
|
playerName = "RED",
|
|
openMessageAsync = function(_, done) shown = shown + 1 done() end,
|
|
},
|
|
})
|
|
vm:start("man")
|
|
for _ = 1, 60 do
|
|
if not vm:isRunning() then break end
|
|
vm:tick()
|
|
end
|
|
return vm, shown
|
|
end
|
|
|
|
MysteryGift.clear(session)
|
|
local vm1, shown1 = runMan()
|
|
eq(shown1, 0, "no saved card opens no delivery message")
|
|
check(Flags.getFlag(session.store, vm1.ctx, FALL_THROUGH),
|
|
"and the script falls through to the rest of the deliveryman script")
|
|
|
|
MysteryGift.clear(session)
|
|
local mystic = builtin("mystic_ticket")
|
|
MysteryGift.receiveCard(session, mystic.card)
|
|
local vm2, shown2 = runMan()
|
|
eq(shown2, 2, "a saved card opens the delivery message, then the obtained line")
|
|
eq(Bag.get(session.bag, MysteryGift.ITEM_MYSTIC_TICKET), 1, "the opcode put the ticket in the bag")
|
|
check(MysteryGift.getFlag(session, FLAG_ENABLE_SHIP_NAVEL_ROCK),
|
|
"and set FLAG_ENABLE_SHIP_NAVEL_ROCK")
|
|
-- pokefirered/src/scrcmd.c:280 ScriptJump
|
|
check(not Flags.getFlag(session.store, vm2.ctx, FALL_THROUGH),
|
|
"the card script jumped away instead of falling through")
|
|
check(not vm2:isRunning(), "and the deliveryman script finished")
|
|
end
|
|
|
|
print("[test] 7. A shiny custom mon gift arrives shiny, with its fanfare")
|
|
if not romBundle then
|
|
print("[skip] no FireRed cache for the species data")
|
|
else
|
|
session.party = {}
|
|
session.bag = Bag.new()
|
|
MysteryGift.clear(session)
|
|
session.store.flags = {}
|
|
local otId = 0x12345678
|
|
local hi = 0xABCD
|
|
local lo = bit.bxor(bit.bxor(0x5678, 0x1234), hi)
|
|
local card = {}
|
|
for k, v in pairs(builtin("surf_pichu").card) do card[k] = v end
|
|
card.gift = { kind = "mon", species = 241, level = 20, personality = hi * 65536 + lo,
|
|
otName = "AURA", otId = otId }
|
|
check(MysteryGift.receiveCard(session, card), "the custom MILTANK card saves")
|
|
local shown = {}
|
|
local adapters = {
|
|
log = function() end,
|
|
playerName = "RED",
|
|
openMessageAsync = function(text, done)
|
|
shown[#shown + 1] = text
|
|
done()
|
|
end,
|
|
}
|
|
Gift.runWonderCardScript(newCtx(), adapters)
|
|
eq(Gift.lastDelivery, MysteryGift.DELIVER_GIVEN, "the deliveryman hands the MILTANK over")
|
|
local mon = session.party[#session.party]
|
|
eq(mon and mon.species, 241, "a MILTANK joined the party")
|
|
eq(mon and mon.otId, 0x5678, "its OT ID is the low half of the card's otId")
|
|
eq(mon and mon.otSecretId, 0x1234, "its secret ID is the high half")
|
|
local Pokemon = require("src.core.game3.pokemon")
|
|
check(mon and Pokemon.isShiny(mon), "and it is shiny")
|
|
eq(#shown, 2, "the card text is followed by an obtained line")
|
|
check(type(Gift.lastObtained) == "string" and Gift.lastObtained:find("obtained", 1, true) ~= nil,
|
|
"which says what was obtained")
|
|
-- pokefirered/include/constants/songs.h:264 MUS_LEVEL_UP
|
|
eq(fanfares[#fanfares], 257, "with the gift-mon fanfare")
|
|
end
|
|
|
|
print("[test] 8. A gift mon saved with the whole otId is split on load")
|
|
do
|
|
local SaveMon = require("src.core.game3.save_mon")
|
|
local mon = { otId = 0x12345678, otSecretId = 999 }
|
|
SaveMon.normalize(mon)
|
|
eq(mon.otId, 0x5678, "the OT ID keeps the low half")
|
|
eq(mon.otSecretId, 0x1234, "the secret ID takes the high half")
|
|
end
|
|
|
|
if failed == 0 then
|
|
print("PASS game3_gift_delivery")
|
|
else
|
|
print("FAIL game3_gift_delivery failures=" .. failed)
|
|
os.exit(1)
|
|
end
|