-- Tests for Gen 3 one-off script specials: -- 1. Field_AskSaveTheGame (0x5D) -- 2. SetWalkingIntoSignVars (0x170) -- 3. LoadPlayerBag (0x14B) -- 4. StickerManGetBragFlags (0x168) -- 5. UpdateTrainerCardPhotoIcons (0x167) -- 6. SeafoamIslandsB4F_CurrentDumpsPlayerOnLand (0x15C) local Std = require("src.core.game3.scripting.stdscripts") local Natives = require("src.core.game3.scripting.natives") local Flags = require("src.core.game3.scripting.flags") local Schema = require("src.core.game3.save_schema_firered") local Link = require("src.core.game3.link.init") local passed, failed = 0, 0 local function check(cond, msg) if cond then passed = passed + 1 print("[ok] " .. msg) else failed = failed + 1 print("[FAIL] " .. msg) end end local function checkEq(got, expected, msg) if got == expected then passed = passed + 1 print(string.format("[ok] %s (got %s)", msg, tostring(got))) else failed = failed + 1 print(string.format("[FAIL] %s: expected %s, got %s", msg, tostring(expected), tostring(got))) end end print("=== 1. Field_AskSaveTheGame (0x5D) ===") do local session = Schema.newGame({ name = "RED" }) local rt = { getSession = function() return session end, game = function() return {} end } package.loaded["src.core.game3.runtime"] = rt local ctx = { flags = session.flags, vars = session.vars, stringVars = session.stringVars, specialVars = session.specialVars, } local mockAdapters = {} -- Mock SaveMenu local SaveMenu = require("src.ui.game3.save_menu") SaveMenu.show = function(opts) SaveMenu._phase = "saved" opts.onClose() end local yielded = Natives.special(ctx, Std.SPECIAL.Field_AskSaveTheGame, mockAdapters) checkEq(yielded, false, "Field_AskSaveTheGame completes") checkEq(Flags.getVar(session, ctx, 0x800D), 1, "Field_AskSaveTheGame sets VAR_RESULT = 1 on save") -- Cancel save test SaveMenu.show = function(opts) SaveMenu._phase = "cancelled" opts.onClose() end Natives.special(ctx, Std.SPECIAL.Field_AskSaveTheGame, mockAdapters) checkEq(Flags.getVar(session, ctx, 0x800D), 0, "Field_AskSaveTheGame sets VAR_RESULT = 0 on cancel") end print("=== 2. SetWalkingIntoSignVars (0x170) ===") do local session = Schema.newGame({ name = "RED" }) package.loaded["src.core.game3.runtime"] = { getSession = function() return session end } local ctx = { session = session, flags = session.flags, vars = session.vars, stringVars = session.stringVars, specialVars = session.specialVars, } Natives.special(ctx, Std.SPECIAL.SetWalkingIntoSignVars) checkEq(ctx.walkAwayFromSignInhibitTimer, 6, "ctx.walkAwayFromSignInhibitTimer set to 6") checkEq(ctx.msgBoxIsCancelable, true, "ctx.msgBoxIsCancelable is true") checkEq(ctx.canWalkAway, true, "ctx.canWalkAway is true") checkEq(session.walkAwayFromSignInhibitTimer, 6, "session.walkAwayFromSignInhibitTimer set to 6") checkEq(session.msgBoxIsCancelable, true, "session.msgBoxIsCancelable is true") end print("=== 3. LoadPlayerBag (0x14B) ===") do local session = Schema.newGame({ name = "RED" }) session.bag = { pockets = { items = { { item = 13, qty = 5 } } } } local rt = { getSession = function() return session end } package.loaded["src.core.game3.runtime"] = rt local ctx = { flags = session.flags, vars = session.vars, stringVars = session.stringVars, specialVars = session.specialVars, } local yielded = Natives.special(ctx, Std.SPECIAL.LoadPlayerBag) checkEq(yielded, false, "LoadPlayerBag completes without yielding") check(Link._bagBackup ~= nil, "Link._bagBackup created") checkEq(Link._bagBackup.pockets.items[1].qty, 5, "Link._bagBackup has correct quantity") end print("=== 4. StickerManGetBragFlags (0x168) ===") do local session = Schema.newGame({ name = "RED" }) -- game_stat.h session.gameStats = { [10] = 12, [13] = 70000, [23] = 5 } session.hofClears = 99 session.eggsHatched = 1 session.linkBattleWins = 1 local rt = { getSession = function() return session end } package.loaded["src.core.game3.runtime"] = rt local ctx = { flags = session.flags, vars = session.vars, stringVars = session.stringVars, specialVars = session.specialVars, } local yielded, result = Natives.special(ctx, Std.SPECIAL.StickerManGetBragFlags) checkEq(yielded, false, "StickerManGetBragFlags does not yield") checkEq(Flags.getVar(session, ctx, 0x8004), 12, "VAR_0x8004 has HOF clears (12)") checkEq(Flags.getVar(session, ctx, 0x8005), 65535, "VAR_0x8005 clamped to 0xFFFF (65535)") checkEq(Flags.getVar(session, ctx, 0x8006), 5, "VAR_0x8006 has link wins (5)") -- Bitmask: bit 0 (HOF>0) = 1, bit 1 (eggs>0) = 2, bit 2 (linkWins>0) = 4 -> 1+2+4 = 7 checkEq(Flags.getVar(session, ctx, 0x8008), 7, "VAR_0x8008 has brag bitmask 7") checkEq(Flags.getVar(session, ctx, 0x800D), 7, "VAR_RESULT has brag bitmask 7") -- Test zero stats session.hofClears = 0 session.eggsHatched = 0 session.linkBattleWins = 0 session.gameStats = { [10] = 0, [13] = 0, [23] = 0 } Natives.special(ctx, Std.SPECIAL.StickerManGetBragFlags) checkEq(Flags.getVar(session, ctx, 0x8008), 0, "VAR_0x8008 is 0 when all stats are 0") end print("=== 5. UpdateTrainerCardPhotoIcons (0x167) ===") do local session = Schema.newGame({ name = "RED" }) -- Party with 3 mons session.party = { { speciesId = 1 }, -- Bulbasaur { speciesId = 4 }, -- Charmander { speciesId = 7 }, -- Squirtle } local rt = { getSession = function() return session end } package.loaded["src.core.game3.runtime"] = rt local ctx = { flags = session.flags, vars = session.vars, stringVars = session.stringVars, specialVars = session.specialVars, } Flags.setVar(session, ctx, 0x8004, 2) -- Tint index 2 Natives.special(ctx, Std.SPECIAL.UpdateTrainerCardPhotoIcons) -- Check slots 1..3 checkEq(Flags.getVar(session, ctx, 0x4043), 1, "VAR_TRAINER_CARD_MON_ICON_1 is Bulbasaur (1)") checkEq(Flags.getVar(session, ctx, 0x4044), 4, "VAR_TRAINER_CARD_MON_ICON_2 is Charmander (4)") checkEq(Flags.getVar(session, ctx, 0x4045), 7, "VAR_TRAINER_CARD_MON_ICON_3 is Squirtle (7)") -- Check empty slots 4..6 are padded with 0 checkEq(Flags.getVar(session, ctx, 0x4046), 0, "VAR_TRAINER_CARD_MON_ICON_4 is empty (0)") checkEq(Flags.getVar(session, ctx, 0x4047), 0, "VAR_TRAINER_CARD_MON_ICON_5 is empty (0)") checkEq(Flags.getVar(session, ctx, 0x4048), 0, "VAR_TRAINER_CARD_MON_ICON_6 is empty (0)") -- Check tint index checkEq(Flags.getVar(session, ctx, 0x4042), 2, "VAR_TRAINER_CARD_MON_ICON_TINT_IDX is 2") end print("=== 6. SeafoamIslandsB4F_CurrentDumpsPlayerOnLand (0x15C) ===") do local session = Schema.newGame({ name = "RED" }) session.surfing = true session.player = { surfing = true, state = "surf", facing = "left" } session.facing = "left" local rt = { getSession = function() return session end, player = { surfing = true, state = "surf", facing = "left" }, } package.loaded["src.core.game3.runtime"] = rt local ctx = { flags = session.flags, vars = session.vars, stringVars = session.stringVars, specialVars = session.specialVars, } Natives.special(ctx, Std.SPECIAL.SeafoamIslandsB4F_CurrentDumpsPlayerOnLand) checkEq(session.surfing, false, "session.surfing is false") checkEq(session.player.surfing, false, "session.player.surfing is false") checkEq(session.player.state, "walk", "session.player.state is 'walk'") checkEq(session.player.facing, "up", "session.player.facing is 'up' (North)") checkEq(rt.player.surfing, false, "rt.player.surfing is false") checkEq(rt.player.state, "walk", "rt.player.state is 'walk'") checkEq(rt.player.facing, "up", "rt.player.facing is 'up' (North)") end print("=== 7. Sign walk-away: DisableMsgBoxWalkaway (0x171) + pollWalkaway ===") do local session = Schema.newGame({ name = "RED" }) package.loaded["src.core.game3.runtime"] = { getSession = function() return session end } local ctx = { session = session, flags = session.flags, vars = session.vars, stringVars = session.stringVars, specialVars = session.specialVars, } local Events = require("src.core.game3.scripting.natives_events") local closed, halted = 0, false local vm = { ctx = ctx, adapters = { closeMessage = function() closed = closed + 1 end }, isRunning = function() return true end, halt = function(_, aborted) halted = aborted == true end, } local inputDown = { wasPressed = function(_, k) return k == "down" end, isDown = function(_, k) return k == "down" end, } local inputUp = { wasPressed = function(_, k) return k == "up" end, isDown = function(_, k) return k == "up" end, } Natives.special(ctx, Std.SPECIAL.SetWalkingIntoSignVars) ctx.messageOpen = true ctx.specialVars[0x800C] = 2 for _ = 1, 6 do Events.pollWalkaway(vm, inputDown) end checkEq(ctx.walkAwayFromSignInhibitTimer, 0, "inhibit timer counts down to 0") checkEq(halted, false, "no cancel while the inhibit window runs") checkEq(closed, 0, "message stays open during the inhibit window") Events.pollWalkaway(vm, inputDown) checkEq(closed, 1, "walkaway closes the sign message") check(halted == true, "walkaway aborts the script (release + end)") checkEq(ctx.walkAwayFromSignInhibitTimer, nil, "walkaway state cleared on cancel") vm.isRunning = function() return false end Natives.special(ctx, Std.SPECIAL.SetWalkingIntoSignVars) Events.pollWalkaway(vm, inputUp) checkEq(ctx.walkAwayFromSignInhibitTimer, nil, "state cleared once the script stops") checkEq(session.msgBoxIsCancelable, nil, "...on ctx and session both") -- script.c:245 vm.isRunning = function() return true end halted, closed = false, 0 Natives.special(ctx, Std.SPECIAL.SetWalkingIntoSignVars) Natives.special(ctx, Std.SPECIAL.DisableMsgBoxWalkaway) ctx.messageOpen = true for _ = 1, 6 do Events.pollWalkaway(vm, inputDown) end Events.pollWalkaway(vm, inputDown) checkEq(ctx.msgBoxIsCancelable, true, "disable keeps ctx.msgBoxIsCancelable") checkEq(ctx.canWalkAway, false, "disable sets ctx.canWalkAway=false") checkEq(session.canWalkAway, false, "disable sets session.canWalkAway=false") checkEq(halted, false, "disabled walkaway never cancels") checkEq(closed, 0, "message stays open when walkaway is disabled") -- field_control_avatar.c:323 local deferred package.loaded["src.core.game3.runtime"].defer = function(fn) deferred = fn return true end local inputStart = { wasPressed = function(_, k) return k == "start" end, isDown = function(_, k) return k == "start" end, } Events.pollWalkaway(vm, inputStart) checkEq(closed, 1, "START cancels the sign even with walkaway disabled") check(halted == true, "START aborts the sign script") check(type(deferred) == "function", "START queues the start menu open") package.loaded["src.core.game3.runtime"].defer = nil halted, closed = false, 0 Natives.special(ctx, Std.SPECIAL.SetWalkingIntoSignVars) ctx.messageOpen = true for _ = 1, 6 do Events.pollWalkaway(vm, inputUp) end Events.pollWalkaway(vm, inputUp) checkEq(halted, false, "pushing the way the player faces does not cancel") end print(string.format("\nTotal: %d passed, %d failed", passed, failed)) if failed > 0 then os.exit(1) end