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
287 lines
12 KiB
Lua
287 lines
12 KiB
Lua
#!/usr/bin/env luajit
|
|
-- Kanto Town Map & Region Map Unit Test Suite
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
require("tests.game3_cache").mountOrSkip("town_map")
|
|
|
|
local failed = 0
|
|
local function check(cond, msg)
|
|
if cond then
|
|
print("[ok] " .. msg)
|
|
else
|
|
failed = failed + 1
|
|
print("[FAIL] " .. msg)
|
|
end
|
|
end
|
|
|
|
local played = {}
|
|
package.loaded["src.core.game3.audio"] = {
|
|
playSe = function(id) played[#played + 1] = id end,
|
|
stopSe = function() end,
|
|
playCry = function() end,
|
|
playSong = function() end,
|
|
stopAll = function() end,
|
|
}
|
|
|
|
local RegionExtract = require("src.import.gba.region_map_extract")
|
|
local RegionMap = require("src.ui.game3.region_map")
|
|
local Position = require("src.ui.game3.region_map_position")
|
|
local ItemUse = require("src.core.game3.item_use")
|
|
local Bag = require("src.core.game3.bag")
|
|
local Dataset = require("src.core.game3.dataset")
|
|
local DEFS = Dataset.buildMaps()
|
|
require("src.core.game3.runtime")._game = { data = { maps = DEFS } }
|
|
|
|
local IDLE = { wasPressed = function() return false end, isDown = function() return false end }
|
|
local function frame(input) RegionMap.handleInput(input or IDLE) end
|
|
local function settle()
|
|
for _ = 1, 400 do
|
|
if RegionMap.inputReady() then return true end
|
|
frame()
|
|
end
|
|
return false
|
|
end
|
|
local function press(btn, frames)
|
|
frame({ wasPressed = function(_, k) return k == btn end, isDown = function(_, k) return k == btn end })
|
|
for _ = 1, frames or 5 do frame() end
|
|
end
|
|
local function runUntil(cond)
|
|
for _ = 1, 400 do
|
|
if cond() then return true end
|
|
frame()
|
|
end
|
|
return cond()
|
|
end
|
|
|
|
local function cell(session)
|
|
RegionExtract.ensureGenerated()
|
|
session.def = function(id) return assert(DEFS[id], id) end
|
|
return Position.playerCell(session, RegionExtract.GEOMETRY)
|
|
end
|
|
|
|
print("=== [TEST 1] GetPlayerPositionOnRegionMap ===")
|
|
do
|
|
-- src/region_map.c:3096
|
|
local x, y = cell({ map = "FR_PALLET_TOWN", x = 10, y = 8 })
|
|
check(x == 4 and y == 11, "Pallet Town resolves to (4, 11)")
|
|
x, y = cell({ map = "FR_VIRIDIAN_CITY", x = 20, y = 20 })
|
|
check(x == 4 and y == 8, "Viridian City resolves to (4, 8)")
|
|
x, y = cell({ map = "FR_PEWTER_CITY", x = 20, y = 20 })
|
|
check(x == 4 and y == 4, "Pewter City resolves to (4, 4)")
|
|
x, y = cell({ map = "FR_CERULEAN_CITY", x = 20, y = 20 })
|
|
check(x == 14 and y == 3, "Cerulean City resolves to (14, 3)")
|
|
-- src/region_map.c:3138
|
|
x, y = cell({ map = "FR_PLAYERS_HOUSE_1F", x = 3, y = 3, escapeWarp = { map = "FR_PALLET_TOWN", x = 6, y = 8 } })
|
|
check(x == 4 and y == 11, "the player's house resolves through the escape warp to Pallet Town")
|
|
-- src/region_map.c:3271
|
|
x, y = cell({ map = "FR_VIRIDIAN_FOREST", x = 30, y = 30 })
|
|
check(x == 4 and y == 6, "Viridian Forest uses its override (4, 6)")
|
|
-- src/region_map.c:3291-3301
|
|
x, y = cell({ map = "FR_ROUTE_21_NORTH", x = 5, y = 5 })
|
|
check(x == 4 and y == 12, "Route 21 North uses its override (4, 12)")
|
|
-- src/region_map.c:3158-3171
|
|
local tx, ty = cell({ map = "FR_ROUTE_1", x = 10, y = 0 })
|
|
local bx, by = cell({ map = "FR_ROUTE_1", x = 10, y = 39 })
|
|
check(tx == bx and ty < by, "Route 1 scales the player's y into its section")
|
|
local secTop = RegionExtract.LAYOUTS[0].map[ty][tx]
|
|
local secBottom = RegionExtract.LAYOUTS[0].map[by][bx]
|
|
check(secTop == secBottom and RegionExtract.KANTO_GRID[ty][tx] == "MAPSEC_ROUTE_1", "both ends stay on ROUTE 1")
|
|
-- src/region_map.c:3121
|
|
x, y = cell({ map = "FR_MT_MOON_1F", x = 10, y = 10, escapeWarp = { map = "FR_ROUTE_4", x = 19, y = 5 } })
|
|
check(RegionExtract.KANTO_GRID[y][x] == "MAPSEC_ROUTE_4", "Mt. Moon places the player on ROUTE 4 through the escape warp")
|
|
-- src/region_map.c:1030-1049
|
|
check(Position.regionFor(DEFS.FR_FOUR_ISLAND.regionMapSectionId, RegionExtract.LAYOUTS) == 2,
|
|
"Four Island belongs to the SEVII 4-5 map")
|
|
check(Position.regionFor(DEFS.SEVII_ONE_ISLAND.regionMapSectionId, RegionExtract.LAYOUTS) == 1,
|
|
"One Island belongs to the SEVII 1-2-3 map")
|
|
check(Position.regionFor(DEFS.FR_SEVEN_ISLAND.regionMapSectionId, RegionExtract.LAYOUTS) == 3,
|
|
"Seven Island belongs to the SEVII 6-7 map")
|
|
check(Position.regionFor(DEFS.FR_PALLET_TOWN.regionMapSectionId, RegionExtract.LAYOUTS) == 0,
|
|
"Pallet Town belongs to Kanto")
|
|
end
|
|
|
|
print("=== [TEST 2] RegionMap Interactive Cursor Navigation & Landmark Updates ===")
|
|
do
|
|
local closed = false
|
|
local session = { map = "FR_PALLET_TOWN", x = 10, y = 8, gender = 0 }
|
|
RegionMap.show({
|
|
session = session,
|
|
onClose = function() closed = true end,
|
|
})
|
|
|
|
check(RegionMap.isOpen() == true, "RegionMap is open")
|
|
check(settle(), "the open animation reaches the input state")
|
|
check(RegionMap.playerX == 4 and RegionMap.playerY == 11, "player location initialized at Pallet Town (4, 11)")
|
|
check(RegionMap.cursorX == 4 and RegionMap.cursorY == 11, "cursor location initialized at (4, 11)")
|
|
check(RegionMap.currentLocationName() == "PALLET TOWN", "initial landmark name is PALLET TOWN")
|
|
|
|
-- src/region_map.c:2754 HandleRegionMapInput, :2836 MoveMapCursor
|
|
frame({ wasPressed = function() return false end, isDown = function(_, k) return k == "up" end })
|
|
check(RegionMap.cursorY == 11, "a held direction slides the cursor before it moves a cell")
|
|
for _ = 1, 3 do frame() end
|
|
check(RegionMap.cursorY == 11, "the slide takes four frames")
|
|
played = {}
|
|
frame()
|
|
check(RegionMap.cursorX == 4 and RegionMap.cursorY == 10, "cursor moved UP to (4, 10)")
|
|
check(RegionMap.currentLocationName() == "ROUTE 1", "landmark name updated to ROUTE 1")
|
|
check(#played == 0, "a route plays no scroll sound")
|
|
|
|
press("up")
|
|
played = {}
|
|
press("up")
|
|
check(RegionMap.cursorX == 4 and RegionMap.cursorY == 8, "cursor moved UP to (4, 8)")
|
|
check(RegionMap.currentLocationName() == "VIRIDIAN CITY", "landmark name updated to VIRIDIAN CITY")
|
|
-- src/region_map.c:1174
|
|
check(played[1] == 101, "a town plays SE_DEX_SCROLL")
|
|
|
|
frame({ wasPressed = function(_, k) return k == "up" or k == "right" end,
|
|
isDown = function(_, k) return k == "up" or k == "right" end })
|
|
for _ = 1, 4 do frame() end
|
|
check(RegionMap.cursorX == 5 and RegionMap.cursorY == 7, "a diagonal hold moves both axes at once")
|
|
|
|
check(RegionMap.state().palTinted == true, "the open map draws the 95% tinted bank 2")
|
|
frame({ wasPressed = function(_, k) return k == "b" end, isDown = function(_, k) return k == "b" end })
|
|
local tintOk, sawClose, sawUntinted = true, false, false
|
|
for _ = 1, 400 do
|
|
local s = RegionMap.state()
|
|
if not s then break end
|
|
if s.task == "mapCloseAnim" then
|
|
sawClose = true
|
|
local cs = s.anim.closeState
|
|
if cs <= 2 and s.palTinted ~= true then tintOk = false end
|
|
if cs >= 3 then
|
|
if s.palTinted ~= false then tintOk = false end
|
|
sawUntinted = true
|
|
end
|
|
elseif sawClose and s.palTinted ~= false then
|
|
tintOk = false
|
|
end
|
|
frame()
|
|
end
|
|
-- src/region_map.c:2572
|
|
check(sawClose and sawUntinted and tintOk, "close state 2 reloads sRegionMap_Pal untinted and it stays untinted")
|
|
check(not RegionMap.isOpen(), "B runs the close animation and fade")
|
|
check(closed == true, "onClose callback executed")
|
|
RegionMap.show({ session = session })
|
|
check(RegionMap.state().palTinted == true, "the next show draws tinted again")
|
|
RegionMap.close()
|
|
end
|
|
|
|
print("=== [TEST 3] Inventory TOWN_MAP Item Use Integration ===")
|
|
do
|
|
local bag = Bag.new()
|
|
Bag.add(bag, 361, 1) -- Town Map
|
|
local session = { map = "FR_CELADON_CITY", x = 30, y = 20, bag = bag }
|
|
|
|
local ok, reason = ItemUse.useField(session, bag, 361, nil)
|
|
check(ok == true, "ItemUse.useField accepts TOWN_MAP (item 361)")
|
|
check(reason == "map", "useField returns 'map' reason")
|
|
check(RegionMap.isOpen() == true, "RegionMap opened via ItemUse")
|
|
settle()
|
|
check(RegionMap.playerX == 11 and RegionMap.playerY == 6, "player position placed at Celadon City (11, 6)")
|
|
check(RegionMap.currentLocationName() == "CELADON CITY", "Celadon City landmark active")
|
|
-- src/region_map.c:2819
|
|
check(RegionMap.state().fromField == true, "no BAG is open, so this is the field Town Map")
|
|
press("select")
|
|
check(runUntil(function() return not RegionMap.isOpen() end), "SELECT closes the field Town Map")
|
|
end
|
|
|
|
print("=== [TEST 4] Start Snapping & Dungeon Guide Modal ===")
|
|
do
|
|
local session = { map = "FR_VIRIDIAN_CITY", x = 20, y = 20, gender = 0 }
|
|
RegionMap.show({ session = session })
|
|
settle()
|
|
|
|
check(RegionMap.cursorX == 4 and RegionMap.cursorY == 8, "cursor at Viridian City (4, 8)")
|
|
|
|
press("start")
|
|
check(RegionMap.cursorX == 21 and RegionMap.cursorY == 13, "START snapped to Cancel button (21, 13)")
|
|
local _, right = RegionMap.topBarText()
|
|
check(right == "gText_RegionMap_AButtonCancel", "the top bar offers A CANCEL")
|
|
|
|
press("start")
|
|
check(RegionMap.cursorX == 4 and RegionMap.cursorY == 8, "START snapped back to Player Icon (4, 8)")
|
|
|
|
press("up")
|
|
press("up")
|
|
check(RegionMap.cursorX == 4 and RegionMap.cursorY == 6, "cursor at Viridian Forest (4, 6)")
|
|
check(RegionMap.currentDungeonName() == "VIRIDIAN FOREST", "current dungeon is VIRIDIAN FOREST")
|
|
check(RegionMap.state().text.dungeonType == RegionMap.MAPSECTYPE.NOT_VISITED,
|
|
"the unvisited dungeon name uses the red text colour")
|
|
|
|
press("a")
|
|
check(RegionMap.previewDungeon == nil, "A on an unvisited dungeon opens no preview")
|
|
check(RegionMap.isOpen() == true, "RegionMap stays open after the refused GUIDE")
|
|
check(RegionMap.canGuideCursor() == false, "GUIDE is refused on a dungeon that has not been visited")
|
|
|
|
-- pokefirered/data/maps/ViridianForest/scripts.inc:6
|
|
session.flags = session.flags or {}
|
|
session.flags["FLAG_WORLD_MAP_VIRIDIAN_FOREST"] = true
|
|
check(RegionMap.canGuideCursor() == true, "GUIDE is offered on a visited dungeon")
|
|
|
|
played = {}
|
|
press("a", 0)
|
|
check(RegionMap.previewDungeon == "MAPSEC_VIRIDIAN_FOREST", "Dungeon Preview Modal opened for Viridian Forest")
|
|
-- src/region_map.c:2021-2030
|
|
press("b", 20)
|
|
check(RegionMap.previewDungeon == "MAPSEC_VIRIDIAN_FOREST", "B is ignored until the flavour text is printed")
|
|
check(runUntil(function() local s = RegionMap.state() return s.preview and s.preview.text ~= nil end),
|
|
"the GUIDE prints its text")
|
|
press("b")
|
|
check(runUntil(function() return RegionMap.previewDungeon == nil end), "B shrinks and closes the GUIDE")
|
|
check(#played == 0, "the GUIDE plays no sound effects")
|
|
check(RegionMap.isOpen() == true, "RegionMap still open after closing modal")
|
|
settle()
|
|
|
|
press("b")
|
|
runUntil(function() return not RegionMap.isOpen() end)
|
|
check(RegionMap.isOpen() == false, "RegionMap closed on B")
|
|
end
|
|
|
|
print("=== [TEST 5] Wall Town Map Metatile & Script Execution ===")
|
|
do
|
|
local Interaction = require("src.core.game3.scripting.interaction_scripts")
|
|
local CollisionStd = require("src.core.game3.scripting.collision_std")
|
|
|
|
local scriptKey = Interaction.scriptFor(0x85, "up")
|
|
check(scriptKey == "EventScript_WallTownMap", "Behavior 0x85 (MB_TOWN_MAP) maps to EventScript_WallTownMap")
|
|
|
|
local collScript = CollisionStd.scriptFor(0x95)
|
|
check(collScript == "EventScript_WallTownMap", "COLL_TOWN_MAP (0x95) maps to EventScript_WallTownMap")
|
|
|
|
local Adapters = require("src.core.game3.scripting.adapters")
|
|
local session = { map = "FR_VIRIDIAN_CITY", x = 20, y = 20 }
|
|
package.loaded["src.core.game3.runtime"].getSession = function() return session end
|
|
local hostAdapters = Adapters.host(nil, { session = session }, nil)
|
|
local mapOpened = false
|
|
hostAdapters.showTownMap(function()
|
|
mapOpened = true
|
|
end)
|
|
check(RegionMap.isOpen() == true, "adapters.showTownMap opens RegionMap")
|
|
RegionMap.close()
|
|
check(RegionMap.isOpen() == false, "RegionMap closed cleanly")
|
|
check(mapOpened == true, "showTownMap callback was executed")
|
|
end
|
|
|
|
print("=== [TEST 6] The Sevii maps (#2430) ===")
|
|
do
|
|
RegionMap.show({ session = { map = "FR_FOUR_ISLAND", x = 12, y = 14 } })
|
|
settle()
|
|
check(RegionMap.state().selectedRegion == 2, "the Town Map on Four Island shows SEVII 4-5")
|
|
check(RegionMap.currentLocationName() == "FOUR ISLAND", "and the cursor starts on FOUR ISLAND")
|
|
RegionMap.close()
|
|
for _, m in ipairs({ "FR_FOUR_ISLAND_POKEMON_CENTER_1F", "FR_FOUR_ISLAND_ICEFALL_CAVE_ENTRANCE", "FR_FOUR_ISLAND_ICEFALL_CAVE_BACK" }) do
|
|
RegionMap.show({ session = { map = m, x = 5, y = 5 } })
|
|
settle()
|
|
check(RegionMap.state().selectedRegion == 2, m .. " opens SEVII 4-5")
|
|
RegionMap.close()
|
|
end
|
|
end
|
|
|
|
if failed > 0 then
|
|
print(string.format("\n[FAILED] %d test(s) failed", failed))
|
|
os.exit(1)
|
|
else
|
|
print("\nALL TOWN MAP & REGION MAP TESTS PASSED CLEANLY!")
|
|
end
|