mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 18:00:50 +02:00
201 lines
8.7 KiB
Lua
201 lines
8.7 KiB
Lua
-- Where a shiny SHOWS on the flat art: the battle pics and the status page.
|
|
--
|
|
-- The Stadium models carry genuinely recoloured texels (see ShinyPalette and
|
|
-- the extraction). Everything drawn as a Game Boy pic cannot, because the
|
|
-- engine bakes a species palette into an image cache keyed by path and
|
|
-- palette name -- a cache with no notion of WHICH Rattata is being drawn. So
|
|
-- the flat side is answered two ways:
|
|
--
|
|
-- the battle pic tinted at draw time, per side, with the multiply
|
|
-- ShinyPalette.tintFor derives from that species' own
|
|
-- shiny slide. Under ADVANCED (`redpp`, the pokered-gbc
|
|
-- colour pack) the pic is at its most colourful and the
|
|
-- tint reads clearly; under the DMG modes there is
|
|
-- barely any colour to shift, which is why the status
|
|
-- page also carries a plain, mode-proof MARK.
|
|
-- the status page a star beside the level, drawn in the engine's own
|
|
-- GB pixel grid so it is palette-processed like every
|
|
-- other pixel on the screen rather than floating over
|
|
-- the finished frame.
|
|
--
|
|
-- Both are monkeypatches, idempotent by sentinel, the pattern the rest of
|
|
-- this mod uses (OverworldBattle.install, Stadium.install). The summary
|
|
-- screen has no hook at all -- there is no `ui.summary.*` anywhere in the
|
|
-- engine -- so a wrap is the only route to it, and it is deliberately a thin
|
|
-- one: draw the engine's screen, then add one glyph.
|
|
|
|
-- the mod namespace (see main.lua): V.require loads a sibling module
|
|
local V = ...
|
|
|
|
local Shiny = V.require("Shiny")
|
|
local ShinyPalette = V.require("ShinyPalette")
|
|
|
|
local ShinyUI = {}
|
|
|
|
-- ------- the tint, applied to one draw
|
|
--
|
|
-- Lifted in shape from OverworldBattle.withTint, and for the same reason it
|
|
-- exists there: the pics layer sets its own colour many times over as it
|
|
-- draws (the faint slide's fade, the damage blink), so the way to tint the
|
|
-- result without clobbering any of that is to multiply every colour it sets
|
|
-- on its way past. Restored unconditionally, including on error, because a
|
|
-- leaked setColor would tint the entire rest of the frame.
|
|
function ShinyUI.withTint(tint, fn, ...)
|
|
if not tint then return fn(...) end
|
|
local r, g, b = tint[1] or 1, tint[2] or 1, tint[3] or 1
|
|
if r > 0.999 and g > 0.999 and b > 0.999 then return fn(...) end
|
|
local gfx = love.graphics
|
|
local setColor = gfx.setColor
|
|
gfx.setColor = function(cr, cg, cb, ca, ...)
|
|
if type(cr) == "table" then
|
|
return setColor({ (cr[1] or 1) * r, (cr[2] or 1) * g, (cr[3] or 1) * b,
|
|
cr[4] }, cg, ...)
|
|
end
|
|
if cr == nil then return setColor(cr, cg, cb, ca, ...) end
|
|
return setColor(cr * r, (cg or 1) * g, (cb or 1) * b, ca, ...)
|
|
end
|
|
local ok, err = pcall(fn, ...)
|
|
gfx.setColor = setColor
|
|
setColor(1, 1, 1, 1)
|
|
if not ok then error(err, 0) end
|
|
end
|
|
|
|
-- The tint for a mon, or nil when it is not shiny or we have no colours.
|
|
function ShinyUI.tintFor(mon, data)
|
|
if not (mon and Shiny.isShiny(mon)) then return nil end
|
|
local def = data and data.pokemon and data.pokemon[mon.species]
|
|
local dex = def and def.dex
|
|
if not dex then return nil end
|
|
return ShinyPalette.tintFor(dex)
|
|
end
|
|
|
|
-- ------- the star
|
|
--
|
|
-- Drawn as rectangles rather than as a font character because the Game Boy
|
|
-- charmap has no star, and a letter would read as a typo. Four spokes and a
|
|
-- centre, in the same near-black the screen's text uses, so the palette pass
|
|
-- treats it exactly like a glyph -- under ADVANCED and under every DMG mode
|
|
-- alike.
|
|
--
|
|
-- Seven pixels square, which is the largest that fits the gap beside the
|
|
-- level without touching the DrawLineBox bracket at column 19.
|
|
function ShinyUI.drawStar(px, py)
|
|
local g = love.graphics
|
|
local r, gg, b, a = g.getColor()
|
|
g.setColor(0, 0, 0, 1)
|
|
-- vertical, horizontal, then the four diagonal nubs
|
|
g.rectangle("fill", px + 3, py, 1, 7)
|
|
g.rectangle("fill", px, py + 3, 7, 1)
|
|
g.rectangle("fill", px + 1, py + 1, 1, 1)
|
|
g.rectangle("fill", px + 5, py + 1, 1, 1)
|
|
g.rectangle("fill", px + 1, py + 5, 1, 1)
|
|
g.rectangle("fill", px + 5, py + 5, 1, 1)
|
|
g.setColor(r, gg, b, a)
|
|
end
|
|
|
|
-- ------- install
|
|
|
|
function ShinyUI.install()
|
|
ShinyUI.installSummary()
|
|
end
|
|
|
|
-- The status page. Wraps the draw and adds the star afterwards, so the
|
|
-- engine's own layout is untouched and a layout change upstream costs us
|
|
-- the glyph's position and nothing else.
|
|
function ShinyUI.installSummary()
|
|
local ok, SummaryMenu = pcall(require, "src.ui.SummaryMenu")
|
|
if not ok or type(SummaryMenu) ~= "table" then return end
|
|
if SummaryMenu.dramaticShapeShiny then return end
|
|
local inner = SummaryMenu.draw
|
|
if type(inner) ~= "function" then return end
|
|
|
|
function SummaryMenu:draw(...)
|
|
local out = { inner(self, ...) }
|
|
-- page 1 only: page 2 wipes the block the level sits in
|
|
-- (status_screen.asm ClearScreenArea over (9,2)), so a mark left there
|
|
-- would be half-erased by the engine's own clear.
|
|
if self.page == 1 and Shiny.isShiny(self.mon) then
|
|
-- beside PrintLevel at (14,2): column 13, row 2, in the gap the
|
|
-- level's own leading space leaves
|
|
pcall(ShinyUI.drawStar, 104, 17)
|
|
end
|
|
return unpack(out)
|
|
end
|
|
|
|
-- The summary PIC, through its PALETTE.
|
|
--
|
|
-- Recolouring the sprite's pixels here does NOT work, and it is worth
|
|
-- writing down why rather than leaving it to be re-attempted: the summary
|
|
-- art is four-shade DMG grey, and the screen's colour is applied
|
|
-- afterwards by the palette pass over the finished frame. Whatever RGB is
|
|
-- put in the ImageData is remapped away by it. The colour of that pic
|
|
-- lives in the palette and nowhere else, so the palette is what has to
|
|
-- move. (Tried it, shot it, reverted it.)
|
|
--
|
|
-- This is the ADVANCED-palette answer, and it is better than a multiply:
|
|
-- SetPal_StatusScreen hands the pic zone the species palette
|
|
-- (PaletteFX.monPal), and sgbPalettes is a method on the MENU, so unlike
|
|
-- the battle pic's image cache it knows which individual is on screen.
|
|
-- Running those four colours through the species' own shiny transform
|
|
-- gives the summary a genuinely recoloured Pokemon -- brightening
|
|
-- included, which a draw-colour multiply cannot do.
|
|
--
|
|
-- Only ZONE entries are touched. The first palette the engine returns is
|
|
-- the whole-screen HP-bar one, and rotating that would recolour the text.
|
|
local innerPal = SummaryMenu.sgbPalettes
|
|
if type(innerPal) == "function" then
|
|
function SummaryMenu:sgbPalettes(game, ...)
|
|
local out = innerPal(self, game, ...)
|
|
if type(out) ~= "table" or not Shiny.isShiny(self.mon) then return out end
|
|
local def = game and game.data and game.data.pokemon
|
|
and game.data.pokemon[self.mon.species]
|
|
local fn = def and def.dex
|
|
and ShinyPalette.transform(ShinyPalette.forDex(def.dex))
|
|
if not fn then return out end
|
|
for _, z in ipairs(out) do
|
|
if type(z) == "table" and z.w and z.h and type(z.colors) == "table" then
|
|
-- copied, never mutated in place: monPal hands back the dataset's
|
|
-- own palette table, and writing through it would recolour every
|
|
-- Pokemon of the species everywhere for the rest of the process
|
|
local cols = {}
|
|
for i, c in ipairs(z.colors) do
|
|
if type(c) == "table" and c[1] then
|
|
local r, g, b = fn(c[1], c[2], c[3])
|
|
cols[i] = { r, g, b }
|
|
else
|
|
cols[i] = c
|
|
end
|
|
end
|
|
z.colors = cols
|
|
end
|
|
end
|
|
return out
|
|
end
|
|
end
|
|
|
|
SummaryMenu.dramaticShapeShiny = true
|
|
end
|
|
|
|
-- ------- the battle pics are NOT tinted here any more
|
|
--
|
|
-- There used to be a third wrap in this file: a multiply over
|
|
-- BattleState:drawPicsLayer, with the tint above. It is gone, and the reason
|
|
-- is worth keeping so it is not put back.
|
|
--
|
|
-- A multiply can only DARKEN. Shiny Gyarados is blue turning red, and the
|
|
-- nearest a multiply gets to that is a dimmer blue -- so every species whose
|
|
-- shiny is lighter, or is a rotation rather than a dimming, read as the
|
|
-- ordinary one with the brightness down. And the engine's pic layer draws
|
|
-- BOTH sides in one call, so a shiny also dimmed the ordinary mon opposite it.
|
|
--
|
|
-- lib/ShinyPics.lua replaces it by moving the PALETTE instead, which is where
|
|
-- a battle pic's colour actually lives: getImage bakes the four DMG shades
|
|
-- into the species palette once and caches the result, so handing that bake a
|
|
-- shiny palette (under a cache name of its own) gives a genuinely recoloured
|
|
-- pic -- brightening included -- for one side alone.
|
|
--
|
|
-- ShinyUI.withTint and ShinyUI.tintFor stay: the 3D path still uses them for
|
|
-- the per-side canvas, and they are the only tint left in the mod.
|
|
|
|
return ShinyUI
|