mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-20 12:40:21 +02:00
feat: expose public Pokemon icon presentation
This commit is contained in:
@@ -426,3 +426,18 @@ platform-bridge mod bundled only with that build's launcher, for example).
|
|||||||
Neither hook needs a `Runtime.wantsHook` guard before calling it: `Hooks:call`
|
Neither hook needs a `Runtime.wantsHook` guard before calling it: `Hooks:call`
|
||||||
already falls straight through to the vanilla function when no mod has
|
already falls straight through to the vanilla function when no mod has
|
||||||
wrapped the name, at negligible cost.
|
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.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ local MODULES = {
|
|||||||
QuantityBox = "src.ui.QuantityBox",
|
QuantityBox = "src.ui.QuantityBox",
|
||||||
NamingScreen = "src.ui.NamingScreen",
|
NamingScreen = "src.ui.NamingScreen",
|
||||||
PicBox = "src.ui.PicBox",
|
PicBox = "src.ui.PicBox",
|
||||||
|
PokemonIcon = "src.ui.PokemonIcon",
|
||||||
TextBox = "src.render.TextBox",
|
TextBox = "src.render.TextBox",
|
||||||
Font = "src.render.Font",
|
Font = "src.render.Font",
|
||||||
Theme = "src.ui.Theme",
|
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