mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 07:41:21 +02:00
163 lines
6.7 KiB
Lua
163 lines
6.7 KiB
Lua
-- Where does this species live? engine/overworld/wildmons.asm FindNest, which
|
|
-- is the data behind the Pokedex's AREA page and the Pokegear MAP card's
|
|
-- "<MON>'S NEST" overlay.
|
|
--
|
|
-- FindNest takes a species and a region (e: 0 Johto, 1 Kanto) and fills the
|
|
-- tilemap with LANDMARK indices -- one per map whose wild data contains the
|
|
-- species. It reads exactly three sources, in this order:
|
|
--
|
|
-- .FindGrass JohtoGrassWildMons / KantoGrassWildMons, all NUM_GRASSMON * 3
|
|
-- slots, so morning, day AND night count
|
|
-- .FindWater JohtoWaterWildMons / KantoWaterWildMons
|
|
-- .RoamMon1/2/3 the three roamers' CURRENT map, Johto only
|
|
--
|
|
-- and nothing else. Headbutt trees, fishing groups, the Bug Contest and swarms
|
|
-- are all absent from it, so a HEADBUTT-only species legitimately has no nest
|
|
-- and the page stays blank -- that is the cart's answer, not a gap.
|
|
--
|
|
-- Region is decided here by LANDMARK INDEX rather than by which of the two
|
|
-- tables a map came from: the extractor emits one `grass`/`water` table keyed
|
|
-- by map, and Johto's landmarks are the run below LANDMARK_PALLET_TOWN with
|
|
-- Kanto's above it (constants/landmark_constants.asm). Same split, different
|
|
-- spelling.
|
|
local Nests = {}
|
|
|
|
-- constants/landmark_constants.asm. LANDMARK_SPECIAL is 0 and never a nest.
|
|
Nests.LANDMARK_PALLET_TOWN = 0x2e
|
|
Nests.LANDMARK_FAST_SHIP = 0x5e
|
|
|
|
function Nests.regionOf(landmark)
|
|
if not landmark or landmark <= 0 then return nil end
|
|
if landmark >= Nests.LANDMARK_FAST_SHIP then return nil end
|
|
return (landmark < Nests.LANDMARK_PALLET_TOWN) and "johto" or "kanto"
|
|
end
|
|
|
|
-- ---------------------------------------------------------- the landmarks
|
|
--
|
|
-- data/maps/landmarks.asm, which on Gold is one index space shared by every
|
|
-- map header's `landmark` byte, the Pokegear MAP card and the #DEX AREA page
|
|
-- this file feeds. It is the `landmarks` registry (src/mods/Schemas.lua), one
|
|
-- of the Gen 2-only six: Red's town map is a different table with a different
|
|
-- id space, so the name is gated under Gen 1 and routed to
|
|
-- gen2Landmarks.landmarks under Gen 2 -- straight onto the cache's own table,
|
|
-- which means the merge lands in the very table the map card draws from and no
|
|
-- Builtins seeding is needed (the same arrangement gen2Maps has).
|
|
--
|
|
-- Two reads go through here rather than through landmarks.order, which is a
|
|
-- plain ordered list the extractor writes and a registered landmark is
|
|
-- therefore absent from: src/core/Game2.lua:currentLandmark and
|
|
-- src/ui/gen2/MapRadio.lua's region test. `index` on a record is the byte the
|
|
-- map header carries, so the lookup is by that and the order list stays the
|
|
-- fallback for a dataset whose records predate it.
|
|
|
|
-- memoized per landmark table (weak keys, so a second dataset in one process
|
|
-- does not pin the first); built on first read, which is after the merge --
|
|
-- nothing asks for a landmark before the overworld exists
|
|
local byIndex = setmetatable({}, { __mode = "k" })
|
|
|
|
-- Two records may claim one index -- a mod that registers a landmark at a byte
|
|
-- the cart already uses -- and pairs() would decide which one answers per
|
|
-- process. The cache's own row wins its own slot (landmarks.order is that
|
|
-- list), and between two newcomers the lower id wins, so the answer is the
|
|
-- same on every boot. A mod that means to MOVE a vanilla landmark patches
|
|
-- that record rather than shadowing its index.
|
|
local function indexTable(landmarks)
|
|
local hit = byIndex[landmarks]
|
|
if hit then return hit end
|
|
local map, order = {}, landmarks.order or {}
|
|
for id, record in pairs(landmarks.landmarks or {}) do
|
|
local index = type(record) == "table" and record.index
|
|
if index then
|
|
local held = map[index]
|
|
if held == nil or order[index + 1] == id
|
|
or (order[index + 1] ~= held and id < held) then
|
|
map[index] = id
|
|
end
|
|
end
|
|
end
|
|
byIndex[landmarks] = map
|
|
return map
|
|
end
|
|
|
|
-- The LANDMARK_* id at a map header's landmark byte, or nil.
|
|
function Nests.landmarkId(data, index)
|
|
local landmarks = data and data.gen2Landmarks
|
|
if not (landmarks and index) then return nil end
|
|
local hit = indexTable(landmarks)[index]
|
|
if hit then return hit end
|
|
return landmarks.order and landmarks.order[index + 1] or nil
|
|
end
|
|
|
|
-- The record behind that byte: the two-line name and the map-card position.
|
|
function Nests.landmark(data, index)
|
|
local landmarks = data and data.gen2Landmarks
|
|
local id = Nests.landmarkId(data, index)
|
|
return id and landmarks.landmarks and landmarks.landmarks[id] or nil
|
|
end
|
|
|
|
local function landmarkOfMap(data, mapId)
|
|
local def = data and data.gen2Maps and data.gen2Maps[mapId]
|
|
return def and def.landmark
|
|
end
|
|
|
|
-- Every slot of one encounter table entry, across all times of day: the cart
|
|
-- walks `NUM_GRASSMON * 3` bytes without caring which third it is in.
|
|
local function tableHasSpecies(entry, species)
|
|
if type(entry) ~= "table" then return false end
|
|
local slots = entry.slots
|
|
if type(slots) ~= "table" then return false end
|
|
for _, list in pairs(slots) do
|
|
if type(list) == "table" then
|
|
for _, slot in ipairs(list) do
|
|
if slot and slot.species == species then return true end
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- The landmark indices where `species` can be met, in ascending order.
|
|
--
|
|
-- `region` is "johto" or "kanto"; nil means both, which no cart screen asks
|
|
-- for but is the useful answer for a test.
|
|
function Nests.find(data, species, region, save)
|
|
local out, seen = {}, {}
|
|
local function add(landmark)
|
|
if not landmark or landmark <= 0 or seen[landmark] then return end
|
|
local where = Nests.regionOf(landmark)
|
|
if not where then return end
|
|
if region and where ~= region then return end
|
|
seen[landmark] = true
|
|
out[#out + 1] = landmark
|
|
end
|
|
|
|
local enc = data and data.gen2Encounters
|
|
for _, key in ipairs({ "grass", "water" }) do
|
|
for mapId, entry in pairs((enc and enc[key]) or {}) do
|
|
if tableHasSpecies(entry, species) then
|
|
add(landmarkOfMap(data, mapId))
|
|
end
|
|
end
|
|
end
|
|
|
|
-- .RoamMon1/2/3: the roamer's CURRENT map, and only while it is still out
|
|
-- there -- a caught or defeated one keeps its slot but loses its species and
|
|
-- map. Johto-only on the cart, which the region filter above enforces.
|
|
--
|
|
-- The test is Roamers.active, NOT "has HP": a roamer starts life at hp 0
|
|
-- (`xor a ; generate new stats`) and only gets a real value once you have met
|
|
-- it, so an HP test would hide all three until first contact -- exactly the
|
|
-- ones the page is most useful for.
|
|
local Roamers = require("src.core.gen2.Roamers")
|
|
for _, slot in ipairs((save and save.roamers) or {}) do
|
|
if Roamers.active(slot) and slot.species == species then
|
|
add(landmarkOfMap(data, slot.map))
|
|
end
|
|
end
|
|
|
|
table.sort(out)
|
|
return out
|
|
end
|
|
|
|
return Nests
|