Merge pull request #2396 from thibautbus/fix/game3-egg-nickname

This commit is contained in:
bryanthaboi
2026-09-22 07:38:18 -04:00
committed by GitHub
21 changed files with 349 additions and 67 deletions
+19
View File
@@ -1160,8 +1160,18 @@ function Pokemon.replaceMove(mon, slot, newMoveId)
return old
end
-- An egg reads as the language's own EGG whatever its nickname holds: pret's
-- GetMonData(MON_DATA_NICKNAME) returns gText_EggNickname for any egg
-- (pokefirered/src/pokemon.c:3020). The stored nickname is only a placeholder
-- -- the cart's daycare writes タマゴ (daycare.c:1100), this engine "EGG".
local function eggName(mon)
if Pokemon.isEgg(mon) then return Strings("EGG") end
end
function Pokemon.displayMonName(mon)
if not mon then return "POKéMON" end
local egg = eggName(mon)
if egg then return egg end
local nick = mon.nickname
if type(nick) == "string" and nick ~= "" then return nick end
if mon.name and mon.name ~= "" then return mon.name end
@@ -1246,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"
@@ -1572,6 +1589,8 @@ end
function Pokemon.displayName(mon)
if not mon then return "?????" end
local egg = eggName(mon)
if egg then return egg end
if mon.nickname and mon.nickname ~= "" then return tostring(mon.nickname) end
-- Prefer pack name over host species string when we can resolve.
local sp = Pokemon.speciesOf(mon)
+4 -1
View File
@@ -79,10 +79,13 @@ local function boxedMon()
(tonumber(session.monBoxId) or 0) + 1, (tonumber(session.monBoxPos) or 0) + 1)
end
-- GetMonData(MON_DATA_NICKNAME) is gText_EggNickname for an egg
-- (pokefirered/src/pokemon.c:3020)
local function nicknameOf(mon)
if not mon then return "" end
if mon.nickname and mon.nickname ~= "" then return tostring(mon.nickname) end
local Pokemon = require("src.core.game3.pokemon")
if Pokemon.isEgg(mon) then return Strings("EGG") end
if mon.nickname and mon.nickname ~= "" then return tostring(mon.nickname) end
pcall(function()
if not Pokemon._names then Pokemon.install(nil) end
end)
+4 -2
View File
@@ -118,11 +118,13 @@ local function speciesFromNational(nat)
return (ok and tonumber(sp)) or nat
end
-- pokefirered/src/field_specials.c:1671
-- pokefirered/src/field_specials.c:1671; GetMonData(MON_DATA_NICKNAME) is
-- gText_EggNickname for an egg (pokemon.c:3020)
local function nicknameOf(mon)
if not mon then return "" end
if mon.nickname and mon.nickname ~= "" then return tostring(mon.nickname) end
local Pokemon = require("src.core.game3.pokemon")
if Pokemon.isEgg(mon) then return Strings("EGG") end
if mon.nickname and mon.nickname ~= "" then return tostring(mon.nickname) end
pcall(function()
if not Pokemon._names then Pokemon.install(nil) end
end)
@@ -137,8 +137,11 @@ local function speciesName(species)
return (Pokemon.name and Pokemon.name(species)) or ""
end
-- GetMonData(MON_DATA_NICKNAME) is gText_EggNickname for an egg
-- (pokefirered/src/pokemon.c:3020)
local function nicknameOf(mon)
if not mon then return "" end
if require("src.core.game3.pokemon").isEgg(mon) then return Strings("EGG") end
if mon.nickname and mon.nickname ~= "" then return tostring(mon.nickname) end
return speciesName(speciesOf(mon))
end
+3
View File
@@ -212,8 +212,11 @@ local function cryFinished(s)
return done and true or false
end
-- pokefirered/src/trade_scene.c:1239 GetMonData(MON_DATA_NICKNAME), which is
-- gText_EggNickname for an egg (pokemon.c:3020)
local function nameOf(mon)
if not mon then return "" end
if isEgg(mon) then return Strings("EGG") end
local nick = mon.nickname or mon.name
if type(nick) == "string" and nick ~= "" then return nick end
local ok, Pokemon = pcall(require, "src.core.game3.pokemon")
+2
View File
@@ -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",
+11 -1
View File
@@ -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
+18 -20
View File
@@ -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)
+3 -1
View File
@@ -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
+2 -2
View File
@@ -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]
+9 -2
View File
@@ -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({
@@ -2111,10 +2111,17 @@ local function draw_filled_slot(i, mon, selected)
local info = slot_info(i)
local desc = slot_description(i, mon)
PartyChrome.drawSlot(win.kind, win.left, win.top, selected, desc ~= nil)
-- pokefirered/src/party_menu.c:781 DisplayPartyPokemonData: an egg's slot
-- has no HP frame and shows only its nickname (gText_EggNickname).
local isEgg = Pokemon.isEgg(mon)
PartyChrome.drawSlot(win.kind, win.left, win.top, selected, desc ~= nil or isEgg)
local name = Pokemon.displayName(mon)
party_print(name, baseX + info.nick[1], baseY + info.nick[2], 56)
if isEgg then
if desc then party_print(desc, baseX + info.desc[1], baseY + info.desc[2], 64) end
return
end
party_print("Lv" .. tostring(mon.level or 0), baseX + info.level[1], baseY + info.level[2], 32)
local gender = mon.gender or (Pokemon.gender and Pokemon.gender(mon.species, mon.personality))
+34 -28
View File
@@ -205,8 +205,8 @@ function PcChrome.drawLeftDataPanel(hoveredMon, hoverFrame)
if not hoveredMon then return end
-- 1. Front Sprite in TV Screen (X: 10..73, Y: 19..80, W: 64, H: 61)
local sp = Pokemon.speciesOf(hoveredMon)
local sprite = Pokemon.frontPic(sp)
-- pokefirered/src/pokemon_storage_system_data.c:1034, :1057 MON_DATA_SPECIES_OR_EGG
local sprite = Pokemon.frontPic(Pokemon.speciesOrEgg(hoveredMon))
if sprite and sprite.image then
love.graphics.setColor(1, 1, 1, 1)
local sw, sh = sprite.image:getDimensions()
@@ -229,6 +229,10 @@ function PcChrome.drawLeftDataPanel(hoveredMon, hoverFrame)
end
local lvl = hoveredMon.level or 5
local gender = hoveredMon.gender or (hoveredMon.personality and ((hoveredMon.personality % 256 < 127) and "F" or "M"))
-- pokefirered/src/pokemon_storage_system_data.c:1091: an egg shows only
-- gText_EggNickname; the species, gender/level and item lines stay blank.
local isEgg = Pokemon.isEgg(hoveredMon)
if isEgg then nick = Strings("EGG") end
-- Line 1: Nickname or Species Name (FONT_NORMAL, Y: 88)
FrlgFont.draw(nick:sub(1, 10), 6, 88, {
@@ -236,32 +240,34 @@ function PcChrome.drawLeftDataPanel(hoveredMon, hoverFrame)
colors = FrlgFont.COLOR.WHITE
})
-- Line 2: /Species Name (FONT_NORMAL, Y: 102)
FrlgFont.draw("/" .. spName:sub(1, 10), 6, 102, {
small = false,
colors = FrlgFont.COLOR.WHITE
})
if not isEgg then
-- Line 2: /Species Name (FONT_NORMAL, Y: 102)
FrlgFont.draw("/" .. spName:sub(1, 10), 6, 102, {
small = false,
colors = FrlgFont.COLOR.WHITE
})
-- Line 3: Gender & Level (FONT_NORMAL, Y: 116)
if gender == "M" or gender == "male" then
FrlgFont.draw("♂", 6, 116, { small = false, colors = FrlgFont.COLOR.MALE })
FrlgFont.draw(Strings("Lv%s", tostring(lvl)), 18, 116, { small = false, colors = FrlgFont.COLOR.WHITE })
elseif gender == "F" or gender == "female" then
FrlgFont.draw("♀", 6, 116, { small = false, colors = FrlgFont.COLOR.FEMALE })
FrlgFont.draw(Strings("Lv%s", tostring(lvl)), 18, 116, { small = false, colors = FrlgFont.COLOR.WHITE })
else
FrlgFont.draw(Strings("Lv%s", tostring(lvl)), 6, 116, { small = false, colors = FrlgFont.COLOR.WHITE })
end
-- Line 3: Gender & Level (FONT_NORMAL, Y: 116)
if gender == "M" or gender == "male" then
FrlgFont.draw("♂", 6, 116, { small = false, colors = FrlgFont.COLOR.MALE })
FrlgFont.draw(Strings("Lv%s", tostring(lvl)), 18, 116, { small = false, colors = FrlgFont.COLOR.WHITE })
elseif gender == "F" or gender == "female" then
FrlgFont.draw("♀", 6, 116, { small = false, colors = FrlgFont.COLOR.FEMALE })
FrlgFont.draw(Strings("Lv%s", tostring(lvl)), 18, 116, { small = false, colors = FrlgFont.COLOR.WHITE })
else
FrlgFont.draw(Strings("Lv%s", tostring(lvl)), 6, 116, { small = false, colors = FrlgFont.COLOR.WHITE })
end
-- Line 4: Held Item Name (if holding an item) (FONT_SMALL, Y: 132)
local held = hoveredMon.heldItem or hoveredMon.item
if held and held > 0 then
local heldName = ItemsData.displayName(held)
if heldName and heldName ~= "" and heldName ~= "NONE" then
FrlgFont.draw(heldName:sub(1, 10), 6, 132, {
small = true,
colors = FrlgFont.COLOR.WHITE
})
-- Line 4: Held Item Name (if holding an item) (FONT_SMALL, Y: 132)
local held = hoveredMon.heldItem or hoveredMon.item
if held and held > 0 then
local heldName = ItemsData.displayName(held)
if heldName and heldName ~= "" and heldName ~= "NONE" then
FrlgFont.draw(heldName:sub(1, 10), 6, 132, {
small = true,
colors = FrlgFont.COLOR.WHITE
})
end
end
end
@@ -381,7 +387,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 +407,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)
+1 -1
View File
@@ -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)
+6 -5
View File
@@ -56,7 +56,7 @@ end
-- pokefirered/src/pokemon_summary_screen.c:5180
local function play_mon_cry()
local mon = current_mon()
if not mon or mon.isEgg then return end
if not mon or Pokemon.isEgg(mon) then return end
local species = Pokemon.speciesOf(mon)
if not species then return end
local okA, Audio = pcall(require, "src.core.game3.audio")
@@ -153,7 +153,7 @@ function SummaryMenu.openMenu(party, startIndex, opts)
SummaryMenu._slide.active = false
local mon = current_mon()
if mon and mon.isEgg then
if mon and Pokemon.isEgg(mon) then
SummaryMenu._page = PAGE_EGG
elseif SummaryMenu._mode == "select_move" then
SummaryMenu._page = PAGE_MOVES_INFO
@@ -194,7 +194,7 @@ local function change_mon(delta)
SummaryMenu._moveCursor = 1
SummaryMenu._swapSlot = nil
local mon = current_mon()
if mon and mon.isEgg then
if mon and Pokemon.isEgg(mon) then
SummaryMenu._page = PAGE_EGG
elseif SummaryMenu._page == PAGE_EGG then
SummaryMenu._page = PAGE_INFO
@@ -676,7 +676,8 @@ local function draw_page_moves(mon, isDetail)
end
local function draw_page_egg(mon)
local species = Pokemon.speciesOf(mon)
-- pokefirered/src/pokemon_summary_screen.c:4016 MON_DATA_SPECIES_OR_EGG
local species = Pokemon.speciesOrEgg(mon)
local nx, ny = cxy("name", 40, 18)
draw_text(Strings("EGG"), nx, ny, 64, "NORMAL")
@@ -759,7 +760,7 @@ function SummaryMenu.draw()
end
-- 2. OVERLAY LAYER (Foreground elements anchored to screen coordinates)
draw_top_bar_text(SummaryMenu._page, mon.isEgg)
draw_top_bar_text(SummaryMenu._page, Pokemon.isEgg(mon))
if SummaryMenu._page == PAGE_EGG then
draw_page_egg(mon)
else
+5 -4
View File
@@ -257,23 +257,24 @@ function TradeSceneUi.draw()
love.graphics.draw(art.linkMonShadow, 129 - w2, (s.crossMonBy or -10) + (s.crossY2b or 0) - h2)
end
-- pokefirered/src/trade_scene.c:757 draws every mon by MON_DATA_SPECIES_OR_EGG
-- pokefirered/src/trade_scene.c:1541
if s.crossMonVisible then
draw_mon(Pokemon.frontPic(s.offer and (s.offer.species or s.offer.speciesId)),
draw_mon(Pokemon.frontPic(Pokemon.speciesOrEgg(s.offer)),
60, 192 + (s.monY2a or 0), 1)
draw_mon(Pokemon.frontPic(s.received and (s.received.species or s.received.speciesId)),
draw_mon(Pokemon.frontPic(Pokemon.speciesOrEgg(s.received)),
180, -32 + (s.monY2b or 0), 1)
end
-- pokefirered/src/trade_scene.c:772
if s.playerVisible then
draw_mon(Pokemon.frontPic(s.offer and (s.offer.species or s.offer.speciesId)),
draw_mon(Pokemon.frontPic(Pokemon.speciesOrEgg(s.offer)),
120 + (s.monX2 or 0), 60, s.monScale or 1)
end
-- pokefirered/src/trade_scene.c:1716
if s.partnerVisible then
draw_mon(Pokemon.frontPic(s.received and (s.received.species or s.received.speciesId)),
draw_mon(Pokemon.frontPic(Pokemon.speciesOrEgg(s.received)),
120, 60, 1)
end
@@ -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,44 @@
#!/usr/bin/env luajit
-- An egg is named by the language's own EGG, as pret's GetMonData(MON_DATA_NICKNAME)
-- does for any egg (pokefirered/src/pokemon.c:3020): the party and every screen
-- that names a mon through Pokemon.displayName / displayMonName show it, whatever
-- placeholder the egg's nickname stores.
package.path = "./?.lua;./?/init.lua;" .. package.path
love = require("tests.love_stub")
local T = require("tests.harness")
local check = T.check
local Strings = require("src.core.Strings")
local Pokemon = require("src.core.game3.pokemon")
-- A minimal pack, so no ROM is needed.
Pokemon._names = { [25] = "PIKACHU", [172] = "PICHU" }
local egg = { species = 172, isEgg = true, nickname = "EGG", name = "EGG" }
local hatched = { species = 172, isEgg = false, nickname = "", name = "PICHU" }
local named = { species = 25, nickname = "SPARKY" }
Strings.load({})
check(Pokemon.displayName(egg) == "EGG", "an egg reads EGG with no catalog")
check(Pokemon.displayMonName(egg) == "EGG", "in both name helpers")
Strings.load({ strings = { EGG = "OEUF" } })
check(Pokemon.displayName(egg) == "OEUF", "an egg follows the catalog's EGG")
check(Pokemon.displayMonName(egg) == "OEUF", "in both name helpers")
check(egg.nickname == "EGG", "without its stored nickname being rewritten")
local bare = { species = 172, isEgg = true, nickname = "" }
check(Pokemon.displayName(bare) == "OEUF", "an egg with no nickname at all is still named EGG")
local species_only = { species = 412, nickname = "" }
check(Pokemon.displayName(species_only) == "OEUF", "and so is one known only by the egg species")
-- (looking up a species the pack lacks reloads it; put the minimal one back)
Pokemon._names = { [25] = "PIKACHU", [172] = "PICHU" }
check(Pokemon.displayName(hatched) == "PICHU", "a hatched egg goes back to its species name")
check(Pokemon.displayName(named) == "SPARKY", "and a nicknamed mon keeps its nickname")
check(Pokemon.displayMonName(named) == "SPARKY", "in both name helpers")
Strings.load({})
T.finish("game3_egg_display_name_test")
@@ -0,0 +1,38 @@
#!/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 (the PC
-- chrome's third call is its hovered-mon front pic).
local SITES = {
{ "src/ui/game3/party_menu.lua", 1 },
{ "src/ui/game3/box_storage_ui.lua", 2 },
{ "src/ui/game3/pc_chrome.lua", 3 },
{ "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 draws %d mon(s) by speciesOrEgg (found %d)"):format(site[1], site[2], n))
end
T.finish("game3_egg_icon_species_test")
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env luajit
-- The screens that show a single mon draw an egg as an egg, as pret does with
-- GetMonData(MON_DATA_SPECIES_OR_EGG) and MON_DATA_NICKNAME
-- (pokefirered/src/pokemon.c:3245, :3020): the party slots, the PC's hovered-mon
-- panel, the summary's egg page, the trade scene, and the script string buffers.
package.path = "./?.lua;./?/init.lua;" .. package.path
local T = require("tests.harness")
local check = T.check
require("src.core.GameVersion").set("firered")
package.loaded["src.core.game3.audio"] = setmetatable({}, { __index = function() return function() end end })
local gfx = setmetatable({}, { __index = function() return function() end end })
gfx.newQuad = function() return {} end
gfx.newImage = function() return nil end
gfx.getWidth = function() return 240 end
gfx.getHeight = function() return 160 end
_G.love = { graphics = gfx, timer = { getTime = function() return 0 end } }
local Strings = require("src.core.Strings")
local Pokemon = require("src.core.game3.pokemon")
Pokemon._names = { [25] = "PIKACHU", [172] = "PICHU" } -- a minimal pack, so no ROM is needed
Strings.load({ strings = { EGG = "OEUF" } })
local pics = {}
Pokemon.frontPic = function(species)
pics[#pics + 1] = species
return { image = { getDimensions = function() return 64, 64 end }, w = 64, h = 64 }
end
local texts = {}
local FrlgFont = require("src.ui.game3.frlg_font")
FrlgFont.draw = function(text) texts[#texts + 1] = tostring(text) end
local function egg()
return { species = 172, isEgg = true, nickname = "EGG", name = "EGG", level = 5,
heldItem = 1, gender = "M", personality = 1, otName = "RED", otId = 1,
hp = 1, maxHp = 1, moves = {}, pp = {} }
end
local function has(list, value)
for _, v in ipairs(list) do if v == value then return true end end
return false
end
-- pokefirered/src/pokemon_storage_system_data.c:1034, :1091
local PcChrome = require("src.ui.game3.pc_chrome")
PcChrome.ensure = function() end
PcChrome.drawWaveforms = function() end
pics, texts = {}, {}
PcChrome.drawLeftDataPanel(egg(), 0)
check(pics[1] == 412, "the PC panel draws an egg's front pic as the EGG's (got " .. tostring(pics[1]) .. ")")
check(texts[1] == "OEUF", "and names it by the language's EGG (got " .. tostring(texts[1]) .. ")")
check(not has(texts, "/PICHU") and not has(texts, "Lv5"),
"with no species, level or item line: " .. table.concat(texts, " | "))
pics, texts = {}, {}
PcChrome.drawLeftDataPanel({ species = 172, nickname = "PICHU", level = 5, gender = "M" }, 0)
check(pics[1] == 172 and has(texts, "/PICHU") and has(texts, "Lv5"),
"a hatched mon keeps its pic, species and level lines")
-- pokefirered/src/party_menu.c:781 DisplayPartyPokemonData, :2197 sSlotTilemap_MainNoHP
local PartyMenu = require("src.ui.game3.party_menu")
local PartyChrome = require("src.ui.game3.party_chrome")
local slotsHidingHp = {}
PartyChrome.drawSlot = function(kind, _, _, _, hideHp)
if kind ~= "empty" then slotsHidingHp[#slotsHidingHp + 1] = hideHp and true or false end
end
texts, slotsHidingHp = {}, {}
PartyMenu.show({ { species = 25, nickname = "SPARKY", level = 12, gender = "M", hp = 30, maxHp = 30 }, egg() })
PartyMenu.draw()
PartyMenu.close()
check(has(texts, "OEUF") and has(texts, "SPARKY"), "the party names both mons: " .. table.concat(texts, " | "))
check(not has(texts, "Lv5") and has(texts, "Lv12"), "an egg's party slot shows no level, the other one does")
check(slotsHidingHp[1] == false and slotsHidingHp[2] == true,
"and an egg's slot has no HP frame (hideHp = " .. tostring(slotsHidingHp[1]) .. ", " .. tostring(slotsHidingHp[2]) .. ")")
-- pokefirered/src/pokemon_summary_screen.c:4016
local SummaryMenu = require("src.ui.game3.summary_menu")
local SummaryChrome = require("src.ui.game3.summary_chrome")
for k, v in pairs(SummaryChrome) do
if type(v) == "function" and k:match("^draw") then SummaryChrome[k] = function() end end
end
pics = {}
SummaryMenu.openMenu({ egg() }, 1, { page = 0 })
SummaryMenu.draw()
SummaryMenu.close()
check(has(pics, 412) and not has(pics, 172),
"the summary's egg page draws the EGG's front pic (got " .. table.concat(pics, ",") .. ")")
pics = {}
local flagOnly = egg()
flagOnly.isEgg, flagOnly.egg = nil, true
SummaryMenu.openMenu({ flagOnly }, 1, { page = 0 })
SummaryMenu.draw()
SummaryMenu.close()
check(has(pics, 412) and not has(pics, 172), "for any egg flag (got " .. table.concat(pics, ",") .. ")")
-- pokefirered/src/trade_scene.c:757, :1239
local TradeSceneUi = require("src.ui.game3.trade_scene")
local offer, received = egg(), { species = 25, nickname = "SPARKY" }
TradeSceneUi._core = { state = function()
return { playerVisible = true, partnerVisible = true, offer = offer, received = received }
end }
pics = {}
TradeSceneUi.draw()
check(pics[1] == 412 and pics[2] == 25,
"the trade scene draws a sent egg as the EGG (got " .. table.concat(pics, ",") .. ")")
TradeSceneUi._core = nil
local savedLove = _G.love
_G.love = nil
local TradeScene = require("src.core.game3.trade_scene")
TradeScene.play(offer, received, nil, {})
local st = TradeScene.state()
check(st.sentName == "OEUF", "and names it by the language's EGG (got " .. tostring(st.sentName) .. ")")
check(st.recvName == "SPARKY", "while the other mon keeps its nickname")
_G.love = savedLove
-- pokefirered/src/field_specials.c:1671 BufferMonNickname, daycare.c:1216
local Ctx = require("src.core.game3.scripting.ctx")
local Flags = require("src.core.game3.scripting.flags")
local Std = require("src.core.game3.scripting.stdscripts")
local Natives = require("src.core.game3.scripting.natives")
package.loaded["src.core.game3.runtime"] = {
getSession = function() return { party = { egg() } } end,
isActive = function() return true end,
}
local ctx = Ctx.new({})
ctx.mode, ctx.status = "bytecode", "running"
Flags.setVar(nil, ctx, 0x8004, 0)
Natives.special(ctx, Std.SPECIAL.GetSelectedMonNicknameAndSpecies, { log = function() end })
check(ctx.stringVars and ctx.stringVars[1] == "OEUF",
"a script buffering an egg's nickname gets the language's EGG (got "
.. tostring(ctx.stringVars and ctx.stringVars[1]) .. ")")
Strings.load({})
T.finish("game3_egg_screens_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",
+6
View File
@@ -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,