From 858c5bc9b8e45d1ef863ff2169b4d004d511e132 Mon Sep 17 00:00:00 2001 From: johnjohto Date: Fri, 31 Jul 2026 09:05:19 -0400 Subject: [PATCH] Fix true-color evolution and trade sprites --- src/ui/EvolutionState.lua | 34 +++++++---- src/ui/TradeAnim.lua | 32 ++++++++--- .../evolution_true_color_bug494_test.lua | 56 +++++++++++++++++++ tests/parity_true_color_ui.lua | 39 +++++++++++++ 4 files changed, 141 insertions(+), 20 deletions(-) create mode 100644 tests/drivers/evolution_true_color_bug494_test.lua diff --git a/src/ui/EvolutionState.lua b/src/ui/EvolutionState.lua index fab1ac00..5954d553 100644 --- a/src/ui/EvolutionState.lua +++ b/src/ui/EvolutionState.lua @@ -48,11 +48,11 @@ end local FLASH_FRAMES = 220 local function frontSprite(game, species, mon) - local path = require("src.pokemon.Sprites").path(game.data, species, "front", - { mon = mon, kind = "evolution" }) - if not path then return nil end + local path, trueColor = require("src.pokemon.Sprites").path( + game.data, species, "front", { mon = mon, kind = "evolution" }) + if not path then return nil, false end local ok, img = pcall(love.graphics.newImage, path) - return ok and img or nil + return ok and img or nil, ok and trueColor or false end function EvolutionState.new(game, mon, newSpecies, onDone, via) @@ -69,8 +69,8 @@ function EvolutionState.new(game, mon, newSpecies, onDone, via) -- wForceEvolution clear and so honour B (#290, #213). self.cancelable = (via ~= "TRADE" and via ~= "ITEM") self.oldName = mon.nickname or game.data.pokemon[mon.species].name - self.oldSprite = frontSprite(game, mon.species, mon) - self.newSprite = frontSprite(game, newSpecies, mon) + self.oldSprite, self.oldSpriteTrueColor = frontSprite(game, mon.species, mon) + self.newSprite, self.newSpriteTrueColor = frontSprite(game, newSpecies, mon) self.t = 0 self.done = false self.canceled = false @@ -127,18 +127,30 @@ function EvolutionState:draw() love.graphics.rectangle("fill", 0, 0, 160, 144) -- accelerating flash between the two forms - local sprite + local sprite, spriteTrueColor if self.done then -- a cancelled evolution settles back on the original form - sprite = self.canceled and self.oldSprite or self.newSprite + if self.canceled then + sprite, spriteTrueColor = self.oldSprite, self.oldSpriteTrueColor + else + sprite, spriteTrueColor = self.newSprite, self.newSpriteTrueColor + end else local period = math.max(4, 28 - math.floor(self.t / 40) * 6) local showNew = math.floor(self.t / period) % 2 == 1 - sprite = showNew and self.newSprite or self.oldSprite + if showNew then + sprite, spriteTrueColor = self.newSprite, self.newSpriteTrueColor + else + sprite, spriteTrueColor = self.oldSprite, self.oldSpriteTrueColor + end end if sprite then - love.graphics.draw(sprite, math.floor((160 - sprite:getWidth()) / 2), - math.max(8, 64 - sprite:getHeight())) + local x = math.floor((160 - sprite:getWidth()) / 2) + local y = math.max(8, 64 - sprite:getHeight()) + love.graphics.draw(sprite, x, y) + if spriteTrueColor then + require("src.render.PaletteFX").markTrueColor(x, y, sprite:getDimensions()) + end end love.graphics.setColor(0, 0, 0, 1) diff --git a/src/ui/TradeAnim.lua b/src/ui/TradeAnim.lua index 29f3dad5..423c2d0f 100644 --- a/src/ui/TradeAnim.lua +++ b/src/ui/TradeAnim.lua @@ -49,9 +49,10 @@ local function dexOf(game, mon) end local function spriteOf(game, mon) - local path = require("src.pokemon.Sprites").path(game.data, mon.species, "front", - { mon = mon, kind = "trade" }) - return tryImage(path) + local path, trueColor = require("src.pokemon.Sprites").path( + game.data, mon.species, "front", { mon = mon, kind = "trade" }) + local image = tryImage(path) + return image, image and trueColor or false end local function expand(game, key, subs) @@ -110,8 +111,8 @@ function TradeAnim.new(game, opts) cableBallAlt = tryImage(art.cableBallAlt or DEFAULT_ART.cableBallAlt), bubble = tryImage(art.bubble or DEFAULT_ART.bubble), } - self.sentSprite = spriteOf(game, self.sent) - self.recvSprite = spriteOf(game, self.received) + self.sentSprite, self.sentSpriteTrueColor = spriteOf(game, self.sent) + self.recvSprite, self.recvSpriteTrueColor = spriteOf(game, self.received) self.seq = 1 self.phase = SEQ[1] @@ -349,8 +350,8 @@ function TradeAnim:drawMonInfo(mon, ot, otId, boxTy) love.graphics.setColor(1, 1, 1, 1) end -function TradeAnim:drawIconInBubble(mon, x, y) - local spr = (mon.species == self.received.species) and self.recvSprite or self.sentSprite +function TradeAnim:drawIconInBubble(sprite, x, y) + local spr = sprite if spr then local sw, sh = spr:getDimensions() local s = 16 / math.max(sw, sh) @@ -425,6 +426,10 @@ function TradeAnim:draw() -- hWY $50, so it sits in the bottom half of the screen if self.monVisible and self.sentSprite then love.graphics.draw(self.sentSprite, 56, 16) + if self.sentSpriteTrueColor then + require("src.render.PaletteFX").markTrueColor( + 56 - self.scx, 16, self.sentSprite:getDimensions()) + end end self:drawMonInfo(self.sent, self.playerOt, self.playerOtId, 10) love.graphics.pop() @@ -466,8 +471,13 @@ function TradeAnim:draw() love.graphics.translate(160, 0) self:drawRightGB() love.graphics.pop() - local mon = (p == "transfer_lr") and self.sent or self.received - self:drawIconInBubble(mon, self.monX, self.monY) + local sprite + if p == "transfer_lr" then + sprite = self.sentSprite + else + sprite = self.recvSprite + end + self:drawIconInBubble(sprite, self.monX, self.monY) if self.cableFlash then love.graphics.setColor(1, 1, 1, 0.15) love.graphics.rectangle("fill", 0, 32, 160, 8) @@ -477,6 +487,10 @@ function TradeAnim:draw() elseif p == "show_enemy" then if self.monVisible and self.recvSprite then love.graphics.draw(self.recvSprite, 56, 16) + if self.recvSpriteTrueColor then + require("src.render.PaletteFX").markTrueColor( + 56, 16, self.recvSprite:getDimensions()) + end end self:drawMonInfo(self.received, self.enemyName, self.enemyOtId, 10) end diff --git a/tests/drivers/evolution_true_color_bug494_test.lua b/tests/drivers/evolution_true_color_bug494_test.lua new file mode 100644 index 00000000..07ec5da7 --- /dev/null +++ b/tests/drivers/evolution_true_color_bug494_test.lua @@ -0,0 +1,56 @@ +-- Driver: inspect a full-color Pokémon while it evolves (#494). The sprite +-- is normally protected from the ADVANCED palette pass by its trueColor flag. +-- EvolutionState used the image but dropped that flag, so the full-color pic +-- was recolored as if it were a four-shade SGB sprite. +-- +-- Run: +-- POKEPORT_DRIVER=tests/drivers/evolution_true_color_bug494_test.lua \ +-- POKEPORT_IDENTITY=bug494 POKEPORT_TOUCH=0 POKEPORT_VERSION=red love . +return function(game) + local U = dofile("tests/drivers/util.lua") + local PaletteFX = require("src.render.PaletteFX") + local Evolution = require("src.pokemon.Evolution") + local Pokemon = require("src.pokemon.Pokemon") + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + game.save.options = game.save.options or {} + game.save.options.colors = "redpp" + PaletteFX.setMode("redpp") + + -- The stock artwork is a four-shade source image. Set the same runtime + -- contract a full-color sprite mod uses so this driver reaches the palette + -- boundary that #494 exposed, without turning the visual result into a test. + local pikachu = game.data.pokemon.PIKACHU + local raichu = game.data.pokemon.RAICHU + check("PIKACHU and RAICHU data are available", pikachu ~= nil and raichu ~= nil) + if not (pikachu and raichu) then return end + pikachu.trueColor = true + raichu.trueColor = true + check("ADVANCED color mode is active", PaletteFX.mode == "redpp") + + U.teleport(game, "ROUTE_1", 5, 5, "down") + U.wait(10) + check("the overworld fixture is ready", game.overworld ~= nil) + + local mon = Pokemon.new(game.data, "PIKACHU", 20) + game.save.party = { mon } + Evolution.evolve(game, mon, "RAICHU") + U.wait(12) + + local top = game.stack:top() + check("the evolution screen opened", top and top.screenId == "EvolutionState") + U.log("Issue #494: true-color sprites during evolutions and trades") + U.log("Watch this PIKACHU evolve into RAICHU in ADVANCED colors.") + U.log("Right: both sprites keep their source colors during the flash and") + U.log("the congratulations screen. Wrong: either sprite is recolored by") + U.log("the surrounding four-shade palette. Press B during the flash to") + U.log("also inspect the cancelled PIKACHU screen.") + + while true do + coroutine.yield() + end +end diff --git a/tests/parity_true_color_ui.lua b/tests/parity_true_color_ui.lua index dae04ac8..415f470a 100644 --- a/tests/parity_true_color_ui.lua +++ b/tests/parity_true_color_ui.lua @@ -16,8 +16,10 @@ require("src.render.Font").load(Data) local PaletteFX = require("src.render.PaletteFX") local Sound = require("src.core.Sound") local DexEntryMenu = require("src.ui.DexEntryMenu") +local EvolutionState = require("src.ui.EvolutionState") local TitleState = require("src.ui.TitleState") local OakSpeech = require("src.ui.OakSpeech") +local TradeAnim = require("src.ui.TradeAnim") local savedCry = Sound.playCry Sound.playCry = function() end @@ -33,7 +35,10 @@ end local def = Data.pokemon.PIKACHU local savedTrueColor = def.trueColor +local raichu = Data.pokemon.RAICHU +local savedRaichuTrueColor = raichu.trueColor def.trueColor = true +raichu.trueColor = true local game = { data = Data, @@ -50,6 +55,39 @@ check(#dexRects == 1 and dexRects[1].x == dx and dexRects[1].y == dy and dexRects[1].h == dex.sprite:getHeight(), "Pokedex reports the true-color sprite rectangle") +local evolving = EvolutionState.new(game, { species = "PIKACHU" }, "RAICHU") +check(evolving.oldSpriteTrueColor == true, + "evolution keeps the current Pokemon sprite's trueColor flag") +check(evolving.newSpriteTrueColor == true, + "evolution keeps the evolved Pokemon sprite's trueColor flag") +local evoRects = uiRects(function() evolving:draw() end) +local ex = math.floor((160 - evolving.oldSprite:getWidth()) / 2) +local ey = math.max(8, 64 - evolving.oldSprite:getHeight()) +check(#evoRects == 1 and evoRects[1].x == ex and evoRects[1].y == ey + and evoRects[1].w == evolving.oldSprite:getWidth() + and evoRects[1].h == evolving.oldSprite:getHeight(), + "evolution reports the true-color sprite rectangle") + +local trade = TradeAnim.new(game, { + sent = { species = "PIKACHU" }, received = { species = "RAICHU" }, +}) +check(trade.sentSpriteTrueColor == true, + "trade keeps the sent Pokemon sprite's trueColor flag") +check(trade.recvSpriteTrueColor == true, + "trade keeps the received Pokemon sprite's trueColor flag") +local tradeRects = uiRects(function() trade:draw() end) +check(#tradeRects == 1 and tradeRects[1].x == 56 and tradeRects[1].y == 16 + and tradeRects[1].w == trade.sentSprite:getWidth() + and tradeRects[1].h == trade.sentSprite:getHeight(), + "trade reports the sent true-color sprite rectangle") +trade.phase = "show_enemy" +local receivedRects = uiRects(function() trade:draw() end) +check(#receivedRects == 1 and receivedRects[1].x == 56 + and receivedRects[1].y == 16 + and receivedRects[1].w == trade.recvSprite:getWidth() + and receivedRects[1].h == trade.recvSprite:getHeight(), + "trade reports the received true-color sprite rectangle") + local titleGame = { data = { pokemon = Data.pokemon, field = { title = { cycleSpecies = { "PIKACHU" } } } }, @@ -83,6 +121,7 @@ check(#oakRects == 1 and oakRects[1].x == ox and oakRects[1].y == oy "Oak intro reports the true-color sprite rectangle") def.trueColor = savedTrueColor +raichu.trueColor = savedRaichuTrueColor Sound.playCry = savedCry PaletteFX.clearTrueColor() S.finish()