-- 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