mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-28 23:00:21 -04:00
438 lines
23 KiB
Lua
438 lines
23 KiB
Lua
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local T = require("tests.harness")
|
|
local check, eq = T.check, T.eq
|
|
|
|
local GameVersion = require("src.core.GameVersion")
|
|
local prevVersion = GameVersion.get()
|
|
GameVersion.set("firered")
|
|
|
|
local Std = require("src.core.game3.scripting.stdscripts")
|
|
local Natives = require("src.core.game3.scripting.natives")
|
|
local Constants = require("src.core.game3.constants")
|
|
local Runtime = require("src.core.game3.runtime")
|
|
|
|
Natives.bind("firered")
|
|
|
|
local FR = Constants.of("firered").specials
|
|
local EM = Constants.of("emerald").specials
|
|
|
|
local SNAPSHOT = {
|
|
natives = { 0x0, 0x36, 0x38, 0x39, 0x3A, 0x3C, 0x5F, 0x60, 0x7C, 0x7D, 0x9D, 0x9E, 0x9F, 0xB4, 0xD6, 0xD7, 0xE6, 0xF9, 0xFA, 0xFB, 0x106, 0x107, 0x110, 0x137, 0x138, 0x139, 0x13A, 0x143, 0x156, 0x15B, 0x164, 0x166, 0x169, 0x16F, 0x172, 0x17D, 0x17E, 0x17F, 0x181, 0x184, 0x187, 0x188, 0x18F, 0x190, 0x193, 0x197, 0x198, 0x199, 0x1B2, 0xF001, 0xF002, 0xF003 },
|
|
natives_corner = { 0x15E },
|
|
natives_cutscene = { 0x108, 0x113, 0x114, 0x18B, 0x18C, 0x191, 0x1A1, 0x1A5, 0x1B5, 0x1B7, 0x1BA },
|
|
natives_daycare = { 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0x15F, 0x176, 0x177, 0x178, 0x179, 0x17A },
|
|
natives_elevator = { 0xD8, 0x111, 0x132, 0x160, 0x1B8 },
|
|
natives_events = { 0x8D, 0x8E, 0xAB, 0xCD, 0xCE, 0x129, 0x135, 0x136, 0x155, 0x157, 0x15C, 0x15D, 0x161, 0x167, 0x168, 0x170, 0x171, 0x19A, 0x1AB, 0x1AC, 0x1B9, 0x1BB },
|
|
natives_fame = { 0x173, 0x174 },
|
|
natives_fan_club = { 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA },
|
|
natives_gift = { 0x180, 0x186, 0x189 },
|
|
natives_link = { 0x1, 0x2, 0x3, 0x4, 0x5, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x2A, 0x3D, 0x5D, 0x127, 0x128, 0x14B, 0x16A, 0x16B, 0x16C, 0x16D, 0x16E, 0x182, 0x183, 0x1B3 },
|
|
natives_listmenu = { 0x158, 0x159 },
|
|
natives_moveteach = { 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0x18D, 0x1A3, 0x1A4 },
|
|
natives_queries = { 0x7B, 0x83, 0x84, 0x85, 0x94, 0x96, 0xBA, 0xC5, 0xC6, 0xD4, 0x11E, 0x11F, 0x130, 0x147, 0x148, 0x14F, 0x150, 0x153, 0x162, 0x163, 0x165, 0x17C, 0x18A, 0x196, 0x19B, 0x1AA, 0x1AD, 0x1AE, 0x1B0, 0x1B1, 0x1B4, 0x1B6 },
|
|
natives_seagallop = { 0x17B, 0x1A7, 0x1A8, 0x1A9 },
|
|
natives_size_record = { 0x77, 0x78, 0x79, 0x7A, 0xD5 },
|
|
natives_tower = { 0x27, 0x28, 0x29, 0xC4, 0xEC, 0xF6, 0xF8, 0x194 },
|
|
natives_trade = { 0xFC, 0xFD, 0xFE, 0xFF },
|
|
natives_wireless = { 0xEB, 0x11D, 0x142, 0x18E, 0x192, 0x195, 0x19C, 0x19D, 0x19E, 0x19F, 0x1A0, 0x1A2, 0x1A6 },
|
|
}
|
|
|
|
local function sourceOf(fn)
|
|
return debug.getinfo(fn, "S").short_src:match("([%w_]+)%.lua$")
|
|
end
|
|
|
|
local function boundIds()
|
|
local ids = {}
|
|
for key, fn in pairs(Natives.ALLOW) do
|
|
local id = tonumber(key:match("^special:(%d+)$"))
|
|
if id then ids[id] = fn end
|
|
end
|
|
return ids
|
|
end
|
|
|
|
local expected, expectedCount = {}, 0
|
|
for file, ids in pairs(SNAPSHOT) do
|
|
for _, id in ipairs(ids) do
|
|
expected[id] = file
|
|
expectedCount = expectedCount + 1
|
|
end
|
|
end
|
|
|
|
local frBound = boundIds()
|
|
local frCount = 0
|
|
for id, fn in pairs(frBound) do
|
|
frCount = frCount + 1
|
|
eq(sourceOf(fn), expected[id], string.format("FireRed special 0x%X is served by its snapshot module", id))
|
|
end
|
|
eq(frCount, expectedCount, "FireRed binds exactly the snapshot id set")
|
|
|
|
local aliases = Std.SPECIAL_ALIASES
|
|
for name, id in pairs(Std.SPECIAL) do
|
|
local pret = aliases[name] or name
|
|
if id < Std.SPECIAL_ENGINE_BASE then
|
|
eq(FR.byId[id], pret, "Std.SPECIAL." .. name .. " is pret special " .. pret)
|
|
end
|
|
local fn = Natives.BY_NAME[pret]
|
|
if fn then
|
|
check(Natives.ALLOW["special:" .. id] == fn,
|
|
string.format("FireRed id 0x%X resolves through its name %s to the same handler", id, pret))
|
|
end
|
|
end
|
|
|
|
for base, mod in pairs(Natives.MODULES) do
|
|
check(type(mod.BY_NAME) == "table", base .. " exports handlers by pret name")
|
|
for name, fn in pairs(mod.BY_NAME or {}) do
|
|
check(FR.byName[name] ~= nil or EM.byName[name] ~= nil or Std.SPECIAL[name] ~= nil,
|
|
base .. "." .. name .. " is a pret special name")
|
|
for id, legacy in pairs(mod.HANDLERS or {}) do
|
|
if FR.byId[id] == name then
|
|
check(legacy == fn, string.format("%s legacy HANDLERS[0x%X] is the %s handler", base, id, name))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
for name in pairs(Natives.CORE) do
|
|
check(FR.byName[name] ~= nil or Std.SPECIAL[name] ~= nil,
|
|
"core handler " .. name .. " is a pret special name or an engine special")
|
|
end
|
|
|
|
GameVersion.set("emerald")
|
|
Natives.resetLog()
|
|
check(Natives.ensureBound() == true, "an Emerald session rebinds the natives")
|
|
eq(Natives.boundGame, "emerald", "bound to the Emerald special table")
|
|
local SHARED = { natives_daycare = true, natives_elevator = true, natives_gift = true, natives_moveteach = true }
|
|
for m in pairs(Natives.MODULES) do
|
|
local Capabilities = require("src.core.game3.capabilities")
|
|
check(SHARED[m] or (Capabilities.nativeFeature(m) ~= nil and not Capabilities.nativeAllowed({ version = "firered" }, m)),
|
|
"Emerald loads only RSE-gated natives modules (" .. m .. ")")
|
|
end
|
|
|
|
local emBound = boundIds()
|
|
for id, fn in pairs(emBound) do
|
|
local name = Std.specialName("emerald", id)
|
|
check(name ~= nil, string.format("Emerald bound id 0x%X has a pret name", id))
|
|
check(Natives.BY_NAME[name] == fn, string.format("Emerald 0x%X runs the %s handler", id, tostring(name)))
|
|
end
|
|
eq(emBound[EM.byName.HealPlayerParty], Natives.CORE.HealPlayerParty, "Emerald HealPlayerParty is the shared heal handler")
|
|
local ereaderNames = { "ValidateEReaderTrainer", "CopyEReaderTrainerGreeting", "BufferEReaderTrainerName",
|
|
"SetEReaderTrainerGfxId" }
|
|
for _, name in ipairs(ereaderNames) do
|
|
local id = EM.byName[name]
|
|
check(id ~= nil and emBound[id] ~= nil, "Emerald " .. name .. " is bound")
|
|
end
|
|
local ereaderSpecial = EM.byName.ValidateEReaderTrainer
|
|
local oldEreaderSession = Runtime.session
|
|
Runtime.session = { version = "emerald", frontier = { ereaderTrainer = {
|
|
name = "ALPHA", party = { { species = 1 } }, greeting = "WELCOME", facilityClass = 0,
|
|
} } }
|
|
local ereaderCtx = { specialVars = {}, stringVars = {} }
|
|
Natives.special(ereaderCtx, ereaderSpecial, {})
|
|
eq(ereaderCtx.specialVars[0x800D], 0, "valid saved e-Reader trainer returns FALSE")
|
|
local ereaderWritten = {}
|
|
Natives.special(ereaderCtx, EM.byName.BufferEReaderTrainerName, {
|
|
setStringVar = function(i, value) ereaderWritten[i] = value end,
|
|
})
|
|
eq(ereaderCtx.stringVars[1], "ALPHA", "e-Reader name is buffered in string variable 1")
|
|
eq(ereaderWritten[1], "ALPHA", "e-Reader name is sent to the script adapter")
|
|
Natives.special(ereaderCtx, EM.byName.CopyEReaderTrainerGreeting, {
|
|
setStringVar = function(i, value) ereaderWritten[i] = value end,
|
|
})
|
|
eq(ereaderCtx.stringVars[4], "WELCOME", "e-Reader greeting is buffered in string variable 4")
|
|
local EReaderTrainers = require("src.core.game3.rse.frontier.trainers")
|
|
local oldFacilityClassToGfx = EReaderTrainers.facilityClassToGfx
|
|
local Space = require("src.core.game3.scripting.space")
|
|
local oldEReaderStore = Space.store
|
|
Space.store = { vars = {} }
|
|
EReaderTrainers.facilityClassToGfx = function(facilityClass)
|
|
eq(facilityClass, 0, "e-Reader gfx lookup receives the saved facility class")
|
|
return 77
|
|
end
|
|
Natives.special(ereaderCtx, EM.byName.SetEReaderTrainerGfxId, {})
|
|
local EReaderConstants = require("src.core.game3.constants").of("emerald")
|
|
local gfxVar = EReaderConstants:var("VAR_OBJ_GFX_ID_0")
|
|
eq(Space.store.vars[gfxVar], 77, "e-Reader graphic is assigned to Emerald's object graphics variable")
|
|
EReaderTrainers.facilityClassToGfx = oldFacilityClassToGfx
|
|
Space.store = oldEReaderStore
|
|
Runtime.session = { version = "emerald", frontier = { ereaderTrainer = {} } }
|
|
Natives.special(ereaderCtx, ereaderSpecial, {})
|
|
eq(ereaderCtx.specialVars[0x800D], 1, "empty e-Reader record returns TRUE")
|
|
Runtime.session = { version = "emerald", frontier = { ereaderTrainer = {
|
|
name = "BROKEN", party = { { species = 1 } }, checksumValid = false,
|
|
} } }
|
|
Natives.special(ereaderCtx, ereaderSpecial, {})
|
|
eq(ereaderCtx.specialVars[0x800D], 1, "invalid e-Reader checksum returns TRUE")
|
|
Runtime.session = oldEreaderSession
|
|
local trainerIntro = EM.byName.ShowTrainerIntroSpeech
|
|
local trainerCantBattle = EM.byName.ShowTrainerCantBattleSpeech
|
|
local prepareSecondTrainer = EM.byName.TryPrepareSecondApproachingTrainer
|
|
local trainerApproach = EM.byName.DoTrainerApproach
|
|
check(Natives.BY_NAME.DoTrainerApproach ~= nil
|
|
and Natives.ALLOW["special:" .. trainerApproach] ~= nil,
|
|
"Emerald DoTrainerApproach has a deliberate TrainerSight no-op handler")
|
|
check(prepareSecondTrainer ~= nil and emBound[prepareSecondTrainer] ~= nil,
|
|
"Emerald TryPrepareSecondApproachingTrainer is bound")
|
|
local secondTrainerCtx = { specialVars = { [0x800D] = 1 } }
|
|
eq(Natives.special(secondTrainerCtx, prepareSecondTrainer, {}), false,
|
|
"TryPrepareSecondApproachingTrainer completes synchronously")
|
|
eq(secondTrainerCtx.specialVars[0x800D], 0, "no queued second approach clears VAR_RESULT")
|
|
check(trainerIntro ~= nil and emBound[trainerIntro] ~= nil, "Emerald ShowTrainerIntroSpeech is bound")
|
|
check(trainerCantBattle ~= nil and emBound[trainerCantBattle] ~= nil,
|
|
"Emerald ShowTrainerCantBattleSpeech is bound")
|
|
local TrainerData = require("src.core.game3.scripting.trainers")
|
|
local Pyramid = require("src.core.game3.rse.frontier.pyramid")
|
|
local Hill = require("src.core.game3.rse.trainer_hill")
|
|
local oldDialogs, oldInPyramid, oldInHill = TrainerData.dialogs, Pyramid.inPyramid, Hill.inChallenge
|
|
TrainerData.dialogs = function(trainerId)
|
|
eq(trainerId, 321, "trainer speech resolves the active opponent id")
|
|
return { intro = "CHALLENGER!", notEnough = "NOT ENOUGH POKEMON!" }
|
|
end
|
|
Pyramid.inPyramid, Hill.inChallenge = function() return false end, function() return false end
|
|
Runtime.session = { version = "emerald" }
|
|
local trainerMessages = {}
|
|
local trainerCtx = { trainerBattleOpponentA = 321, specialVars = {} }
|
|
Natives.special(trainerCtx, trainerIntro, { openMessageAsync = function(message, done)
|
|
trainerMessages[#trainerMessages + 1] = message
|
|
done()
|
|
end })
|
|
eq(trainerMessages[1], "CHALLENGER!", "ShowTrainerIntroSpeech opens the trainer intro text")
|
|
eq(trainerCtx.trainerIntroShown, true, "field intro is marked to prevent duplicate trainerbattle text")
|
|
Natives.special(trainerCtx, trainerCantBattle, { openMessageAsync = function(message, done)
|
|
trainerMessages[#trainerMessages + 1] = message
|
|
done()
|
|
end })
|
|
eq(trainerMessages[2], "NOT ENOUGH POKEMON!", "ShowTrainerCantBattleSpeech opens the refusal text")
|
|
TrainerData.dialogs, Pyramid.inPyramid, Hill.inChallenge = oldDialogs, oldInPyramid, oldInHill
|
|
local Vm = require("src.core.game3.scripting.vm")
|
|
local Ops = require("src.core.game3.scripting.ops_a")
|
|
local oldFoeFromId = TrainerData.foeFromId
|
|
TrainerData.dialogs = function() return { intro = "DUPLICATE INTRO" } end
|
|
TrainerData.foeFromId = function(id) return { trainerId = id } end
|
|
local duplicateIntroCount = 0
|
|
local introVm = Vm.new({ version = "emerald", adapters = {} })
|
|
introVm.ctx.trainerIntroShown = true
|
|
introVm.ctx.pc = { listKey = "test", index = 1 }
|
|
introVm.adapters.openMessageAsync = function(_, done)
|
|
duplicateIntroCount = duplicateIntroCount + 1
|
|
done()
|
|
end
|
|
introVm.adapters.startTrainerBattle = function(_, done) done("lose") end
|
|
Ops.dispatch(introVm, { op = "trainerbattle", type = 0, trainer = 321 })
|
|
eq(duplicateIntroCount, 0, "trainerbattle does not repeat an intro already shown by the special")
|
|
eq(introVm.ctx.trainerIntroShown, nil, "trainerbattle consumes the intro-shown marker")
|
|
TrainerData.dialogs, TrainerData.foeFromId = oldDialogs, oldFoeFromId
|
|
Runtime.session = oldEreaderSession
|
|
check(emBound[EM.byName.ShouldTryRematchBattle] ~= Natives.CORE.ShouldTryRematchBattle, "the VS Seeker rematch handler is not bound on Emerald")
|
|
local frRematchId = FR.byName.ShouldTryRematchBattle
|
|
eq(Std.specialName("emerald", frRematchId), "GetTrainerFlag",
|
|
"the FireRed rematch id means Emerald GetTrainerFlag at that index")
|
|
eq(emBound[frRematchId], Natives.BY_NAME.GetTrainerFlag,
|
|
"the overlapping Emerald index dispatches by Emerald special name")
|
|
eq(emBound[0xF001], Natives.CORE.FadeScreen, "engine specials stay bound on Emerald")
|
|
check(emBound[EM.byName.ChooseMonForWirelessMinigame] ~= nil,
|
|
"Emerald wireless minigame party selection uses the shared eligibility-aware picker")
|
|
local porthole = EM.byName.LookThroughPorthole
|
|
check(porthole ~= nil and emBound[porthole] ~= nil,
|
|
"Emerald LookThroughPorthole is bound to the route sailing scene")
|
|
local PortholeScene = require("src.core.game3.special_scene_rse")
|
|
local portholeGroups = {
|
|
MAP_ROUTE132 = { group = 0, num = 47 },
|
|
MAP_ROUTE133 = { group = 0, num = 48 },
|
|
MAP_ROUTE134 = { group = 0, num = 49 },
|
|
}
|
|
local function portholeDest(state, steps)
|
|
return PortholeScene.portholeDestination(state, steps, portholeGroups)
|
|
end
|
|
local dest = portholeDest(2, 0)
|
|
eq(dest.map, "MAP_ROUTE134", "Slateport departure begins sailing across Route 134")
|
|
eq(dest.x, 19, "Slateport departure starts at the Emerald Route 134 coordinate")
|
|
eq(portholeDest(2, 60).map, "MAP_ROUTE133", "eastbound sailing crosses to Route 133 at step 60")
|
|
eq(portholeDest(2, 140).x, 0, "eastbound sailing starts Route 132 at x=0")
|
|
eq(portholeDest(7, 0).x, 65, "westbound halfway sailing starts Route 132 at x=65")
|
|
eq(portholeDest(7, 66).map, "MAP_ROUTE133", "westbound sailing crosses to Route 133 at step 66")
|
|
eq(portholeDest(7, 146).x, 78, "westbound sailing enters Route 134 at x=78")
|
|
eq(portholeDest(99, 0), nil, "invalid porthole cruise states have no route destination")
|
|
local logs = {}
|
|
local adapters = { log = function(m) logs[#logs + 1] = m end }
|
|
local poisonWhiteOut = EM.byName.TryFieldPoisonWhiteOut
|
|
check(poisonWhiteOut ~= nil and Natives.ALLOW["special:" .. poisonWhiteOut] ~= nil,
|
|
"Emerald TryFieldPoisonWhiteOut is bound")
|
|
local Pokemon = require("src.core.game3.pokemon")
|
|
local RomText = require("src.core.game3.rom_text")
|
|
local oldPoisonSession, oldIsEgg = Runtime.session, Pokemon.isEgg
|
|
local oldAdjust, oldName, oldMapSec, oldBox = Pokemon.adjustFriendship, Pokemon.displayMonName,
|
|
Pokemon.currentMapSec, RomText.box
|
|
local poisonSession = { version = "emerald", map = "EM_LITTLEROOT_TOWN", vars = {}, party = {
|
|
{ species = 1, hp = 0, status = "PSN" }, { species = 4, hp = 0 },
|
|
} }
|
|
Runtime.session = poisonSession
|
|
Pokemon.isEgg = function(mon) return mon.isEgg == true end
|
|
Pokemon.adjustFriendship = function() end
|
|
Pokemon.displayMonName = function() return "MON" end
|
|
Pokemon.currentMapSec = function() return 0 end
|
|
RomText.box = function(key, ctx) return key .. ":" .. ctx.stringVars[1] end
|
|
local poisonMessageCount, poisonCtx = 0, { specialVars = {} }
|
|
Natives.special(poisonCtx, poisonWhiteOut, { openMessageAsync = function(msg, done)
|
|
poisonMessageCount = poisonMessageCount + 1
|
|
check(msg:find("MON", 1, true) ~= nil, "poison faint message buffers the fainted Pokémon")
|
|
done()
|
|
end })
|
|
eq(poisonMessageCount, 1, "poison whiteout shows each poison faint once")
|
|
eq(poisonCtx.specialVars[0x800D], 1, "ordinary party wipe returns FLDPSN_WHITEOUT")
|
|
eq(poisonSession.party[1].status, nil, "poison faint clears the status")
|
|
Runtime.session, Pokemon.isEgg, Pokemon.adjustFriendship, Pokemon.displayMonName,
|
|
Pokemon.currentMapSec, RomText.box = oldPoisonSession, oldIsEgg, oldAdjust, oldName, oldMapSec, oldBox
|
|
local rematchStart = EM.byName.BattleSetup_StartRematchBattle
|
|
check(rematchStart ~= nil and Natives.ALLOW["special:" .. rematchStart] ~= nil,
|
|
"Emerald BattleSetup_StartRematchBattle is bound")
|
|
local Trainers = require("src.core.game3.scripting.trainers")
|
|
local Rematch = require("src.core.game3.rse.rematch")
|
|
local oldRematchSession, oldFoeFromId, oldRematchWon = Runtime.session, Trainers.foeFromId, Rematch.onRematchBattleWon
|
|
local rematchSession = { version = "emerald", flags = {}, vars = {}, trainerRematches = {} }
|
|
Runtime.session = rematchSession
|
|
Trainers.foeFromId = function(id) return { trainerId = id, species = 1, level = 10 } end
|
|
local wonTrainer, startOptions
|
|
Rematch.onRematchBattleWon = function(sess, id) wonTrainer = id; eq(sess, rematchSession, "rematch completion uses active session") end
|
|
local battleStarted = false
|
|
Natives.special({ trainerBattleOpponentA = 123, trainerBattleMode = 4 }, rematchStart, {
|
|
startTrainerBattle = function(foe, done, opts)
|
|
battleStarted, startOptions = foe.trainerId == 123, opts
|
|
done("win")
|
|
end,
|
|
})
|
|
eq(battleStarted, true, "rematch special starts the active trainer battle")
|
|
eq(startOptions and startOptions.double, true, "double rematch preserves its battle type")
|
|
eq(wonTrainer, 123, "won rematch updates Emerald rematch state")
|
|
Runtime.session, Trainers.foeFromId, Rematch.onRematchBattleWon = oldRematchSession, oldFoeFromId, oldRematchWon
|
|
local partnerNames = EM.byName.GetLinkPartnerNames
|
|
check(partnerNames ~= nil and Natives.ALLOW["special:" .. partnerNames] ~= nil,
|
|
"Emerald GetLinkPartnerNames is bound")
|
|
local Link = require("src.core.game3.link.init")
|
|
local oldLink = Link.link
|
|
local partnerPlayers = {
|
|
{ seat = 0, name = "BRENDAN", isLocal = true },
|
|
{ seat = 1, name = "MAY", isLocal = false },
|
|
{ seat = 2, name = "WALLY", isLocal = false },
|
|
}
|
|
Link.link = { role = "host", isOpen = function() return true end,
|
|
players = function() return partnerPlayers end }
|
|
local partnerCtx, writtenNames = { stringVars = {} }, {}
|
|
Natives.special(partnerCtx, partnerNames, { setStringVar = function(i, v) writtenNames[i] = v end })
|
|
eq(partnerCtx.stringVars[1], "MAY", "GetLinkPartnerNames fills the first remote name")
|
|
eq(partnerCtx.stringVars[2], "WALLY", "GetLinkPartnerNames fills the second remote name")
|
|
eq(writtenNames[1], "MAY", "GetLinkPartnerNames writes through the VM adapter")
|
|
eq(require("src.core.game3.link.battle").playerCount(), 3, "test link has three multiplayer players")
|
|
local gfxSpecial = EM.byName.SetBattleTowerLinkPlayerGfx
|
|
check(gfxSpecial ~= nil and Natives.ALLOW["special:" .. gfxSpecial] ~= nil,
|
|
"Emerald SetBattleTowerLinkPlayerGfx is bound")
|
|
local oldRuntimeSession, oldSetVar = Runtime.session, Link.setVar
|
|
Runtime.session = { version = "emerald" }
|
|
local gfxRows = {
|
|
{ seat = 0, gender = 0, isLocal = true },
|
|
{ seat = 1, gender = 1, isLocal = false },
|
|
}
|
|
Link.link.players = function() return gfxRows end
|
|
local gfxVars = {}
|
|
Link.setVar = function(_, id, value) gfxVars[id] = value end
|
|
Natives.special({ specialVars = {} }, gfxSpecial, adapters)
|
|
local EC = require("src.core.game3.constants").of("emerald")
|
|
local gfxVar = EC:var("VAR_OBJ_GFX_ID_F")
|
|
eq(gfxVars[gfxVar], EC:require("event_objects", "OBJ_EVENT_GFX_BRENDAN_NORMAL"),
|
|
"link graphics special selects Brendan for a male player")
|
|
eq(gfxVars[gfxVar - 1], EC:require("event_objects", "OBJ_EVENT_GFX_RIVAL_MAY_NORMAL"),
|
|
"link graphics special selects May for a female partner")
|
|
local spawnPartners = EM.byName.SpawnLinkPartnerObjectEvent
|
|
check(spawnPartners ~= nil and Natives.ALLOW["special:" .. spawnPartners] ~= nil,
|
|
"Emerald SpawnLinkPartnerObjectEvent is bound")
|
|
local VirtualObjects = require("src.core.game3.virtual_objects")
|
|
local Player = require("src.core.game3.player")
|
|
local BattleLink = require("src.core.game3.link.battle")
|
|
local oldFacing, oldCellX, oldCellY, oldMultiplayerId = Player.facing, Player.cellX, Player.cellY,
|
|
BattleLink.multiplayerId
|
|
Runtime.session = { version = "emerald" }
|
|
Player.facing, Player.cellX, Player.cellY = "right", 10, 10
|
|
BattleLink.multiplayerId = function() return 0 end
|
|
Link.link.players = function() return gfxRows end
|
|
VirtualObjects.remove(0x7F01)
|
|
Natives.special({ specialVars = { [0x8004] = 2 } }, spawnPartners, adapters)
|
|
local partnerAvatar = VirtualObjects.get(0x7F01)
|
|
check(partnerAvatar ~= nil, "partner spawn creates a virtual avatar for the remote seat")
|
|
eq(partnerAvatar.graphicsId, EC:require("event_objects", "OBJ_EVENT_GFX_RIVAL_MAY_NORMAL"),
|
|
"partner avatar uses Emerald's female trainer sprite")
|
|
eq(partnerAvatar.x, 11, "partner avatar uses the Emerald facing-relative seat x")
|
|
eq(partnerAvatar.y, 11, "partner avatar uses the Emerald facing-relative seat y")
|
|
VirtualObjects.remove(0x7F01)
|
|
gfxRows[2].version = "sapphire"
|
|
Natives.special({ specialVars = { [0x8004] = 2 } }, spawnPartners, adapters)
|
|
partnerAvatar = VirtualObjects.get(0x7F01)
|
|
eq(partnerAvatar.graphicsId, EC:require("event_objects", "OBJ_EVENT_GFX_LINK_RS_MAY"),
|
|
"Ruby/Sapphire link partner keeps the legacy R/S player graphics")
|
|
VirtualObjects.remove(0x7F01)
|
|
gfxRows[2].version = nil
|
|
Player.facing, Player.cellX, Player.cellY, BattleLink.multiplayerId = oldFacing, oldCellX, oldCellY,
|
|
oldMultiplayerId
|
|
Runtime.session, Link.setVar = oldRuntimeSession, oldSetVar
|
|
Link.link = oldLink
|
|
local startWired = EM.byName.Script_StartWiredTrade
|
|
check(Natives.BY_NAME.Script_StartWiredTrade == nil
|
|
and Natives.ALLOW["special:" .. startWired] == nil,
|
|
"Emerald Script_StartWiredTrade is left unbound until wired trade flow exists")
|
|
local saveGameSpecial = EM.byName.CableClubSaveGame
|
|
local oldGame = Runtime._game
|
|
local saved = false
|
|
Runtime._game = { saveGame = function() saved = true end }
|
|
check(saveGameSpecial ~= nil and Natives.ALLOW["special:" .. saveGameSpecial] ~= nil,
|
|
"Emerald CableClubSaveGame is bound")
|
|
Natives.special({ specialVars = {} }, saveGameSpecial, adapters)
|
|
eq(saved, true, "CableClubSaveGame calls the live game's save function")
|
|
local towerSave = EM.byName.SaveForBattleTowerLink
|
|
check(towerSave ~= nil and Natives.ALLOW["special:" .. towerSave] ~= nil,
|
|
"Emerald SaveForBattleTowerLink is bound")
|
|
saved = false
|
|
Natives.special({ specialVars = {} }, towerSave, adapters)
|
|
eq(saved, true, "SaveForBattleTowerLink saves before Tower link play")
|
|
Runtime._game = oldGame
|
|
|
|
local weatherSpecial = EM.byName.Unused_SetWeatherSunny
|
|
check(weatherSpecial ~= nil and Natives.ALLOW["special:" .. weatherSpecial] ~= nil,
|
|
"Emerald Unused_SetWeatherSunny has a runtime handler")
|
|
local Weather = require("src.core.game3.weather")
|
|
local WeatherEngine = require("src.core.game3.field_weather_rse")
|
|
local oldSetWeather, oldSetCurrent = Weather.setWeather, WeatherEngine.setCurrentAndNextWeather
|
|
local savedWeather, currentWeather
|
|
Weather.setWeather = function(id) savedWeather = id end
|
|
WeatherEngine.setCurrentAndNextWeather = function(id) currentWeather = id end
|
|
Natives.special({ specialVars = {} }, weatherSpecial, adapters)
|
|
eq(savedWeather, Weather.SUNNY, "sunny special updates the saved weather")
|
|
eq(currentWeather, Weather.SUNNY, "sunny special immediately updates current and next weather")
|
|
Weather.setWeather, WeatherEngine.setCurrentAndNextWeather = oldSetWeather, oldSetCurrent
|
|
|
|
oldGame = Runtime._game
|
|
Runtime._game = {}
|
|
local softReset = EM.byName.DoSoftReset
|
|
check(softReset ~= nil and Natives.ALLOW["special:" .. softReset] ~= nil,
|
|
"Emerald DoSoftReset has a runtime handler")
|
|
Natives.special({ specialVars = {} }, softReset, adapters)
|
|
eq(Runtime._game.softResetRequested, true, "soft-reset special schedules the existing title reset")
|
|
Runtime._game = oldGame
|
|
|
|
GameVersion.set("firered")
|
|
check(Natives.ensureBound() == true, "switching back to FireRed rebinds")
|
|
local again = boundIds()
|
|
local same = true
|
|
for id, fn in pairs(frBound) do
|
|
if again[id] ~= fn then same = false end
|
|
end
|
|
for id in pairs(again) do
|
|
if frBound[id] == nil then same = false end
|
|
end
|
|
check(same, "FireRed binding is identical after an Emerald round trip")
|
|
eq(Natives.MODULES.natives_queries, Natives.Queries, "Natives.Queries tracks the bound module set")
|
|
|
|
GameVersion.set("leafgreen")
|
|
eq(Natives.ensureBound(), false, "LeafGreen shares the FireRed binding")
|
|
|
|
GameVersion.set(prevVersion)
|
|
Natives.bind(prevVersion)
|
|
T.finish("game3_specials_by_name_test")
|