Compare commits

...

3 Commits

Author SHA1 Message Date
bryanthaboi b9a89c1fd2 Merge pull request #434 from johnjohto/fuchsia-swamp-trainer-support-upstream
Support custom trainer palettes and base portraits
2026-07-30 11:23:56 -04:00
johnjohto 17eee1ca0e Test custom trainer portrait support 2026-07-30 00:00:36 -04:00
johnjohto 7aa06942be Support custom trainer palettes and base portraits 2026-07-29 23:59:59 -04:00
4 changed files with 53 additions and 2 deletions
+23 -1
View File
@@ -188,6 +188,27 @@ local function namedPalette(data, name)
return { name = key, colors = colors }
end
-- Custom trainer portraits can opt into the same Advanced OBJ palette source
-- as their overworld walker. Vanilla trainers preserve the hardware-faithful
-- MEWMON fallback used during the battle introduction.
function BattleState.trainerPalette(data, trainer)
local source = trainer and trainer.paletteSource
if source then
local PaletteFX = require("src.render.PaletteFX")
local colors, group = PaletteFX.spriteObp({ paletteSource = source }, trainer.id)
if colors then
return { name = "trainer:" .. source .. ":" .. tostring(group), colors = colors }
end
end
return namedPalette(data, "MEWMON")
end
function BattleState.trainerPicPath(data, trainer)
if trainer and trainer.pic then return trainer.pic end
local base = trainer and trainer.basePic and data.trainers[trainer.basePic]
return base and base.pic or nil
end
-- The battle-BGP fade variant of a pic (AnimationFlashScreen and the
-- SetAnimationBGPalette effects remap the four BG shades; on the SGB
-- the colorizer then colors the REMAPPED shade, so a faded pic shows
@@ -608,7 +629,8 @@ function BattleState.newTrainer(game, oppClass, partyIndex)
-- MonsterPalettes[0] = PAL_MEWMON -- InitBattleCommon zeroes
-- wEnemyMonSpecies2 before the intro's SET_PAL_BATTLE
-- (engine/battle/core.asm:6682, engine/gfx/palettes.asm SetPal_Battle)
self.trainerPic = getImage(self.trainer.pic, namedPalette(game.data, "MEWMON"))
self.trainerPic = getImage(BattleState.trainerPicPath(game.data, self.trainer),
BattleState.trainerPalette(game.data, self.trainer))
self.introText = Strings("%s wants\nto fight!", self.trainer.name)
return self
end
+9
View File
@@ -561,6 +561,11 @@ R.trainers = {
index = f.opt(f.int(0, 255)),
-- unused vanilla classes ship without a pic, so it cannot be required
pic = f.opt(f.path),
-- Optional Advanced-mode OBJ palette source for a custom trainer portrait.
-- It follows the same ROM crosswalk form as sprites.paletteSource.
paletteSource = f.opt(f.str),
-- Reuse a base trainer class's portrait without redistributing its asset.
basePic = f.opt(f.id("trainers")),
baseMoney = f.opt(f.int(0)),
parties = f.list(f.list(f.rec{ level = f.int(1),
species = f.id("pokemon") })),
@@ -580,6 +585,10 @@ R.sprites = {
frames = f.int(1),
walker = f.opt(f.bool),
trueColor = f.opt(f.bool),
-- Mod art can opt into an existing ROM sprite's Advanced-mode OBJ
-- palette assignment without claiming that the image itself came from
-- the ROM (which is what `source` documents on imported records).
paletteSource = f.opt(f.str),
},
example = 'mod.content.sprites:register("SPRITE_HERO", { image = "...", frames = 6 })',
}
+1 -1
View File
@@ -537,7 +537,7 @@ end
function PaletteFX.spriteObp(spriteDef, seed)
local pack = PaletteFX.gbcPack()
local w = pack and pack.world
local src = spriteDef and spriteDef.source
local src = spriteDef and (spriteDef.paletteSource or spriteDef.source)
if not (w and src) then return nil end
local idx = tonumber(src:match("%[(%d+)%]"))
-- RedBikeSprite loads outside SpriteSheetPointerTable
+20
View File
@@ -11,6 +11,7 @@ local Font = require("src.render.Font")
Font.load(Data)
local BattleState = require("src.battle.BattleState")
local PaletteFX = require("src.render.PaletteFX")
local Catching = require("src.battle.Catching")
local Damage = require("src.battle.Damage")
local Events = require("src.mods.Events")
@@ -327,6 +328,25 @@ end
-- ------- ai_classes: brains and layer records
-- ------- custom trainer portraits: palette sources and base portraits
do
local oldMode = PaletteFX.mode
PaletteFX.mode = "redpp"
local custom = { id = "TEST_TRAINER",
paletteSource = "ROM:SpriteSheetPointerTable[21]" }
local pal = BattleState.trainerPalette(Data, custom)
local expected = PaletteFX.spriteObp({ paletteSource = custom.paletteSource }, custom.id)
PaletteFX.mode = oldMode
check(pal and expected and pal.colors[3][1] == expected[3][1]
and pal.colors[3][2] == expected[3][2]
and pal.colors[3][3] == expected[3][3],
"a custom trainer portrait resolves its Advanced OBJ palette source")
check(BattleState.trainerPicPath(Data, { basePic = "OPP_ENGINEER" })
== Data.trainers.OPP_ENGINEER.pic,
"a custom trainer can reuse a base trainer portrait by id")
end
do
Data.ai_classes = { OPP_YOUNGSTER = { brain = function(battle)
return { id = "SPLASH", pp = 1, brained = true }