mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-07 01:18:32 -04:00
313 lines
11 KiB
Lua
313 lines
11 KiB
Lua
-- Pokédex CONTENTS screen: engine/menus/pokedex.asm:157-348
|
|
-- (Yellow: pokeyellow engine/menus/pokedex.asm:263-336).
|
|
|
|
local Font = require("src.render.Font")
|
|
local Strings = require("src.core.Strings")
|
|
local Theme = require("src.ui.Theme")
|
|
local MenuRepeat = require("src.ui.MenuRepeat")
|
|
|
|
local PokedexMenu = {}
|
|
PokedexMenu.__index = PokedexMenu
|
|
PokedexMenu.isOpaque = true
|
|
|
|
-- engine/menus/pokedex.asm:226 (`ld d, 7`), :21 (top menu item Y 3, X 0)
|
|
local ROWS = 7
|
|
local FIRST_ROW_Y = 24
|
|
local NUM_X, BALL_X, NAME_X, CURSOR_X = 8, 24, 32, 0
|
|
|
|
-- engine/menus/pokedex.asm:163-199
|
|
-- (Yellow: pokeyellow engine/menus/pokedex.asm:266-306)
|
|
local RED_ROWS = { seen = 2, own = 5, rule = 8, items = 10 }
|
|
local YELLOW_ROWS = { seen = 1, own = 4, rule = 6, items = 8 }
|
|
|
|
-- engine/menus/pokedex.asm:350 (DrawPokedexVerticalLine): column 14, $71
|
|
-- toggling to $70 down each nine-tile run, both runs restarting on $71
|
|
local function dividerCodes()
|
|
local codes = { [0] = 0x71 }
|
|
for _, top in ipairs({ 1, 9 }) do
|
|
local code = 0x71
|
|
for i = 0, 8 do
|
|
codes[top + i] = code
|
|
code = code == 0x71 and 0x70 or 0x71
|
|
end
|
|
end
|
|
return codes
|
|
end
|
|
local DIVIDER = dividerCodes()
|
|
|
|
-- SGB: PalPacket_Pokedex, whole screen
|
|
function PokedexMenu:sgbPalettes(game)
|
|
return require("src.render.PaletteFX").wholeNamed(game.data, "BROWNMON")
|
|
end
|
|
|
|
function PokedexMenu.new(game, opts)
|
|
opts = opts or {}
|
|
local self = setmetatable({}, PokedexMenu)
|
|
self.game = game
|
|
self.onCancel = opts.onCancel -- B returns to the start menu when opened from it
|
|
self.index = 1
|
|
self.scroll = 0
|
|
-- engine/menus/pokedex.asm:13
|
|
self.hold = MenuRepeat.new(MenuRepeat.GEN1_DELAY, MenuRepeat.GEN1_RATE)
|
|
self.rowsAt = require("src.core.GameVersion").isYellow()
|
|
and YELLOW_ROWS or RED_ROWS
|
|
local dex = game.save.pokedex or { seen = {}, owned = {} }
|
|
local byDex = {}
|
|
for species, def in pairs(game.data.pokemon) do
|
|
if def.dex then byDex[def.dex] = def end
|
|
end
|
|
-- dex bound and number width come from constants; the fallbacks keep a
|
|
-- cache imported before those keys existed on the Kanto numbering
|
|
local constants = game.data.constants or {}
|
|
local numFmt = ("%%0%dd"):format(constants.dexDigits or 3)
|
|
local dexSize = constants.dexSize or 151
|
|
-- engine/menus/pokedex.asm:200-216: the list stops at wDexMaxSeenMon, the
|
|
-- highest number the player has seen, not at the end of the roster
|
|
local maxSeen = 0
|
|
local seen, owned = 0, 0
|
|
for n = 1, dexSize do
|
|
local def = byDex[n]
|
|
if def then
|
|
if dex.owned[def.id] then
|
|
owned = owned + 1
|
|
seen = seen + 1
|
|
maxSeen = n
|
|
elseif dex.seen[def.id] then
|
|
seen = seen + 1
|
|
maxSeen = n
|
|
end
|
|
end
|
|
end
|
|
local items = {}
|
|
for n = 1, math.min(dexSize, maxSeen) do
|
|
local def = byDex[n]
|
|
if def then
|
|
local known = dex.owned[def.id] or dex.seen[def.id]
|
|
-- engine/menus/pokedex.asm:265 (.dashedLine) for anything unseen
|
|
local name = known and def.name or "----------"
|
|
items[#items + 1] = {
|
|
num = numFmt:format(n),
|
|
name = name,
|
|
label = (numFmt .. " %s"):format(n, name),
|
|
-- owned entries carry the pokéball marker like the original
|
|
-- list; seen-only entries are just the name
|
|
ball = dex.owned[def.id] or nil,
|
|
value = known and def.id or nil,
|
|
}
|
|
end
|
|
end
|
|
self.items = items
|
|
-- SEEN / OWN, `lb bc, 1, 3` at hlcoord 16,3 and 16,6: engine/menus/
|
|
-- pokedex.asm HandlePokedexListMenu (#639)
|
|
self.seenCount, self.ownedCount = seen, owned
|
|
self.footer = Strings("SEEN %3d OWN %3d", seen, owned)
|
|
return self
|
|
end
|
|
|
|
-- HandleMenuInput_ replays SFX_PRESS_AB for A and B (home/window.asm)
|
|
local function beep(self)
|
|
if not (self.game and self.game.data) then return end
|
|
require("src.core.Sound").play(self.game.data, "Press_AB")
|
|
end
|
|
|
|
function PokedexMenu:rows()
|
|
return math.min(ROWS, #self.items)
|
|
end
|
|
|
|
-- engine/menus/pokedex.asm:290-313
|
|
function PokedexMenu:syncScroll()
|
|
local rows = self:rows()
|
|
if rows == 0 then
|
|
self.index, self.scroll = 1, 0
|
|
return
|
|
end
|
|
self.index = math.max(1, math.min(#self.items, self.index))
|
|
if self.index - self.scroll > rows then self.scroll = self.index - rows end
|
|
if self.index - self.scroll < 1 then self.scroll = self.index - 1 end
|
|
end
|
|
|
|
-- engine/menus/pokedex.asm:314-342: Left and Right move wListScrollOffset
|
|
-- by seven and never touch wCurrentMenuItem, so the cursor keeps its row
|
|
function PokedexMenu:pageScroll(dir)
|
|
local n = #self.items
|
|
if n < ROWS then return end
|
|
local row = self.index - self.scroll
|
|
local scroll = self.scroll + dir * ROWS
|
|
scroll = math.max(0, math.min(n - ROWS, scroll))
|
|
self.scroll = scroll
|
|
self.index = math.min(n, scroll + row)
|
|
end
|
|
|
|
function PokedexMenu:close()
|
|
local top = self.game.stack:top()
|
|
if top == self then self.game.stack:pop() end
|
|
end
|
|
|
|
function PokedexMenu:update(dt)
|
|
-- back on top of the stack, so the side menu's hollow '▷' is gone
|
|
-- (engine/menus/pokedex.asm:61, PlaceUnfilledArrowMenuCursor)
|
|
self.hollowIndex = nil
|
|
local input = self.game.input
|
|
if #self.items == 0 then
|
|
if input:wasPressed("a") or input:wasPressed("b") then
|
|
beep(self)
|
|
self.game.stack:pop()
|
|
if self.onCancel then self.onCancel() end
|
|
end
|
|
return
|
|
end
|
|
local dir = MenuRepeat.direction(self.hold, input)
|
|
if dir == "up" then
|
|
self.index = self.index - 1
|
|
elseif dir == "down" then
|
|
self.index = self.index + 1
|
|
elseif dir == "left" then
|
|
self:pageScroll(-1)
|
|
elseif dir == "right" then
|
|
self:pageScroll(1)
|
|
elseif input:wasPressed("b") then
|
|
beep(self)
|
|
self.game.stack:pop()
|
|
if self.onCancel then self.onCancel() end
|
|
return
|
|
elseif input:wasPressed("a") then
|
|
beep(self)
|
|
self.onChoose(self.items[self.index], self)
|
|
return
|
|
end
|
|
self:syncScroll()
|
|
end
|
|
|
|
-- engine/menus/pokedex.asm:371-375, :81-85
|
|
function PokedexMenu:sideItems()
|
|
local labels = { Strings("DATA"), Strings("CRY"), Strings("AREA") }
|
|
if require("src.core.GameVersion").isYellow() then
|
|
labels[#labels + 1] = Strings("PRNT")
|
|
end
|
|
labels[#labels + 1] = Strings("QUIT")
|
|
return labels
|
|
end
|
|
|
|
local function sideRowY(menu, row)
|
|
return (menu.ty + menu.th - 2 - (#menu.items - row) * 2) * 8
|
|
end
|
|
|
|
local function drawSideMenu(self)
|
|
love.graphics.setColor(0, 0, 0, 1)
|
|
for row = 1, #self.items do
|
|
Font.draw(self.items[row].label, (self.tx + 2) * 8, sideRowY(self, row))
|
|
end
|
|
Font.drawCode(Theme.cursor, (self.tx + 1) * 8, sideRowY(self, self.index))
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
end
|
|
|
|
-- engine/menus/pokedex.asm HandlePokedexSideMenu, ShowPokedexMenu
|
|
-- .exitPokedex (#571)
|
|
local function chooseEntry(item, dexList)
|
|
local game = dexList.game
|
|
-- engine/menus/pokedex.asm:77-80: an unseen row hands back b=2 at once
|
|
if not item.value then return end
|
|
local Menu = require("src.ui.Menu")
|
|
local Screens = require("src.ui.Screens")
|
|
local entries = {
|
|
{ label = Strings("DATA"), onSelect = function()
|
|
Screens.push(game, "DexEntryMenu", item.value)
|
|
end },
|
|
{ label = Strings("CRY"), keepOpen = true, onSelect = function()
|
|
require("src.core.Sound").playCry(game.data, item.value)
|
|
end },
|
|
{ label = Strings("AREA"), onSelect = function()
|
|
Screens.push(game, "TownMap", { nestSpecies = item.value })
|
|
end },
|
|
}
|
|
-- pokeyellow engine/menus/pokedex.asm PokedexMenuItemsText,
|
|
-- PrintPokedexEntry
|
|
if require("src.core.GameVersion").isYellow() then
|
|
entries[#entries + 1] = { label = Strings("PRNT"), onSelect = function()
|
|
local DexEntryMenu = require("src.ui.DexEntryMenu")
|
|
local Printer = require("src.core.Printer")
|
|
local TextBox = require("src.render.TextBox")
|
|
local def = game.data.pokemon[item.value]
|
|
local path = require("src.pokemon.Sprites").path(
|
|
game.data, item.value, "front", { kind = "dex" })
|
|
local ok, sprite = false, nil
|
|
if path then ok, sprite = pcall(love.graphics.newImage, path) end
|
|
local saved, err = Printer.save("dex_" .. item.value, 160, 144,
|
|
function()
|
|
DexEntryMenu.render(game, def, ok and sprite or nil, false)
|
|
end)
|
|
game.stack:push(TextBox.new(game, saved
|
|
and (Strings("Printed %s's\ndata!\f", def.name)
|
|
.. Printer.savedWhereText(saved))
|
|
or Strings("Printer error!\n%s", tostring(err))))
|
|
end }
|
|
end
|
|
entries[#entries + 1] = { label = Strings("QUIT"), onSelect = function()
|
|
-- engine/menus/pokedex.asm .exitPokedex
|
|
dexList:close()
|
|
if dexList.onCancel then dexList.onCancel() end
|
|
end }
|
|
local ty = dexList.rowsAt.items - 2
|
|
local side = Menu.new(game, entries, { tx = 14, ty = ty, tw = 6 })
|
|
-- the block is already on screen, so the side menu is the cursor alone:
|
|
-- text at column 16, cursor at 15 (engine/menus/pokedex.asm:82-86)
|
|
side.tx, side.tw, side.ty, side.th = 14, 6, ty, #entries * 2 + 2
|
|
side.draw = drawSideMenu
|
|
dexList.hollowIndex = dexList.index
|
|
game.stack:push(side)
|
|
end
|
|
PokedexMenu.onChoose = chooseEntry
|
|
|
|
-- engine/menus/pokedex.asm:256, engine/gfx/load_pokedex_tiles.asm:8-11
|
|
local function drawBall(game, x, y)
|
|
local DexEntryMenu = require("src.ui.DexEntryMenu")
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
DexEntryMenu.tile(game, 0x72, x / 8, y / 8)
|
|
love.graphics.setColor(0, 0, 0, 1)
|
|
end
|
|
|
|
function PokedexMenu:draw()
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.rectangle("fill", 0, 0, 160, 144)
|
|
local at = self.rowsAt
|
|
local DexEntryMenu = require("src.ui.DexEntryMenu")
|
|
-- engine/menus/pokedex.asm:159-176: the divider column and the rule that
|
|
-- fences SEEN / OWN off from the side-menu block
|
|
for ty = 0, 17 do
|
|
DexEntryMenu.tile(self.game, DIVIDER[ty], 14, ty)
|
|
end
|
|
love.graphics.setColor(0, 0, 0, 1)
|
|
Font.draw(("─"):rep(5), 120, at.rule * 8)
|
|
Font.draw(Strings("CONTENTS"), 8, 8)
|
|
Font.draw(Strings("SEEN"), 128, at.seen * 8)
|
|
Font.draw(Strings("OWN"), 128, at.own * 8)
|
|
-- PrintNumber's fixed three-digit field ends on column 19
|
|
local function count(n, ty)
|
|
local text = tostring(n)
|
|
Font.draw(text, 152 - Font.width(text), ty * 8)
|
|
end
|
|
count(self.seenCount, at.seen + 1)
|
|
count(self.ownedCount, at.own + 1)
|
|
local labels = self:sideItems()
|
|
for i, label in ipairs(labels) do
|
|
Font.draw(label, 128, (at.items + (i - 1) * 2) * 8)
|
|
end
|
|
for row = 1, self:rows() do
|
|
local i = self.scroll + row
|
|
local item = self.items[i]
|
|
if not item then break end
|
|
local y = FIRST_ROW_Y + (row - 1) * 16
|
|
-- the number sits one row above its name (engine/menus/pokedex.asm:242-246)
|
|
Font.draw(item.num, NUM_X, y - 8)
|
|
if item.ball then drawBall(self.game, BALL_X, y) end
|
|
Font.draw(item.name, NAME_X, y)
|
|
if i == self.index then
|
|
Font.drawCode(self.hollowIndex == i
|
|
and Theme.cursorHollow or Theme.cursor, CURSOR_X, y)
|
|
end
|
|
end
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
end
|
|
|
|
return PokedexMenu
|