mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-06 17:10:55 -04:00
b305652fdb
Same decoded pixels, about 60 percent smaller caches. Bumps the FireRed/LeafGreen, Emerald and Ruby/Sapphire cache versions.
479 lines
16 KiB
Lua
479 lines
16 KiB
Lua
-- Pokémon Summary Screen Chrome (ROM-baked textures, bars, icons, and layout).
|
|
-- Handles Love2D texture creation and quad rendering with fallbacks.
|
|
|
|
local Display = require("src.core.game3.display")
|
|
local Extract = require("src.import.gba.extract_island1")
|
|
local SummaryChromeExtract = require("src.import.gba.summary_chrome_extract")
|
|
local CacheBlob = require("src.import.CacheBlob")
|
|
|
|
local SummaryChrome = {}
|
|
|
|
SummaryChrome._cache = nil
|
|
SummaryChrome._pages = {}
|
|
SummaryChrome._hpBars = {}
|
|
SummaryChrome._expBar = nil
|
|
SummaryChrome._statusIcons = nil
|
|
SummaryChrome._cursorLeft = nil
|
|
SummaryChrome._cursorRight = nil
|
|
SummaryChrome._shinyStar = nil
|
|
SummaryChrome._pokerus = nil
|
|
SummaryChrome._menuInfo = nil
|
|
SummaryChrome._manifest = nil
|
|
SummaryChrome._quads = {}
|
|
SummaryChrome._logged = false
|
|
|
|
local function cache_root()
|
|
local okD, Dataset = pcall(require, "src.core.game3.dataset")
|
|
if okD and Dataset and Dataset.mountExtractRoots then
|
|
Dataset.mountExtractRoots()
|
|
end
|
|
return Extract.CACHE_ROOT or "data/generated/gba"
|
|
end
|
|
|
|
local function summary_root()
|
|
return cache_root() .. "/pokemon/summary"
|
|
end
|
|
|
|
local function log(msg)
|
|
if SummaryChrome._logged then return end
|
|
SummaryChrome._logged = true
|
|
print("[game3/summary_chrome] " .. tostring(msg))
|
|
end
|
|
|
|
local function read_bytes(rel)
|
|
local cache = SummaryChrome._cache
|
|
if cache and cache.read then
|
|
local d = cache:read(rel)
|
|
if type(d) == "string" and #d > 0 then return d end
|
|
end
|
|
local okD, Dataset = pcall(require, "src.core.game3.dataset")
|
|
if okD and Dataset and Dataset.cache then
|
|
local d = Dataset.cache():read(rel)
|
|
if type(d) == "string" and #d > 0 then return d end
|
|
end
|
|
local ok, CacheFs = pcall(require, "src.import.CacheFs")
|
|
if ok and CacheFs and CacheFs.readActive then
|
|
local d = CacheFs.readActive(rel)
|
|
if type(d) == "string" and #d > 0 then return d end
|
|
end
|
|
if love and love.filesystem and love.filesystem.read then
|
|
local d = CacheBlob.readFs(rel)
|
|
if type(d) == "string" and #d > 0 then return d end
|
|
local alt = "data/generated/gba/" .. (rel:gsub("^data/generated/gba/", ""))
|
|
d = CacheBlob.readFs(alt)
|
|
if type(d) == "string" and #d > 0 then return d end
|
|
end
|
|
local candidates = {
|
|
rel,
|
|
"data/generated/gba/" .. (rel:gsub("^data/generated/gba/", "")),
|
|
}
|
|
for _, p in ipairs(candidates) do
|
|
local f = io.open(p, "rb")
|
|
if f then
|
|
local d = CacheBlob.decode(p, f:read("*a"))
|
|
f:close()
|
|
if d and #d > 0 then return d end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function load_lua(rel)
|
|
local src = read_bytes(rel)
|
|
if not src then return nil end
|
|
local chunk = load(src, "@" .. rel, "t", {})
|
|
if not chunk then return nil end
|
|
local ok, t = pcall(chunk)
|
|
if ok then return t end
|
|
return nil
|
|
end
|
|
|
|
local function rgba_to_image(rgba, w, h)
|
|
if not (love and love.image and love.graphics) then return nil end
|
|
if not rgba or #rgba < w * h * 4 then return nil end
|
|
local ok, imageData = pcall(love.image.newImageData, w, h, "rgba8", rgba)
|
|
if not ok or not imageData then
|
|
imageData = love.image.newImageData(w, h)
|
|
local i = 1
|
|
for y = 0, h - 1 do
|
|
for x = 0, w - 1 do
|
|
imageData:setPixel(x, y,
|
|
(rgba:byte(i) or 0) / 255,
|
|
(rgba:byte(i + 1) or 0) / 255,
|
|
(rgba:byte(i + 2) or 0) / 255,
|
|
(rgba:byte(i + 3) or 0) / 255)
|
|
i = i + 4
|
|
end
|
|
end
|
|
end
|
|
local image = love.graphics.newImage(imageData)
|
|
if image.setFilter then image:setFilter("nearest", "nearest") end
|
|
return image
|
|
end
|
|
|
|
function SummaryChrome.install(cache)
|
|
if not cache or not cache.read then
|
|
local okD, Dataset = pcall(require, "src.core.game3.dataset")
|
|
if okD and Dataset and Dataset.cache then
|
|
cache = Dataset.cache()
|
|
end
|
|
end
|
|
SummaryChrome._cache = cache
|
|
SummaryChrome._pages = {}
|
|
SummaryChrome._hpBars = {}
|
|
SummaryChrome._expBar = nil
|
|
SummaryChrome._statusIcons = nil
|
|
SummaryChrome._cursorLeft = nil
|
|
SummaryChrome._cursorRight = nil
|
|
SummaryChrome._shinyStar = nil
|
|
SummaryChrome._pokerus = nil
|
|
SummaryChrome._menuInfo = nil
|
|
SummaryChrome._manifest = nil
|
|
SummaryChrome._quads = {}
|
|
end
|
|
|
|
function SummaryChrome.manifest()
|
|
if SummaryChrome._manifest then return SummaryChrome._manifest end
|
|
SummaryChrome._manifest = load_lua(summary_root() .. "/manifest.lua")
|
|
return SummaryChrome._manifest
|
|
end
|
|
|
|
local function cached_image(name, w, h)
|
|
local key = name
|
|
if SummaryChrome._pages[key] ~= nil then return SummaryChrome._pages[key] or nil end
|
|
local raw = read_bytes(summary_root() .. "/" .. name .. ".rgba")
|
|
local img = raw and rgba_to_image(raw, w, h)
|
|
SummaryChrome._pages[key] = img or false
|
|
return img
|
|
end
|
|
|
|
local function variant(name, shiny)
|
|
return shiny and (name .. "_shiny") or name
|
|
end
|
|
|
|
-- pokefirered/src/pokemon_summary_screen.c:1862
|
|
function SummaryChrome.drawBg3(kind, shiny)
|
|
local img = cached_image(variant("bg3_" .. kind, shiny), 240, 160)
|
|
if not (img and love and love.graphics) then return false end
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.draw(img, 0, 0)
|
|
return true
|
|
end
|
|
|
|
-- pokefirered/src/pokemon_summary_screen.c:3276
|
|
function SummaryChrome.drawProgress(kind, shiny)
|
|
local img = cached_image(variant("progress_" .. kind, shiny and kind ~= "egg"), 48, 16)
|
|
if not (img and love and love.graphics) then return false end
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.draw(img, 104, 0)
|
|
return true
|
|
end
|
|
|
|
function SummaryChrome.drawLayer(kind, xOffset, shiny)
|
|
local img = cached_image(variant("layer_" .. kind, shiny and kind ~= "egg"), 240, 160)
|
|
if not (img and love and love.graphics) then return false end
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.draw(img, xOffset or 0, 0)
|
|
return true
|
|
end
|
|
|
|
function SummaryChrome.hpBarImage(color)
|
|
color = color or "green"
|
|
if SummaryChrome._hpBars[color] then return SummaryChrome._hpBars[color] end
|
|
local raw = read_bytes(summary_root() .. "/hp_bar_" .. color .. ".rgba")
|
|
if raw then
|
|
local img = rgba_to_image(raw, 96, 8)
|
|
SummaryChrome._hpBars[color] = img
|
|
return img
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function SummaryChrome.expBarImage()
|
|
if SummaryChrome._expBar then return SummaryChrome._expBar end
|
|
local raw = read_bytes(summary_root() .. "/exp_bar.rgba")
|
|
if raw then
|
|
local img = rgba_to_image(raw, 96, 8)
|
|
SummaryChrome._expBar = img
|
|
return img
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function SummaryChrome.statusIconsImage()
|
|
if SummaryChrome._statusIcons then return SummaryChrome._statusIcons end
|
|
local raw = read_bytes(summary_root() .. "/status_icons.rgba")
|
|
if raw then
|
|
local img = rgba_to_image(raw, 32, 64)
|
|
SummaryChrome._statusIcons = img
|
|
return img
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function SummaryChrome.menuInfoImage()
|
|
if SummaryChrome._menuInfo then return SummaryChrome._menuInfo end
|
|
local raw = read_bytes(summary_root() .. "/menu_info.rgba")
|
|
local img = raw and rgba_to_image(raw, 128, 128)
|
|
SummaryChrome._menuInfo = img
|
|
return img
|
|
end
|
|
|
|
function SummaryChrome.cursorImages()
|
|
if SummaryChrome._cursorLeft and SummaryChrome._cursorRight then
|
|
return SummaryChrome._cursorLeft, SummaryChrome._cursorRight
|
|
end
|
|
local rawL = read_bytes(summary_root() .. "/cursor_left.rgba")
|
|
local rawR = read_bytes(summary_root() .. "/cursor_right.rgba")
|
|
if rawL then SummaryChrome._cursorLeft = rgba_to_image(rawL, 64, 64) end
|
|
if rawR then SummaryChrome._cursorRight = rgba_to_image(rawR, 64, 64) end
|
|
return SummaryChrome._cursorLeft, SummaryChrome._cursorRight
|
|
end
|
|
|
|
function SummaryChrome.shinyStarImage()
|
|
if SummaryChrome._shinyStar then return SummaryChrome._shinyStar end
|
|
local raw = read_bytes(summary_root() .. "/shiny_star.rgba")
|
|
if raw then
|
|
local img = rgba_to_image(raw, 8, 16)
|
|
SummaryChrome._shinyStar = img
|
|
return img
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function SummaryChrome.pokerusImage()
|
|
if SummaryChrome._pokerus then return SummaryChrome._pokerus end
|
|
local raw = read_bytes(summary_root() .. "/pokerus.rgba")
|
|
if raw then
|
|
local img = rgba_to_image(raw, 8, 8)
|
|
SummaryChrome._pokerus = img
|
|
return img
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function get_quad(key, x, y, w, h, sw, sh)
|
|
if not (love and love.graphics and love.graphics.newQuad) then return nil end
|
|
if not SummaryChrome._quads[key] then
|
|
SummaryChrome._quads[key] = love.graphics.newQuad(x, y, w, h, sw, sh)
|
|
end
|
|
return SummaryChrome._quads[key]
|
|
end
|
|
|
|
|
|
--- Pret UpdateHpBarObjs: 9 sprites at (x + i*8, y).
|
|
--- anim 9=HP label, 10=left cap, 0..8=fill (0 empty..8 full), 11=right cap.
|
|
--- Fill spans sprites[2..7] (6 tiles); `x` is sprite[0] left edge (pret: 172).
|
|
local function bar_fill_anims(cur, max, fillSlots)
|
|
-- Mirror pret: pointsPerTile = (max<<2)/slots; accumulate whole tiles from sprite index 2.
|
|
local anims = {}
|
|
for i = 1, fillSlots do anims[i] = 0 end
|
|
cur = math.max(0, tonumber(cur) or 0)
|
|
max = math.max(1, tonumber(max) or 1)
|
|
if cur >= max then
|
|
for i = 1, fillSlots do anims[i] = 8 end
|
|
return anims
|
|
end
|
|
local pointsPerTile = (max * 4) / fillSlots
|
|
if pointsPerTile <= 0 then return anims end
|
|
local totalPoints = cur * 4
|
|
local whole = 0
|
|
while totalPoints > pointsPerTile do
|
|
totalPoints = totalPoints - pointsPerTile
|
|
whole = whole + 1
|
|
end
|
|
for i = 1, math.min(whole, fillSlots) do
|
|
anims[i] = 8
|
|
end
|
|
if whole < fillSlots then
|
|
local partial = math.floor((totalPoints * fillSlots) / pointsPerTile)
|
|
if partial < 0 then partial = 0 end
|
|
if partial > 7 then partial = 7 end
|
|
anims[whole + 1] = partial
|
|
end
|
|
return anims
|
|
end
|
|
|
|
local function draw_bar_tile(img, quadKey, tileIndex, dx, dy)
|
|
local q = get_quad(quadKey .. tileIndex, tileIndex * 8, 0, 8, 8, 96, 8)
|
|
love.graphics.draw(img, q, dx, dy)
|
|
end
|
|
|
|
function SummaryChrome.drawHpBar(x, y, curHp, maxHp)
|
|
if not (love and love.graphics) then return end
|
|
curHp = math.max(0, tonumber(curHp) or 0)
|
|
maxHp = math.max(1, tonumber(maxHp) or 1)
|
|
local ratio = curHp / maxHp
|
|
local color = "green"
|
|
if ratio <= 0.20 then
|
|
color = "red"
|
|
elseif ratio <= 0.50 then
|
|
color = "yellow"
|
|
end
|
|
|
|
local img = SummaryChrome.hpBarImage(color)
|
|
if not img then
|
|
love.graphics.setColor(0.1, 0.1, 0.1, 1)
|
|
love.graphics.rectangle("fill", x + 16, y + 2, 48, 4)
|
|
if color == "green" then love.graphics.setColor(0.2, 0.8, 0.2, 1)
|
|
elseif color == "yellow" then love.graphics.setColor(0.9, 0.8, 0.1, 1)
|
|
else love.graphics.setColor(0.9, 0.2, 0.2, 1) end
|
|
love.graphics.rectangle("fill", x + 16, y + 2, math.floor(48 * ratio), 4)
|
|
return
|
|
end
|
|
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
local fills = bar_fill_anims(curHp, maxHp, 6)
|
|
-- sprites[0]=HP, [1]=cap, [2..7]=fill, [8]=end (pret CreateHpBarObjs)
|
|
draw_bar_tile(img, "hp_t", 9, x + 0 * 8, y)
|
|
draw_bar_tile(img, "hp_t", 10, x + 1 * 8, y)
|
|
for i = 0, 5 do
|
|
draw_bar_tile(img, "hp_t", fills[i + 1] or 0, x + (2 + i) * 8, y)
|
|
end
|
|
draw_bar_tile(img, "hp_t", 11, x + 8 * 8, y)
|
|
end
|
|
|
|
--- Pret UpdateExpBarObjs: 11 sprites at (x + i*8, y).
|
|
--- anim 9=EXP label, 10=left cap, 0..8=fill, 11=right cap. Fill = sprites[2..9].
|
|
function SummaryChrome.drawExpBar(x, y, progressPercent)
|
|
if not (love and love.graphics) then return end
|
|
progressPercent = math.max(0.0, math.min(1.0, tonumber(progressPercent) or 0.0))
|
|
local img = SummaryChrome.expBarImage()
|
|
if not img then
|
|
love.graphics.setColor(0.1, 0.1, 0.1, 1)
|
|
love.graphics.rectangle("fill", x + 16, y + 2, 64, 4)
|
|
love.graphics.setColor(0.2, 0.5, 0.9, 1)
|
|
love.graphics.rectangle("fill", x + 16, y + 2, math.floor(64 * progressPercent), 4)
|
|
return
|
|
end
|
|
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
-- progressPercent is already cur/needed within the level; fake a 100-unit bar.
|
|
local fills = bar_fill_anims(math.floor(progressPercent * 100 + 0.5), 100, 8)
|
|
draw_bar_tile(img, "exp_t", 9, x + 0 * 8, y)
|
|
draw_bar_tile(img, "exp_t", 10, x + 1 * 8, y)
|
|
for i = 0, 7 do
|
|
draw_bar_tile(img, "exp_t", fills[i + 1] or 0, x + (2 + i) * 8, y)
|
|
end
|
|
draw_bar_tile(img, "exp_t", 11, x + 10 * 8, y)
|
|
end
|
|
|
|
--- Draw Status Ailment Icon (32x8 px)
|
|
--- 1: PSN, 2: PRZ, 3: SLP, 4: FRZ, 5: BRN, 6: PKRS, 7: FNT
|
|
function SummaryChrome.drawStatusIcon(x, y, ailmentCode)
|
|
if not (love and love.graphics) then return end
|
|
ailmentCode = tonumber(ailmentCode) or 0
|
|
if ailmentCode < 1 or ailmentCode > 7 then return end
|
|
local img = SummaryChrome.statusIconsImage()
|
|
if not img then return end
|
|
local frame = ailmentCode - 1
|
|
local q = get_quad("status_" .. frame, 0, frame * 8, 32, 8, 32, 64)
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.draw(img, q, x, y)
|
|
end
|
|
|
|
--- Type badge mapping in menu_info.png (128x128)
|
|
local TYPE_RECTS = {
|
|
[0] = { x = 0, y = 16, w = 32, h = 12 }, -- NORMAL
|
|
[1] = { x = 32, y = 48, w = 32, h = 12 }, -- FIGHTING
|
|
[2] = { x = 0, y = 48, w = 32, h = 12 }, -- FLYING
|
|
[3] = { x = 0, y = 64, w = 32, h = 12 }, -- POISON
|
|
[4] = { x = 64, y = 32, w = 32, h = 12 }, -- GROUND
|
|
[5] = { x = 32, y = 32, w = 32, h = 12 }, -- ROCK
|
|
[6] = { x = 96, y = 48, w = 32, h = 12 }, -- BUG
|
|
[7] = { x = 64, y = 48, w = 32, h = 12 }, -- GHOST
|
|
[8] = { x = 64, y = 64, w = 32, h = 12 }, -- STEEL
|
|
[9] = { x = 32, y = 80, w = 32, h = 12 }, -- MYSTERY
|
|
[10] = { x = 32, y = 16, w = 32, h = 12 }, -- FIRE
|
|
[11] = { x = 64, y = 16, w = 32, h = 12 }, -- WATER
|
|
[12] = { x = 96, y = 16, w = 32, h = 12 }, -- GRASS
|
|
[13] = { x = 0, y = 32, w = 32, h = 12 }, -- ELECTRIC
|
|
[14] = { x = 32, y = 64, w = 32, h = 12 }, -- PSYCHIC
|
|
[15] = { x = 96, y = 32, w = 32, h = 12 }, -- ICE
|
|
[16] = { x = 0, y = 80, w = 32, h = 12 }, -- DRAGON
|
|
[17] = { x = 96, y = 64, w = 32, h = 12 }, -- DARK
|
|
}
|
|
|
|
local TYPE_NAMES = {
|
|
NORMAL = 0, FIGHTING = 1, FLYING = 2, POISON = 3, GROUND = 4,
|
|
ROCK = 5, BUG = 6, GHOST = 7, STEEL = 8, MYSTERY = 9,
|
|
FIRE = 10, WATER = 11, GRASS = 12, ELECTRIC = 13, PSYCHIC = 14,
|
|
ICE = 15, DRAGON = 16, DARK = 17,
|
|
}
|
|
|
|
--- Draw Type Badge (32x12)
|
|
function SummaryChrome.drawTypeBadge(typeId, x, y)
|
|
if not (love and love.graphics) then return end
|
|
if type(typeId) == "string" then
|
|
typeId = TYPE_NAMES[typeId:upper()] or 0
|
|
end
|
|
typeId = tonumber(typeId) or 0
|
|
local rect = TYPE_RECTS[typeId] or TYPE_RECTS[0]
|
|
local img = SummaryChrome.menuInfoImage()
|
|
if not img then
|
|
love.graphics.setColor(0.4, 0.4, 0.4, 1)
|
|
love.graphics.rectangle("fill", x, y, 32, 12)
|
|
return
|
|
end
|
|
local q = get_quad("type_" .. typeId, rect.x, rect.y, rect.w, rect.h, 128, 128)
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.draw(img, q, x, y)
|
|
end
|
|
|
|
--- Draw Move Selection Cursor (Red for selecting, Blue for swap target)
|
|
--- 1:1 pret PokeSum_CreateMoveSelectionCursorObjs (two 64x32 sprites at x, x+64)
|
|
function SummaryChrome.drawMoveSelectionCursor(x, y, w, h, isBlue)
|
|
if not (love and love.graphics) then return end
|
|
if type(w) == "boolean" then
|
|
isBlue = w
|
|
w, h = 128, 32
|
|
end
|
|
local curL, curR = SummaryChrome.cursorImages()
|
|
if curL and curR then
|
|
-- Frame 0 is Red (selecting: 0..31), Frame 1 is Blue (swapping: 32..63)
|
|
local vOffset = isBlue and 32 or 0
|
|
local qL = get_quad("cur_l_" .. (isBlue and "b" or "r"), 0, vOffset, 64, 32, 64, 64)
|
|
local qR = get_quad("cur_r_" .. (isBlue and "b" or "r"), 0, vOffset, 64, 32, 64, 64)
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.draw(curL, qL, x, y)
|
|
love.graphics.draw(curR, qR, x + 64, y)
|
|
else
|
|
-- Fallback outline
|
|
love.graphics.setColor(isBlue and { 0.2, 0.4, 0.9, 1 } or { 0.9, 0.2, 0.2, 1 })
|
|
love.graphics.rectangle("line", x, y, w or 128, h or 32)
|
|
end
|
|
end
|
|
|
|
--- Draw Shiny Star (8x8)
|
|
function SummaryChrome.drawShinyStar(x, y)
|
|
if not (love and love.graphics) then return end
|
|
local img = SummaryChrome.shinyStarImage()
|
|
if not img then return end
|
|
-- pokefirered/src/pokemon_summary_screen.c:610
|
|
local q = get_quad("shiny_star", 0, 8, 8, 8, 8, 16)
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.draw(img, q, x, y)
|
|
end
|
|
|
|
--- Draw Cured Pokérus icon (8x8)
|
|
function SummaryChrome.drawPokerus(x, y)
|
|
if not (love and love.graphics) then return end
|
|
local img = SummaryChrome.pokerusImage()
|
|
if not img then return end
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.draw(img, x, y)
|
|
end
|
|
|
|
-- pokefirered/src/mon_markings.c:558
|
|
function SummaryChrome.drawMarkings(markings, x, y)
|
|
markings = tonumber(markings) or 0
|
|
if markings <= 0 or markings > 15 then return end
|
|
local img = cached_image("markings", 32, 128)
|
|
if not (img and love and love.graphics) then return end
|
|
local q = get_quad("markings_" .. markings, 0, markings * 8, 32, 8, 32, 128)
|
|
love.graphics.setColor(1, 1, 1, 1)
|
|
love.graphics.draw(img, q, x, y)
|
|
end
|
|
|
|
return SummaryChrome
|