Stop offering the route Pokemon Centers as fly destinations

This commit is contained in:
johnjohto
2026-08-04 12:28:08 -04:00
parent 1820f411ae
commit 6fe106f55c
6 changed files with 114 additions and 14 deletions
+4 -5
View File
@@ -12,12 +12,11 @@ function FlyMenu.new(game)
local seen = {}
for _, mapId in ipairs(game.data.field.flyOrder or {}) do
-- towns only (dungeon escape spots share the table), each listed once.
-- Indigo Plateau (tileset PLATEAU) is a valid Fly destination too, so allow
-- it past the OVERWORLD-only isOutdoor gate while the CAVERN/FACILITY escape
-- spots stay excluded (LoadTownMap_Fly cycles it like any town, #203).
-- Map.isFlyTown is the BuildFlyLocationsList gate: map ids 0..10, so
-- INDIGO_PLATEAU cycles like any town (#203) while the ROUTE_4/ROUTE_10
-- Pokemon Centers, fly warps but not towns, stay out (#788).
local def = game.data.maps[mapId]
if visited[mapId] and def and not seen[mapId]
and (Map.isOutdoor(def) or def.tileset == "PLATEAU") then
if visited[mapId] and def and not seen[mapId] and Map.isFlyTown(def) then
seen[mapId] = true
table.insert(items, {
value = mapId,
+7 -6
View File
@@ -147,13 +147,14 @@ local function buildFlyList(game, byMap)
for _, mapId in ipairs(field.flyOrder or {}) do
local def = game.data.maps and game.data.maps[mapId]
-- INDIGO_PLATEAU is a normal Fly spot (engine/menus/town_map.asm
-- LoadTownMap_Fly cycles it like any town), but its map uses tileset
-- "PLATEAU" not OVERWORLD, so Map.isOutdoor() alone dropped it from the
-- cursor even though it is visited and has a fly warp. Allow PLATEAU here
-- while the CAVERN/FACILITY dungeon escape spots that share flyOrder still
-- fail the gate and stay out (#203).
-- LoadTownMap_Fly cycles it like any town): its map id sits inside
-- BuildFlyLocationsList's 0..NUM_CITY_MAPS-1 walk, which is what
-- Map.isFlyTown checks, so it passes even though its tileset is
-- "PLATEAU" not OVERWORLD (#203). The ROUTE_4/ROUTE_10 Pokemon Centers
-- carry fly warps but are not towns, so they stay out (#788), as do the
-- CAVERN/FACILITY dungeon escape spots that share flyOrder.
if not seen[mapId] and visited[mapId] and flyWarps[mapId]
and def and (Map.isOutdoor(def) or def.tileset == "PLATEAU") then
and def and Map.isFlyTown(def) then
seen[mapId] = true
local loc = byMap[mapId] or { name = mapId:gsub("_", " ") }
table.insert(flyLocs, loc)
+18
View File
@@ -24,6 +24,13 @@ local NO_SHORE_TILESETS = { SHIP_PORT = true }
-- what counts as "outside" for the wLastMap memory (CheckIfInOutsideMap)
local OUTSIDE_TILESETS = { "OVERWORLD", "PLATEAU" }
-- pokered's fly destination gate: BuildFlyLocationsList
-- (engine/items/town_map.asm) walks map ids 0..NUM_CITY_MAPS-1, the eleven
-- towns PALLET_TOWN..SAFFRON_CITY, so routes never appear even though
-- ROUTE_4/ROUTE_10 carry fly-warp landing spots (those exist for the
-- dungeon-escape/heal tables, special_warps.asm FlyWarpDataPtr)
local NUM_CITY_MAPS = 11
-- warp pads and fall-through holes (data/tilesets/warp_pad_hole_tile_ids
-- .asm WarpPadAndHoleData); a tileset record carrying warpPadTiles
-- ({ [tileId] = "pad"|"hole" }) wins over these vanilla rows
@@ -153,6 +160,17 @@ function Map.isOutside(def, tilesets)
return false
end
-- FLY destination (LoadTownMap_Fly / BuildFlyLocationsList): the eleven
-- towns, map indices 0..NUM_CITY_MAPS-1. ROUTE_4 and ROUTE_10 are outdoor
-- and have fly warps but are not towns, so the outdoor test alone offered
-- their Pokemon Centers as fly targets (#788). Maps without a vanilla
-- index (mod-authored) keep the old outdoor/PLATEAU surface test, which is
-- how a mod adds its own fly town.
function Map.isFlyTown(def)
if def.index ~= nil then return def.index < NUM_CITY_MAPS end
return Map.isOutdoor(def) or def.tileset == "PLATEAU"
end
-- region groups maps a rule applies to without naming them; the id prefix
-- is the fallback for caches that predate the property
function Map.inRegion(def, region, prefix)
+6 -1
View File
@@ -364,7 +364,12 @@ function OverworldState:setMap(mapId, x, y, facing, opts)
Game.save.flashLit = nil
self:setDark(false)
end
if Game.data.field.flyWarps[mapId] then
-- MarkTownVisitedAndLoadToggleableObjects marks towns only (cp
-- FIRST_ROUTE_MAP): the fly-warp table also carries the ROUTE_4/ROUTE_10
-- Pokemon Centers and the dungeon escape spots, and entering those never
-- sets a wTownVisitedFlag bit, so it must not set save.visited either (#788)
local mapDef = Game.data.maps[mapId]
if Game.data.field.flyWarps[mapId] and mapDef and Map.isFlyTown(mapDef) then
Game.save.visited = Game.save.visited or {}
Game.save.visited[mapId] = true
end
+2 -2
View File
@@ -29,8 +29,8 @@ return function(game)
}
-- Exercise the real visited-marking path (OverworldController marks any
-- flyWarps map visited on entry) by standing on the Plateau exterior first,
-- then hop back to Pallet for a clean starting point.
-- flyWarps TOWN visited on entry, Map.isFlyTown) by standing on the Plateau
-- exterior first, then hop back to Pallet for a clean starting point.
U.teleport(game, "INDIGO_PLATEAU", 9, 6, "down")
U.wait(5)
assert(game.save.visited.INDIGO_PLATEAU,
+77
View File
@@ -0,0 +1,77 @@
-- Parity test: the Route 4 / Route 10 Pokemon Centers are not FLY
-- destinations (#788).
--
-- pokered keeps fly-warp landing spots for ROUTE_4 and ROUTE_10 in
-- data/maps/special_warps.asm FlyWarpDataPtr, but the fly picker never
-- offers them: BuildFlyLocationsList (engine/items/town_map.asm) walks map
-- ids 0..NUM_CITY_MAPS-1 only, and MarkTownVisitedAndLoadToggleableObjects
-- (engine/overworld/toggleable_objects.asm) sets a wTownVisitedFlag bit
-- only for maps below FIRST_ROUTE_MAP. The port walked the whole fly-warp
-- table and gated on "outdoor", so both route centers showed up as fly
-- targets the moment the player had set foot on those routes.
package.path = "./?.lua;./?/init.lua;" .. package.path
if not _G.love then _G.love = require("tests.love_stub") end
local Data = require("src.core.Data")
if not (Data.maps and Data.maps.PALLET_TOWN) then Data:load() end
local S = require("tests.harness").suite("parity fly route centers")
local check, eq = S.check, S.eq
local Map = require("src.world.Map")
local FlyMenu = require("src.ui.FlyMenu")
local TownMap = require("src.ui.TownMap")
local TOWNS = {
"PALLET_TOWN", "VIRIDIAN_CITY", "PEWTER_CITY", "CERULEAN_CITY",
"LAVENDER_TOWN", "VERMILION_CITY", "CELADON_CITY", "FUCHSIA_CITY",
"CINNABAR_ISLAND", "INDIGO_PLATEAU", "SAFFRON_CITY",
}
-- ------------------------------------------------------------------
-- 1) the gate itself, straight off generated map records
-- ------------------------------------------------------------------
-- map indices 0..10 are exactly the eleven towns (data/generated/maps.lua
-- carries pokered's map constant order), and Map.isFlyTown keys off them
check(Map.isFlyTown(Data.maps.PALLET_TOWN), "PALLET_TOWN is a fly town")
check(Map.isFlyTown(Data.maps.INDIGO_PLATEAU),
"INDIGO_PLATEAU is a fly town (index 9, past the PLATEAU tileset)")
check(not Map.isFlyTown(Data.maps.ROUTE_4), "ROUTE_4 is not a fly town")
check(not Map.isFlyTown(Data.maps.ROUTE_10), "ROUTE_10 is not a fly town")
check(not Map.isFlyTown(Data.maps.POKEMON_MANSION_1F),
"POKEMON_MANSION_1F (dungeon escape spot) is not a fly town")
-- a mod-authored town has no vanilla index and keeps the outdoor test
check(Map.isFlyTown({ tileset = "OVERWORLD" }),
"an index-less outdoor map (mod town) is a fly town")
check(not Map.isFlyTown({ tileset = "CAVERN" }),
"an index-less cave map is not a fly town")
-- ------------------------------------------------------------------
-- 2) both pickers exclude the route centers, even on a polluted save
-- ------------------------------------------------------------------
-- Saves written before this fix already carry visited.ROUTE_4 /
-- visited.ROUTE_10 (any map with a fly warp was marked on entry), so the
-- menu side has to filter, not just the writer.
local visited = { ROUTE_4 = true, ROUTE_10 = true }
for _, id in ipairs(TOWNS) do visited[id] = true end
local save = { visited = visited }
local menu = FlyMenu.new({ data = Data, save = save })
local menuIds = {}
for _, item in ipairs(menu.items or {}) do menuIds[#menuIds + 1] = item.value end
eq(#menuIds, #TOWNS, "FLY lists exactly the eleven towns")
for i, id in ipairs(TOWNS) do
eq(menuIds[i], id, ("FLY entry %d is %s"):format(i, id))
end
local tm = TownMap.new({ data = Data, save = save }, { fly = true })
check(tm.fly == true, "the town map opens in fly mode")
local listed = {}
for _, id in ipairs(tm.flyMapIds or {}) do listed[id] = true end
eq(#tm.flyMapIds, #TOWNS, "the fly town map cycles exactly the eleven towns")
check(not listed.ROUTE_4, "ROUTE_4 is not on the fly town map")
check(not listed.ROUTE_10, "ROUTE_10 is not on the fly town map")
for _, id in ipairs(TOWNS) do
check(listed[id], id .. " is on the fly town map")
end
S.finish()