mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
Draw a FireRed egg's menu icon as an egg
The party, the PC boxes and the release animation drew an egg with the icon of the species it will hatch into, so a Pichu egg showed as a Pichu. pret's party menu picks the icon from GetMonData(MON_DATA_SPECIES_OR_EGG), which is SPECIES_EGG for any egg (pokefirered/src/pokemon.c:3245, party_menu.c:2655). Pokemon.speciesOrEgg does the same, and the icon call sites use it. The species pass only extracted icons up to NUM_SPECIES - 1, so the egg extractor now also bakes gMonIconTable[SPECIES_EGG] as pokemon/icons/412.rgba. Cache version 114 re-imports existing caches so the icon appears.
This commit is contained in:
@@ -1256,6 +1256,13 @@ function Pokemon.isEgg(mon)
|
||||
return (mon.isEgg == true) or (mon.egg == true) or (mon.species == 412)
|
||||
end
|
||||
|
||||
-- pokefirered/src/pokemon.c:3245 MON_DATA_SPECIES_OR_EGG: an egg's menu icon is
|
||||
-- SPECIES_EGG's, not the species it will hatch into (party_menu.c:2655).
|
||||
function Pokemon.speciesOrEgg(mon)
|
||||
if Pokemon.isEgg(mon) then return Pokemon.SPECIES_EGG end
|
||||
return Pokemon.speciesOf(mon)
|
||||
end
|
||||
|
||||
local function read_rgba(species)
|
||||
local cache = resolve_cache(Pokemon._cache)
|
||||
local root = (Extract.CACHE_ROOT or "data/generated/gba") .. "/pokemon"
|
||||
|
||||
@@ -377,6 +377,8 @@ CacheContract.VERSION_REQUIRED_FILES_OVERRIDE = {
|
||||
"data/generated/gba/pokemon/egg/hatch.rgba",
|
||||
"data/generated/gba/pokemon/egg/shard.rgba",
|
||||
"data/generated/gba/pokemon/front/412.rgba",
|
||||
-- src/party_menu.c:2655
|
||||
"data/generated/gba/pokemon/icons/412.rgba",
|
||||
-- src/battle_records.c:563
|
||||
"data/generated/gba/trainer_tower/manifest.lua",
|
||||
"data/generated/gba/trainer_tower/records_bg.rgba",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
local Versions = require("src.import.gba.versions")
|
||||
local Lz77 = require("src.import.gba.lz77")
|
||||
local BgBake = require("src.import.gba.bg_bake")
|
||||
local PokemonExtract = require("src.import.gba.pokemon_extract")
|
||||
|
||||
local EggExtract = {}
|
||||
|
||||
@@ -77,6 +78,10 @@ function EggExtract.run(rom, cache, opts)
|
||||
cache:write(cacheRoot .. "/pokemon/front/" .. EggExtract.SPECIES_EGG .. ".rgba",
|
||||
BgBake.bakeSpriteRgba(tiles, picBank, 0, PIC_W, PIC_H, false, false))
|
||||
|
||||
-- src/party_menu.c:2655 draws an egg's icon from MON_DATA_SPECIES_OR_EGG
|
||||
cache:write(cacheRoot .. "/pokemon/icons/" .. EggExtract.SPECIES_EGG .. ".rgba",
|
||||
PokemonExtract.iconRgba(rom, EggExtract.SPECIES_EGG))
|
||||
|
||||
cache:write(root .. "/manifest.lua", string.format([[
|
||||
return {
|
||||
format_version = %d,
|
||||
@@ -102,7 +107,12 @@ function EggExtract.ready(cache, cacheRoot)
|
||||
for _, rel in ipairs({ "hatch.rgba", "shard.rgba", "manifest.lua" }) do
|
||||
if not cache:exists(root .. "/" .. rel) then return false end
|
||||
end
|
||||
return cache:exists(cacheRoot .. "/pokemon/front/" .. EggExtract.SPECIES_EGG .. ".rgba")
|
||||
for _, sub in ipairs({ "front", "icons" }) do
|
||||
if not cache:exists(cacheRoot .. "/pokemon/" .. sub .. "/" .. EggExtract.SPECIES_EGG .. ".rgba") then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return EggExtract
|
||||
|
||||
@@ -648,9 +648,26 @@ local function extract_egg_moves(rom, num)
|
||||
return eggMoves
|
||||
end
|
||||
|
||||
-- gMonIconTable / gMonIconPaletteIndices entry for one species, both frames
|
||||
-- (32x64 RGBA); they run past NUM_SPECIES, so SPECIES_EGG has its own icon.
|
||||
local function icon_rgba(rom, sp, pals)
|
||||
pals = pals or load_icon_pals(rom)
|
||||
local w = Versions.MON_ICON_W or 32
|
||||
local iconH = (Versions.MON_ICON_H or 32) * 2 -- 64 (2 frames)
|
||||
local iconBytes = Versions.MON_ICON_BYTES or math.floor(w * iconH / 2) -- 1024 for 32x64
|
||||
local off = gba_off(rom:u32(Versions.MON_ICON_TABLE + sp * 4))
|
||||
if not off then return string.rep(string.char(0, 0, 0, 0), w * iconH * 4) end
|
||||
local palIdx = rom:get(Versions.MON_ICON_PAL_INDICES + sp) or 0
|
||||
if palIdx >= Versions.MON_ICON_PAL_COUNT then palIdx = 0 end
|
||||
local pixels = decode_4bpp(rom:readBytes(off, iconBytes), w, iconH)
|
||||
return bake_icon_rgba(pixels, pals[palIdx] or pals[0], w, iconH)
|
||||
end
|
||||
|
||||
-- Exposed for tests (tests/engine/game3_egg_moves.lua).
|
||||
PokemonExtract.eggMovesFromRom = extract_egg_moves
|
||||
PokemonExtract.writeEggMovesLua = write_egg_moves_lua
|
||||
-- src/import/gba/egg_extract.lua bakes the SPECIES_EGG icon with it.
|
||||
PokemonExtract.iconRgba = icon_rgba
|
||||
|
||||
--- Extract full pack into cache under {cacheRoot}/pokemon/.
|
||||
function PokemonExtract.run(rom, cache, opts)
|
||||
@@ -669,13 +686,7 @@ function PokemonExtract.run(rom, cache, opts)
|
||||
local nameBase = Versions.SPECIES_NAMES
|
||||
local infoBase = Versions.SPECIES_INFO
|
||||
local natBase = Versions.SPECIES_TO_NATIONAL
|
||||
local w = Versions.MON_ICON_W or 32
|
||||
local h = Versions.MON_ICON_H or 32
|
||||
local iconH = h * 2 -- 64 (2 frames)
|
||||
local iconBytes = Versions.MON_ICON_BYTES or math.floor(w * iconH / 2) -- 1024 for 32x64
|
||||
local palIdxBase = Versions.MON_ICON_PAL_INDICES
|
||||
local pals = load_icon_pals(rom)
|
||||
local iconTable = Versions.MON_ICON_TABLE
|
||||
local frontPicTable = (Versions.OAK_SPEECH and Versions.OAK_SPEECH.mon_front_pic_table) or 0x2350AC
|
||||
local backPicTable = Versions.MON_BACK_PIC_TABLE or 0x23654C
|
||||
local palTable = (Versions.OAK_SPEECH and Versions.OAK_SPEECH.mon_palette_table) or 0x23730C
|
||||
@@ -727,20 +738,7 @@ function PokemonExtract.run(rom, cache, opts)
|
||||
-- Table omits SPECIES_NONE; SpeciesToNationalPokedexNum uses [species - 1].
|
||||
toNat[sp] = (sp >= 1) and rom:u16(natBase + (sp - 1) * 2) or 0
|
||||
|
||||
local ptr = rom:u32(iconTable + sp * 4)
|
||||
local off = gba_off(ptr)
|
||||
local palIdx = rom:get(palIdxBase + sp) or 0
|
||||
if palIdx >= Versions.MON_ICON_PAL_COUNT then palIdx = 0 end
|
||||
local pal = pals[palIdx] or pals[0]
|
||||
local rgba
|
||||
if off then
|
||||
local bytes = rom:readBytes(off, iconBytes)
|
||||
local pixels = decode_4bpp(bytes, w, iconH)
|
||||
rgba = bake_icon_rgba(pixels, pal, w, iconH)
|
||||
else
|
||||
rgba = string.rep(string.char(0, 0, 0, 0), w * iconH * 4)
|
||||
end
|
||||
put(cache, root .. "/icons/" .. sp .. ".rgba", rgba)
|
||||
put(cache, root .. "/icons/" .. sp .. ".rgba", icon_rgba(rom, sp, pals))
|
||||
picsWritten.icons = picsWritten.icons + 1
|
||||
|
||||
-- Front Pic (64x64 RGBA)
|
||||
|
||||
@@ -37,7 +37,9 @@ Versions.ROM_SIZE = 16777216
|
||||
-- setptrbyte each carry a leading byte plus a word (were shorter). The
|
||||
-- old sizes mis-decoded every instruction after one, so every cached
|
||||
-- script is stale.
|
||||
Versions.CACHE_VERSION = 113
|
||||
-- v114: pokemon/icons/412.rgba, the SPECIES_EGG menu icon — eggs were drawn
|
||||
-- with the icon of the species they hatch into.
|
||||
Versions.CACHE_VERSION = 114
|
||||
Versions.NATIVE_VERSION = 6
|
||||
Versions.OW_VERSION = 1
|
||||
Versions.ANIM_VERSION = 1
|
||||
|
||||
@@ -738,7 +738,7 @@ function BoxStorageUI.draw()
|
||||
local isHovered = (BoxStorageUI.cursorSlot == s and BoxStorageUI.mode ~= "party_drawer" and not BoxStorageUI.holdingMon)
|
||||
local bounceY = (isHovered and BoxStorageUI.hoverFrame == 1) and -2 or 0
|
||||
local f = (isHovered and BoxStorageUI.hoverFrame == 1) and 1 or 0
|
||||
local sp = Pokemon.speciesOf(mon)
|
||||
local sp = Pokemon.speciesOrEgg(mon)
|
||||
local icon = Pokemon.icon(sp)
|
||||
|
||||
if icon and icon.image then
|
||||
@@ -804,7 +804,7 @@ function BoxStorageUI.draw()
|
||||
|
||||
-- If holding a mon, draw floating mini-icon under hand cursor
|
||||
if BoxStorageUI.holdingMon then
|
||||
local hSp = Pokemon.speciesOf(BoxStorageUI.holdingMon)
|
||||
local hSp = Pokemon.speciesOrEgg(BoxStorageUI.holdingMon)
|
||||
local hIcon = Pokemon.icon(hSp)
|
||||
if hIcon and hIcon.image then
|
||||
local q = hIcon.quads and hIcon.quads[0]
|
||||
|
||||
@@ -446,7 +446,7 @@ local function ensure_slot_sprites(i, mon, selected)
|
||||
local maxHp = tonumber(mon.maxHp) or tonumber(mon.maxhp) or 1
|
||||
local hpLevel = get_hp_bar_level(hp, maxHp, mon.isEgg)
|
||||
|
||||
local icon = Pokemon.icon(Pokemon.speciesOf(mon))
|
||||
local icon = Pokemon.icon(Pokemon.speciesOrEgg(mon))
|
||||
local q0 = icon and icon.quads and icon.quads[0]
|
||||
if not slot.mon then
|
||||
local id = select(1, Oam.createSprite({
|
||||
|
||||
@@ -381,7 +381,7 @@ function PcChrome.drawPartyDrawer(party, partyCursor, hoverFrame, holdingSource)
|
||||
local isLeadPickedUp = (holdingSource and holdingSource.loc == "party" and holdingSource.slot == 1)
|
||||
local leadMon = (not isLeadPickedUp) and party[1]
|
||||
if leadMon then
|
||||
local sp = Pokemon.speciesOf(leadMon)
|
||||
local sp = Pokemon.speciesOrEgg(leadMon)
|
||||
local icon = Pokemon.icon(sp)
|
||||
if icon and icon.image then
|
||||
local isHovered = (partyCursor == 1)
|
||||
@@ -401,7 +401,7 @@ function PcChrome.drawPartyDrawer(party, partyCursor, hoverFrame, holdingSource)
|
||||
local isPickedUp = (holdingSource and holdingSource.loc == "party" and holdingSource.slot == p)
|
||||
local pMon = (not isPickedUp) and party[p]
|
||||
if pMon then
|
||||
local sp = Pokemon.speciesOf(pMon)
|
||||
local sp = Pokemon.speciesOrEgg(pMon)
|
||||
local icon = Pokemon.icon(sp)
|
||||
if icon and icon.image then
|
||||
local isHovered = (partyCursor == p)
|
||||
|
||||
@@ -125,7 +125,7 @@ function ReleaseSeq.draw()
|
||||
local curX = ReleaseSeq.startX
|
||||
local curY = ReleaseSeq.startY - (progress * 40) -- float upward 40px
|
||||
|
||||
local icon = ReleaseSeq.mon and Pokemon.icon(Pokemon.speciesOf(ReleaseSeq.mon))
|
||||
local icon = ReleaseSeq.mon and Pokemon.icon(Pokemon.speciesOrEgg(ReleaseSeq.mon))
|
||||
if icon and icon.image then
|
||||
local q = icon.quads and icon.quads[0]
|
||||
love.graphics.setColor(1, 1, 1, alpha)
|
||||
|
||||
@@ -42,6 +42,7 @@ local ROUND2_SAMPLE = {
|
||||
"data/generated/gba/move_relearner/bg.rgba",
|
||||
"data/generated/gba/pokemon/egg/hatch.rgba",
|
||||
"data/generated/gba/pokemon/front/412.rgba",
|
||||
"data/generated/gba/pokemon/icons/412.rgba",
|
||||
"data/generated/gba/native/layouts/alt_366.mid",
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env luajit
|
||||
-- An egg's menu icon is SPECIES_EGG's, as pret draws it from
|
||||
-- GetMonData(MON_DATA_SPECIES_OR_EGG) (pokefirered/src/pokemon.c:3245,
|
||||
-- party_menu.c:2655), not the icon of the species it will hatch into.
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
love = require("tests.love_stub")
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check = T.check
|
||||
|
||||
local Pokemon = require("src.core.game3.pokemon")
|
||||
Pokemon._names = { [25] = "PIKACHU", [172] = "PICHU" } -- a minimal pack, so no ROM is needed
|
||||
|
||||
check(Pokemon.speciesOrEgg({ species = 172, isEgg = true }) == 412, "a Pichu egg's icon is the EGG's")
|
||||
check(Pokemon.speciesOrEgg({ species = 172, egg = true }) == 412, "whichever egg flag it carries")
|
||||
check(Pokemon.speciesOrEgg({ species = 412 }) == 412, "an egg known only by the egg species stays one")
|
||||
check(Pokemon.speciesOrEgg({ species = 172, isEgg = false }) == 172, "a hatched mon keeps its species")
|
||||
check(Pokemon.speciesOrEgg({ species = 25 }) == 25, "and so does any other mon")
|
||||
check(Pokemon.speciesOrEgg(nil) == nil, "no mon, no species")
|
||||
|
||||
-- Every screen that draws a party or box mon's icon goes through it.
|
||||
local SITES = {
|
||||
{ "src/ui/game3/party_menu.lua", 1 },
|
||||
{ "src/ui/game3/box_storage_ui.lua", 2 },
|
||||
{ "src/ui/game3/pc_chrome.lua", 2 },
|
||||
{ "src/ui/game3/release_seq.lua", 1 },
|
||||
}
|
||||
for _, site in ipairs(SITES) do
|
||||
local f = assert(io.open(site[1], "rb"))
|
||||
local src = f:read("*a")
|
||||
f:close()
|
||||
local n = select(2, src:gsub("Pokemon%.speciesOrEgg%(", ""))
|
||||
check(n == site[2], ("%s picks %d icon(s) by speciesOrEgg (found %d)"):format(site[1], site[2], n))
|
||||
end
|
||||
|
||||
T.finish("game3_egg_icon_species_test")
|
||||
@@ -164,6 +164,7 @@ local ROUND2_KEYS = {
|
||||
"data/generated/gba/pokemon/egg/hatch.rgba",
|
||||
"data/generated/gba/pokemon/egg/shard.rgba",
|
||||
"data/generated/gba/pokemon/front/412.rgba",
|
||||
"data/generated/gba/pokemon/icons/412.rgba",
|
||||
-- src/trainer_card.c:265, :1454, :1560
|
||||
"data/generated/gba/trainer_card/front_0.rgba",
|
||||
"data/generated/gba/trainer_card/front_4_female.rgba",
|
||||
|
||||
@@ -95,6 +95,7 @@ local WANT = {
|
||||
"data/generated/gba/pokemon/egg/hatch.rgba",
|
||||
"data/generated/gba/pokemon/egg/shard.rgba",
|
||||
"data/generated/gba/pokemon/front/412.rgba",
|
||||
"data/generated/gba/pokemon/icons/412.rgba",
|
||||
"data/generated/gba/trainer_tower/manifest.lua",
|
||||
"data/generated/gba/trainer_tower/records_bg.rgba",
|
||||
"data/generated/gba/trainer_card/front_0.rgba",
|
||||
@@ -165,6 +166,8 @@ written["x/pokemon/egg/shard.rgba"] = "stub"
|
||||
written["x/pokemon/egg/manifest.lua"] = "stub"
|
||||
check(EggExtract.ready(stub, "x") == false, "egg ready() also wants the SPECIES_EGG front pic")
|
||||
written["x/pokemon/front/412.rgba"] = "stub"
|
||||
check(EggExtract.ready(stub, "x") == false, "egg ready() also wants the SPECIES_EGG icon")
|
||||
written["x/pokemon/icons/412.rgba"] = "stub"
|
||||
check(EggExtract.ready(stub, "x") == true, "egg ready() accepts the whole group")
|
||||
|
||||
local regionFiles = {}
|
||||
@@ -234,6 +237,7 @@ local SHEETS = {
|
||||
{ "pokemon/egg/hatch.rgba", 32, 128 },
|
||||
{ "pokemon/egg/shard.rgba", 32, 8 },
|
||||
{ "pokemon/front/412.rgba", 64, 64 },
|
||||
{ "pokemon/icons/412.rgba", 32, 64 },
|
||||
{ "trainer_tower/records_bg.rgba", 240, 160 },
|
||||
{ "trainer_card/front_0.rgba", 240, 160 },
|
||||
{ "trainer_card/front_4_female.rgba", 240, 160 },
|
||||
@@ -389,6 +393,8 @@ local eggPic = blobs["pokemon/front/412.rgba"]
|
||||
check(distinctColors(eggPic) > 4, "the EGG front pic is painted art")
|
||||
local _, _, _, eggCorner = pixel(eggPic, 64, 0, 0)
|
||||
eq(eggCorner, 0, "the EGG pic corner is transparent, as pic index 0 is")
|
||||
local eggIcon = blobs["pokemon/icons/412.rgba"]
|
||||
check(distinctColors(eggIcon) > 2, "the EGG menu icon is painted art")
|
||||
local eggMan = loadTable("pokemon/egg/manifest.lua")
|
||||
check(eggMan ~= nil and eggMan.hatch ~= nil and eggMan.hatch.frames == 4
|
||||
and eggMan.hatch.sheetHeight == 128 and eggMan.shard.sheetWidth == 32,
|
||||
|
||||
Reference in New Issue
Block a user