mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 15:51:17 +02:00
15029d811f
# Closed issues CLOSES #17: Incorrect Character Visuals (Only with GBC filter) CLOSES #23: Could you allow the player to change the order of the moves CLOSES #24: Evolution music not playing during evolution CLOSES #26: Standing on door glitch CLOSES #27: Battle Intro text automatically continues CLOSES #29: Visual bug when zoomed out CLOSES #32: Team Rocket recruiter doesn't battle with you unless you speak with him first CLOSES #33: Developer/Debug Console CLOSES #35: Professor Oak's introduction Inaccuracies CLOSES #36: Missing Pokemon Dex entries when picking starter + Rival Pathing issues CLOSES #39: Guy who stops player from skipping brock doesn't bring you to brock's gym + doesn't leave once you've beaten brock CLOSES #40: Bill cutscene is broken CLOSES #41: Ticket guy failing to be a Ticket guy CLOSES #42: S.S. anne odd behavior + Missing sailing away animation CLOSES #43: Dig Attack animation appears to be glitched CLOSES #44: Pokeball flashing doesn't appear to be accurate CLOSES #45: Inaccurate Cut Animation CLOSES #46: Dugtrio i caught in diglett cave has two of the same move CLOSES #47: Rival ignores player in Lavender Tower CLOSES #48: Healing pad in lavender tower does not function CLOSES #49: Incorrect dialogue with parched security guard CLOSES #50: (Game Breaking!) Rocket grunt guarding poster refuses to move CLOSES #51: Badges showing up as items I can deposit in PC CLOSES #52: Visual bug on Celadon Department Store roof (Red Filter) CLOSES #54: Visual issue on route 15 + Fuchsia City CLOSES #56: Running animation missing CLOSES #57: Safari Zone does not display steps while you are inside of it CLOSES #58: (Game Breaking!) Softlock at cycling road gate CLOSES #59: Bike visual issues CLOSES #60: Cycling road not forcing you to get on your bike CLOSES #61: No keycard doors in Silph Co. CLOSES #63: Missing teleporter animation CLOSES #64: Inaccurate spinning CLOSES #65: Reimplement unused Silph Co. Chief and Professor Oak trainer battles
241 lines
8.3 KiB
Lua
241 lines
8.3 KiB
Lua
-- Runtime map built from generated data. All queries use "cells": the
|
|
-- 16x16 walk grid (2x2 tiles). A map is width x height blocks; each block
|
|
-- is 2x2 cells (4x4 tiles).
|
|
--
|
|
-- Collision follows the original engine: a cell is passable when the
|
|
-- BOTTOM-LEFT 8x8 tile of the cell is in the tileset's walkable list
|
|
-- (pokered checks the tile at the sprite's feet). Doors, warp tiles and
|
|
-- grass use the same convention.
|
|
|
|
local Map = {}
|
|
Map.__index = Map
|
|
|
|
-- Stale-cache fallbacks for the tileset properties the importer does not
|
|
-- stamp yet (item_effects.asm IsNextTileShoreOrWater, home/overworld.asm
|
|
-- CollisionCheckOnWater): $14 is water everywhere; the shore tiles $32 and
|
|
-- $48 (Safari Zone) everywhere EXCEPT SHIP_PORT, where $32 is the dock's
|
|
-- boarding platform -- a land tile. A tileset record that carries
|
|
-- waterTiles/shoreTiles wins outright, which is how a new tileset gets
|
|
-- surfable water without naming Kanto's.
|
|
local WATER_TILES = { 0x14 }
|
|
local SHORE_TILES = { 0x32, 0x48 }
|
|
local NO_SHORE_TILESETS = { SHIP_PORT = true }
|
|
|
|
-- what counts as "outside" for the wLastMap memory (CheckIfInOutsideMap)
|
|
local OUTSIDE_TILESETS = { "OVERWORLD", "PLATEAU" }
|
|
|
|
-- 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
|
|
local WARP_PAD_TILES = {
|
|
FACILITY = { [0x20] = "pad", [0x11] = "hole" },
|
|
CAVERN = { [0x22] = "hole" },
|
|
INTERIOR = { [0x55] = "pad" },
|
|
}
|
|
|
|
local function hashSet(list, into)
|
|
for _, t in ipairs(list) do into[t] = true end
|
|
return into
|
|
end
|
|
|
|
-- Passability of a cell of an UNLOADED map def -- the connected neighbor
|
|
-- during an edge crossing. pokered's collision check reads the neighbor
|
|
-- strip's tile bytes, so a step off the map edge onto a solid tile of
|
|
-- the connected map bumps exactly like an in-map wall; the port needs
|
|
-- the same read without building the whole Map. Same math as cellTile
|
|
-- on the raw blocks, honoring the surf rule (water/shore passable only
|
|
-- while surfing, same fallbacks as Map.new).
|
|
function Map.defPassable(def, tilesetDef, cx, cy, surfing)
|
|
-- Fail closed: a missing tileset used to return true and re-open the
|
|
-- Pallet south-shore stranding (cross onto ROUTE_21 solids). No data
|
|
-- means we cannot prove the landing is safe, so the step bumps.
|
|
if not (def and tilesetDef and tilesetDef.blocks and tilesetDef.walkable) then
|
|
return false
|
|
end
|
|
local tx, ty = cx * 2, cy * 2 + 1
|
|
local bx, by = math.floor(tx / 4), math.floor(ty / 4)
|
|
local id
|
|
if bx < 0 or by < 0 or bx >= def.width or by >= def.height then
|
|
id = def.borderBlock
|
|
else
|
|
id = def.blocks[by * def.width + bx + 1]
|
|
end
|
|
local block = tilesetDef.blocks[(id or 0) + 1]
|
|
if not block then return false end
|
|
local tile = block[(ty % 4) * 4 + (tx % 4) + 1]
|
|
for _, t in ipairs(tilesetDef.walkable) do
|
|
if t == tile then return true end
|
|
end
|
|
if surfing then
|
|
for _, t in ipairs(tilesetDef.waterTiles or WATER_TILES) do
|
|
if t == tile then return true end
|
|
end
|
|
local shore = tilesetDef.shoreTiles
|
|
if shore == nil and not NO_SHORE_TILESETS[def.tileset] then
|
|
shore = SHORE_TILES
|
|
end
|
|
for _, t in ipairs(shore or {}) do
|
|
if t == tile then return true end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function Map.new(def, tilesetDef)
|
|
local self = setmetatable({}, Map)
|
|
self.def = def
|
|
self.tileset = tilesetDef
|
|
self.id = def.id
|
|
self.widthCells = def.width * 2
|
|
self.heightCells = def.height * 2
|
|
|
|
self.walkable = {}
|
|
for _, t in ipairs(tilesetDef.walkable) do self.walkable[t] = true end
|
|
self.doorTiles = {}
|
|
for _, t in ipairs(tilesetDef.doorTiles or {}) do self.doorTiles[t] = true end
|
|
self.warpTiles = {}
|
|
for _, t in ipairs(tilesetDef.warpTiles or {}) do self.warpTiles[t] = true end
|
|
-- water and shore share one lookup: both are surfable, only the caller's
|
|
-- water_tilesets.asm membership check separates them
|
|
self.waterTiles = hashSet(tilesetDef.waterTiles or WATER_TILES, {})
|
|
local shore = tilesetDef.shoreTiles
|
|
if shore == nil and not NO_SHORE_TILESETS[def.tileset] then shore = SHORE_TILES end
|
|
hashSet(shore or {}, self.waterTiles)
|
|
|
|
self.warpAt = {}
|
|
for i, w in ipairs(def.warps or {}) do
|
|
self.warpAt[w.y * self.widthCells + w.x] = { index = i, def = w }
|
|
end
|
|
self.signAt = {}
|
|
for _, s in ipairs(def.signs or {}) do
|
|
self.signAt[s.y * self.widthCells + s.x] = s
|
|
end
|
|
return self
|
|
end
|
|
|
|
-- ------- map record properties (authored maps set them; vanilla falls back)
|
|
|
|
-- town/route surface: door SFX, the walk-out step, the Fly menu and the
|
|
-- town map all mean this one
|
|
function Map.isOutdoor(def)
|
|
if def.outdoor ~= nil then return def.outdoor end
|
|
return def.tileset == "OVERWORLD"
|
|
end
|
|
|
|
-- CheckIfInOutsideMap, a strictly wider set: Route 23 / Indigo Plateau are
|
|
-- outside for the wLastMap memory without being outdoor for the door SFX
|
|
function Map.isOutside(def, tilesets)
|
|
if Map.isOutdoor(def) then return true end
|
|
for _, ts in ipairs(tilesets or OUTSIDE_TILESETS) do
|
|
if ts == def.tileset then return true end
|
|
end
|
|
return false
|
|
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)
|
|
if def.region ~= nil then return def.region == region end
|
|
return prefix ~= nil and def.id:find(prefix, 1, true) == 1
|
|
end
|
|
|
|
-- unidentifiable wild battles on this map unless the player holds an item
|
|
function Map.ghostBattles(def)
|
|
if def.ghostBattles ~= nil then return def.ghostBattles end
|
|
if def.id:find("POKEMON_TOWER", 1, true) == 1 then
|
|
return { unlessItem = "SILPH_SCOPE" }
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- strength-pushable map objects (engine/overworld/push_boulder.asm)
|
|
function Map.isPushable(objDef)
|
|
if objDef.pushable ~= nil then return objDef.pushable end
|
|
return objDef.sprite == "SPRITE_BOULDER"
|
|
end
|
|
|
|
function Map:blockAt(bx, by)
|
|
if bx < 0 or by < 0 or bx >= self.def.width or by >= self.def.height then
|
|
return self.def.borderBlock
|
|
end
|
|
return self.def.blocks[by * self.def.width + bx + 1]
|
|
end
|
|
|
|
-- tile id at tile coordinates (8px grid), border-extended
|
|
function Map:tileAt(tx, ty)
|
|
local bx, by = math.floor(tx / 4), math.floor(ty / 4)
|
|
local block = self.tileset.blocks[self:blockAt(bx, by) + 1]
|
|
local ix = (ty % 4) * 4 + (tx % 4) + 1
|
|
return block[ix]
|
|
end
|
|
|
|
-- the collision tile of a cell: bottom-left 8x8 tile
|
|
function Map:cellTile(cx, cy)
|
|
return self:tileAt(cx * 2, cy * 2 + 1)
|
|
end
|
|
|
|
function Map:inBounds(cx, cy)
|
|
return cx >= 0 and cy >= 0 and cx < self.widthCells and cy < self.heightCells
|
|
end
|
|
|
|
function Map:isWalkableCell(cx, cy)
|
|
return self.walkable[self:cellTile(cx, cy)] or false
|
|
end
|
|
|
|
function Map:isGrassCell(cx, cy)
|
|
local grass = self.tileset.grassTile
|
|
return grass ~= nil and self:cellTile(cx, cy) == grass
|
|
end
|
|
|
|
-- Water and eastern-shore tiles, from the tileset's waterTiles/shoreTiles
|
|
-- (hash sets built in Map.new). Tileset membership in water_tilesets.asm
|
|
-- is checked by the caller.
|
|
function Map:isWaterCell(cx, cy)
|
|
return self.waterTiles[self:cellTile(cx, cy)] or false
|
|
end
|
|
|
|
-- Replace a block (Cut trees); the caller rebuilds the renderer.
|
|
function Map:setBlock(bx, by, block)
|
|
if bx < 0 or by < 0 or bx >= self.def.width or by >= self.def.height then
|
|
return
|
|
end
|
|
self.def.blocks[by * self.def.width + bx + 1] = block
|
|
end
|
|
|
|
-- true if the cell's collision tile is a door or warp-activating tile
|
|
function Map:isWarpTileCell(cx, cy)
|
|
local t = self:cellTile(cx, cy)
|
|
return self.doorTiles[t] or self.warpTiles[t] or false
|
|
end
|
|
|
|
-- "pad"/"hole" when the cell's collision tile is a teleporter warp pad or
|
|
-- a fall-through hole (IsPlayerStandingOnWarpPadOrHole), nil otherwise
|
|
function Map:warpPadOrHoleAt(cx, cy)
|
|
local table_ = self.tileset.warpPadTiles or WARP_PAD_TILES[self.def.tileset]
|
|
if not table_ then return nil end
|
|
return table_[self:cellTile(cx, cy)]
|
|
end
|
|
|
|
-- counter tiles allow talking to NPCs across them (mart clerks, nurses)
|
|
function Map:isCounterCell(cx, cy)
|
|
local t = self:cellTile(cx, cy)
|
|
for _, c in ipairs(self.tileset.counterTiles or {}) do
|
|
if c == t then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function Map:warpAtCell(cx, cy)
|
|
return self.warpAt[cy * self.widthCells + cx]
|
|
end
|
|
|
|
function Map:signAtCell(cx, cy)
|
|
return self.signAt[cy * self.widthCells + cx]
|
|
end
|
|
|
|
function Map:connection(dir)
|
|
return self.def.connections and self.def.connections[dir]
|
|
end
|
|
|
|
return Map
|