mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-15 07:41:21 +02:00
Merge pull request #1079 from MaxTomahawk/feat/mod-pokemon-icon
feat(mods): expose canonical Pokémon icon presentation
This commit is contained in:
@@ -495,6 +495,21 @@ Neither hook needs a `Runtime.wantsHook` guard before calling it: `Hooks:call`
|
||||
already falls straight through to the vanilla function when no mod has
|
||||
wrapped the name, at negligible cost.
|
||||
|
||||
## Detached Pokémon icon presentation
|
||||
|
||||
`mod.ui.PokemonIcon.draw(game, summary, x, y, opts)` draws the same party icon
|
||||
the native Party menu would resolve without exposing a live Pokémon record or
|
||||
the private Party menu. `summary` is the detached data-only shape
|
||||
`{ species = string, hp = integer, maxHp = integer }`; `opts.selected` and
|
||||
`opts.counter` optionally request the native selected-icon animation phase.
|
||||
|
||||
The engine retains icon ownership. Content registered through
|
||||
`mod.content.icons`, species `icon` definitions, asset overrides, and the
|
||||
public `pokemon.icon` hook therefore continue to compose. Invalid summaries
|
||||
return `false, code, message` and draw nothing. The helper is presentation
|
||||
only: it does not expose moves, status, checkpoint payloads, or mutable party
|
||||
state.
|
||||
|
||||
## Shared date and time presentation
|
||||
|
||||
The global Options menu owns `DATE FORMAT` (`DEVICE`, `DD-MM-YYYY`,
|
||||
|
||||
@@ -12,6 +12,7 @@ local MODULES = {
|
||||
QuantityBox = "src.ui.QuantityBox",
|
||||
NamingScreen = "src.ui.NamingScreen",
|
||||
PicBox = "src.ui.PicBox",
|
||||
PokemonIcon = "src.ui.PokemonIcon",
|
||||
TextBox = "src.render.TextBox",
|
||||
Font = "src.render.Font",
|
||||
Theme = "src.ui.Theme",
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
-- Public read-only Pokemon icon presentation for detached summaries.
|
||||
-- Resolution and rendering deliberately stay engine-owned: PartyMenu already
|
||||
-- composes content icon registrations, per-species definitions, asset
|
||||
-- overrides, and the pokemon.icon hook in one canonical path.
|
||||
|
||||
local PartyMenu = require("src.ui.PartyMenu")
|
||||
|
||||
local PokemonIcon = {}
|
||||
|
||||
local function finite(value)
|
||||
return type(value) == "number" and value == value
|
||||
and value ~= math.huge and value ~= -math.huge
|
||||
end
|
||||
|
||||
local function integer(value, minimum)
|
||||
return finite(value) and value % 1 == 0 and value >= minimum
|
||||
end
|
||||
|
||||
function PokemonIcon.draw(game, summary, x, y, opts)
|
||||
opts = type(opts) == "table" and opts or {}
|
||||
if type(game) ~= "table" or type(summary) ~= "table"
|
||||
or type(summary.species) ~= "string" or summary.species == ""
|
||||
or not integer(summary.hp, 0) or not integer(summary.maxHp, 1)
|
||||
or summary.hp > summary.maxHp or not finite(x) or not finite(y)
|
||||
or (opts.selected ~= nil and type(opts.selected) ~= "boolean")
|
||||
or (opts.counter ~= nil and not finite(opts.counter)) then
|
||||
return false, "invalid_pokemon_preview",
|
||||
"Pokemon icon presentation needs species and valid captured HP values."
|
||||
end
|
||||
local ok, message = pcall(PartyMenu.drawIcon, game, {
|
||||
species = summary.species,
|
||||
hp = summary.hp,
|
||||
stats = { hp = summary.maxHp },
|
||||
}, x, y, opts.selected == true, opts.counter or 0)
|
||||
if not ok then
|
||||
return false, "pokemon_icon_failed", tostring(message)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return PokemonIcon
|
||||
@@ -0,0 +1,60 @@
|
||||
-- Public read-only Pokemon icon presentation delegates to the same resolver
|
||||
-- PartyMenu uses, so content registrations and pokemon.icon hooks compose.
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.modkit")
|
||||
local PartyMenu = require("src.ui.PartyMenu")
|
||||
|
||||
local FIXTURE = {
|
||||
["mods/icon_probe/manifest.json"] = [[{
|
||||
"id": "icon_probe",
|
||||
"name": "Icon Probe",
|
||||
"version": "1.0.0",
|
||||
"entry": "main.lua",
|
||||
"api": 2
|
||||
}]],
|
||||
["mods/icon_probe/main.lua"] = [[
|
||||
local mod = ...
|
||||
mod.exports.icon = mod.ui.PokemonIcon
|
||||
]],
|
||||
}
|
||||
|
||||
local run = T.sdk.loadMods({ "mods/icon_probe" }, { fs = T.sdk.memfs(FIXTURE) })
|
||||
T.eq(#run.errors, 0, "fixture mod loads cleanly")
|
||||
local icon = run.loader.exports.icon_probe.icon
|
||||
T.eq(type(icon), "table", "mod.ui exposes the PokemonIcon helper")
|
||||
T.eq(type(icon.draw), "function", "PokemonIcon exposes a draw operation")
|
||||
|
||||
local original = PartyMenu.drawIcon
|
||||
local call
|
||||
PartyMenu.drawIcon = function(game, mon, x, y, selected, counter)
|
||||
call = { game = game, mon = mon, x = x, y = y,
|
||||
selected = selected, counter = counter }
|
||||
end
|
||||
|
||||
local game = { data = {} }
|
||||
local drawn, code = icon.draw(game, {
|
||||
species = "PIKACHU", hp = 4, maxHp = 10,
|
||||
}, 8, 16, { selected = true, counter = 7 })
|
||||
T.eq(drawn, true, "valid detached Pokemon summary is drawable")
|
||||
T.eq(code, nil, "valid summary has no rejection code")
|
||||
T.check(call and call.game == game, "helper delegates with the live game")
|
||||
T.eq(call.mon.species, "PIKACHU", "species reaches the shared party resolver")
|
||||
T.eq(call.mon.hp, 4, "captured current HP reaches icon animation semantics")
|
||||
T.eq(call.mon.stats.hp, 10, "captured maximum HP reaches icon animation semantics")
|
||||
T.eq(call.selected, true, "selection state is presentation-only")
|
||||
T.eq(call.counter, 7, "animation counter is presentation-only")
|
||||
|
||||
call = nil
|
||||
local bad, badCode = icon.draw(game, {
|
||||
species = "PIKACHU", hp = 11, maxHp = 10,
|
||||
}, 0, 0)
|
||||
T.eq(bad, false, "invalid detached summary fails closed")
|
||||
T.eq(badCode, "invalid_pokemon_preview", "invalid summary has a stable error")
|
||||
T.eq(call, nil, "invalid summary never reaches renderer internals")
|
||||
|
||||
PartyMenu.drawIcon = original
|
||||
run.release()
|
||||
|
||||
T.finish("pokemon_icon")
|
||||
Reference in New Issue
Block a user